From 624e6f2db61f16342d6117399d1dba22452c3bad Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Wed, 4 Jun 2025 20:07:18 -0400 Subject: [PATCH] Added function to remove a song from the library/ playlist --- dmp-core/src/music_controller/controller.rs | 2 + .../src/music_controller/controller_handle.rs | 17 +++ .../src/music_controller/library_command.rs | 46 ++++++- dmp-core/src/music_storage/library.rs | 12 +- dmp-core/src/music_storage/playlist.rs | 29 ++++- src-tauri/src/commands.rs | 38 +++--- src-tauri/src/lib.rs | 5 +- src-tauri/src/wrappers.rs | 2 +- src/App.tsx | 122 +++++++++--------- 9 files changed, 185 insertions(+), 88 deletions(-) diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index 2e19d6c..46475b7 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -108,6 +108,7 @@ pub enum LibraryCommand { Song(Uuid), AllSongs, GetLibrary, + LibraryRemoveSong(Uuid), ExternalPlaylist(Uuid), PlaylistSong { list_uuid: Uuid, item_uuid: Uuid }, Playlist(Uuid), @@ -115,6 +116,7 @@ pub enum LibraryCommand { Save, Playlists, PlaylistAddSong { playlist: Uuid, song: Uuid }, + PlaylistRemoveSong { playlist: Uuid, song: Uuid }, DeletePlaylist(Uuid), } diff --git a/dmp-core/src/music_controller/controller_handle.rs b/dmp-core/src/music_controller/controller_handle.rs index 3a9170d..260682d 100644 --- a/dmp-core/src/music_controller/controller_handle.rs +++ b/dmp-core/src/music_controller/controller_handle.rs @@ -46,6 +46,14 @@ impl ControllerHandle { }; } + pub async fn lib_remove_song(&self, song: Uuid) { + let (command, tx) = LibraryCommandInput::command(LibraryCommand::LibraryRemoveSong(song)); + self.lib_mail_rx.send(command).await.unwrap(); + let LibraryResponse::Ok = tx.recv().await.unwrap() else { + unreachable!() + }; + } + // The Playlist Section pub async fn playlist_get(&self, uuid: Uuid) -> Result { let (command, tx) = LibraryCommandInput::command(LibraryCommand::ExternalPlaylist(uuid)); @@ -92,6 +100,15 @@ impl ControllerHandle { }; } + pub async fn playlist_remove_song(&self, song: Uuid, playlist: Uuid) { + let (command, tx) = + LibraryCommandInput::command(LibraryCommand::PlaylistRemoveSong { song, playlist }); + self.lib_mail_rx.send(command).await.unwrap(); + let LibraryResponse::Ok = tx.recv().await.unwrap() else { + unreachable!() + }; + } + // The Queue Section pub async fn queue_append( &self, diff --git a/dmp-core/src/music_controller/library_command.rs b/dmp-core/src/music_controller/library_command.rs index d99bed0..c04e643 100644 --- a/dmp-core/src/music_controller/library_command.rs +++ b/dmp-core/src/music_controller/library_command.rs @@ -108,7 +108,18 @@ impl Controller { LibraryCommand::PlaylistAddSong { playlist, song } => { let playlist = library.query_playlist_uuid_mut(&playlist).unwrap(); playlist.add_track(song); - library.save(config.read().path.clone()).unwrap(); + let lib_uuid = library.uuid; + library + .save( + config + .read() + .libraries + .get_library(&lib_uuid) + .unwrap() + .path + .clone(), + ) + .unwrap(); res_rx.send(LibraryResponse::Ok).await.unwrap(); } LibraryCommand::DeletePlaylist(uuid) => { @@ -127,6 +138,39 @@ impl Controller { .unwrap(); res_rx.send(LibraryResponse::Ok).await.unwrap(); } + LibraryCommand::LibraryRemoveSong(uuid) => { + library.remove_uuid(&uuid).unwrap(); + let lib_uuid = library.uuid; + library + .save_path( + &config + .read() + .libraries + .get_library(&lib_uuid) + .unwrap() + .path + .clone(), + ) + .unwrap(); + res_rx.send(LibraryResponse::Ok).await.unwrap(); + } + LibraryCommand::PlaylistRemoveSong { playlist, song } => { + library.playlists.delete_song(song, &playlist).unwrap(); + + let lib_uuid = library.uuid; + library + .save( + config + .read() + .libraries + .get_library(&lib_uuid) + .unwrap() + .path + .clone(), + ) + .unwrap(); + res_rx.send(LibraryResponse::Ok).await.unwrap(); + } _ => { todo!() } diff --git a/dmp-core/src/music_storage/library.rs b/dmp-core/src/music_storage/library.rs index 8e2c36d..985ced2 100644 --- a/dmp-core/src/music_storage/library.rs +++ b/dmp-core/src/music_storage/library.rs @@ -18,7 +18,6 @@ use file_format::{FileFormat, Kind}; use lofty::file::{AudioFile as _, TaggedFileExt as _}; use lofty::probe::Probe; use lofty::tag::{ItemKey, ItemValue, TagType}; -use rayon::iter::plumbing::Folder; use rcue::parser::parse_from_file; use std::fs; use std::path::{Path, PathBuf}; @@ -985,6 +984,17 @@ impl MusicLibrary { Ok(location) } + pub fn remove_uuid(&mut self, target_uuid: &Uuid) -> Result> { + let location = match self.query_uuid(target_uuid) { + Some(value) => value.1, + None => return Err("Uuid not in database".into()), + }; + + self.library.remove(location); + + Ok(location) + } + /// Scan the song by a location and update its tags // TODO: change this to work with multiple uris pub fn update_uri( diff --git a/dmp-core/src/music_storage/playlist.rs b/dmp-core/src/music_storage/playlist.rs index 7880771..c340197 100644 --- a/dmp-core/src/music_storage/playlist.rs +++ b/dmp-core/src/music_storage/playlist.rs @@ -14,6 +14,7 @@ use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; use chrono::format::Item; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use thiserror::Error; use uuid::Uuid; use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; @@ -99,6 +100,20 @@ impl PlaylistFolder { None } } + + pub fn delete_song(&mut self, song: Uuid, playlist: &Uuid) -> Result<(), PlaylistError> { + match self.query_uuid_mut(playlist) { + Some(list) => { + if let Some((_, index)) = list.query_uuid(&song) { + list.remove_track(index); + Ok(()) + } else { + Err(PlaylistError::NoUuid(song, *playlist)) + } + } + None => Err(PlaylistError::NoPlaylist(*playlist)), + } + } } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -146,9 +161,9 @@ impl Playlist { self.tracks.push(track); } - pub fn remove_track(&mut self, index: i32) { - let index = index as usize; - if (self.tracks.len() - 1) >= index { + pub fn remove_track(&mut self, index: usize) { + let len = self.tracks.len(); + if len > 0 && (len - 1) >= index { self.tracks.remove(index); } } @@ -453,6 +468,14 @@ impl ExternalPlaylist { } } +#[derive(Debug, Error)] +pub enum PlaylistError { + #[error("No uuid {0} found in playlist {1}")] + NoUuid(Uuid, Uuid), + #[error("No Playlist found with uuid {0}")] + NoPlaylist(Uuid), +} + #[cfg(test)] mod test_super { use super::*; diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index eddc3c5..ab46b55 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -11,7 +11,7 @@ use tauri::{AppHandle, Emitter, State, Wry}; use tempfile::TempDir; use uuid::Uuid; -use crate::{wrappers::_Song, LAST_FM_API_KEY, LAST_FM_API_SECRET}; +use crate::{LAST_FM_API_KEY, LAST_FM_API_SECRET, wrappers::_Song}; #[tauri::command] pub async fn add_song_to_queue( @@ -93,19 +93,23 @@ pub async fn last_fm_init_auth(ctrl_handle: State<'_, ControllerHandle>) -> Resu Ok(()) } -// #[tauri::command] -// pub async fn test_menu( -// ctrl_handle: State<'_, ControllerHandle>, -// app: AppHandle, -// window: Window, -// uuid: Uuid, -// ) -> Result<(), String> { -// let handle = app.app_handle(); -// let menu = MenuBuilder::new(handle) -// .item(&MenuItem::new(handle, "Add to Queue", true, None::<&str>).unwrap()) -// .build() -// .unwrap(); -// window.set_menu(menu).unwrap(); -// println!("Menu popup!"); -// Ok(()) -// } +#[tauri::command] +pub async fn remove_from_lib_playlist( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, + song: Uuid, + location: PlayerLocation, +) -> Result<(), String> { + match location { + PlayerLocation::Library => { + ctrl_handle.lib_remove_song(song).await; + app.emit("library_loaded", ()).unwrap(); + } + PlayerLocation::Playlist(uuid) => { + ctrl_handle.playlist_remove_song(song, uuid).await; + } + _ => unimplemented!(), + } + + Ok(()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d03cbff..ceb9ad5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -28,7 +28,9 @@ use crate::wrappers::{ get_queue, get_song, import_playlist, next, pause, play, play_next_queue, prev, remove_from_queue, seek, set_volume, }; -use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now}; +use commands::{ + add_song_to_queue, display_album_art, last_fm_init_auth, play_now, remove_from_lib_playlist, +}; pub mod commands; pub mod config; @@ -74,6 +76,7 @@ pub fn run() { delete_playlist, play_next_queue, clear_queue, + remove_from_lib_playlist, // test_menu, ]) .manage(tempfile::TempDir::new().unwrap()) diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index c6bee53..1b74ad4 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -316,6 +316,6 @@ pub async fn clear_queue( ctrl_handle: State<'_, ControllerHandle>, ) -> Result<(), String> { let res = ctrl_handle.queue_clear().await.map_err(|e| e.to_string()); - app.emit("queue_updated", ()); + _ = app.emit("queue_updated", ()); res } diff --git a/src/App.tsx b/src/App.tsx index 372e804..e03be31 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -116,17 +116,39 @@ interface PlaylistHeadProps { } function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playlistsInfo }: PlaylistHeadProps) { + function getPlaylist(playlist: PlaylistInfo) { + invoke('get_playlist', { uuid: playlist.uuid }).then((list) => { + setLibrary([...(list as any[]).map((song) => { + // console.log(song); + const reload = () => getPlaylist(playlist) + return ( + + ) + })]) + }) + setViewName( playlist.name ) + } useEffect(() => { const unlisten = appWindow.listen("playlists_gotten", (_res) => { // console.log(event); let res = _res.payload as PlaylistInfo[]; playlistsInfo.current = [...res]; - console.log(playlistsInfo, res); + // console.log(playlistsInfo, res); setPlaylists([ ...res.map( (list) => { - + const _getPlaylist = () => getPlaylist(list) const deletePlaylist = () => { invoke('delete_playlist', { uuid: list.uuid }).then(() => {}); invoke('get_playlists').then(() => {}); @@ -139,30 +161,10 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli ] }); menu.popup(); - } + } return ( - ) @@ -177,31 +179,9 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli setPlaylists([ ...playlists, - + ]) - console.log(res.name); + // console.log(res.name); }) } return ( @@ -210,11 +190,11 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli setViewName("Library"); invoke('get_library').then((lib) => { setLibrary([...(lib as any[]).map((song) => { - console.log(song); + // console.log(song); return ( + playlists: MutableRefObject, + reload?: () => void } function Song(props: SongProps) { // console.log(props.tags); - const add_to_queue_test = (_: string) => { + const addToQueue = (_: string) => { invoke('add_song_to_queue', { uuid: props.uuid, location: props.playerLocation }).then(() => {}); } - + const playNow = () => invoke("play_now", { uuid: props.uuid, location: props.playerLocation }).then(() => {}) + const playNext = () => invoke("play_next_queue", { uuid: props.uuid, location: props.playerLocation }).then(() => {}) + const removeLibPlaylist = () => { + invoke("remove_from_lib_playlist", { song: props.uuid, location: props.playerLocation }).then(() => { + if (props.reload !== undefined) { + props.reload() + } + }) + } async function clickHandler(event: React.MouseEvent) { event.preventDefault(); - console.log(props.playlists); + const _ = await invoke('get_playlists'); + let removeText = "Remove from Library"; + if (props.playerLocation != "Library") { + removeText = "Remove from Playlist"; + } const menu = await Menu.new({ items: [ - { id: "add_song_to_queue" + props.uuid, text: "Add to Queue", action: add_to_queue_test }, + { id: "play_now_" + props.uuid, text: "Play Now", action: playNow }, + { id: "play_next_" + props.uuid, text: "Play Next", action: playNext }, + { id: "add_song_to_queue" + props.uuid, text: "Add to Queue", action: addToQueue }, await Submenu.new( { text: "Add to Playlist...", @@ -314,16 +309,17 @@ function Song(props: SongProps) { invoke('add_song_to_playlist', { playlist: list.uuid, song: props.uuid }).then(() => {}); } return { id: "add_song_to_playlists" + props.uuid + list.uuid, text: list.name, action: addToPlaylist } - })] + })] } as SubmenuOptions - ) + ), + { id: "remove_from_lib_playlist" + props.location + props.uuid, text: removeText, action: removeLibPlaylist }, ] }) ; const pos = new LogicalPosition(event.clientX, event.clientY); menu.popup(pos); } - + // useEffect(() => { // const unlistenPromise = listen("add_song_to_queue", (event) => { // switch (event.payload) { @@ -339,10 +335,8 @@ function Song(props: SongProps) { return(
{ - invoke("play_now", { uuid: props.uuid, location: props.playerLocation }).then(() => {}) - }} - onContextMenu={clickHandler} + onDoubleClick = { playNow } + onContextMenu={ clickHandler } className="song">

{ props.tags.TrackArtist }

{ props.tags.TrackTitle }

@@ -484,7 +478,7 @@ function QueueSong({ song, location, index }: QueueSongProps) { { id: "play_next_" + song.uuid + index, text: "Play Next in Queue", action: playNext }, { id: "remove_queue" + song.uuid + index, text: "Remove from Queue", action: removeFromQueue }, { id: "clear_queue", text: "Clear Queue", action: clearQueue }, - ] + ] }) menu.popup(); } @@ -510,4 +504,4 @@ function getConfig(): any { invoke('lib_already_created').then(() => {}) } }) -} +} \ No newline at end of file