From 4f2d5ab64a77fbe45003aa50a4875d9a7a312778 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Wed, 25 Dec 2024 18:26:29 -0500 Subject: [PATCH] Added Controller State and Playlist loading on startup --- dmp-core/src/config/mod.rs | 12 +- dmp-core/src/config/other_settings.rs | 7 - dmp-core/src/music_controller/controller.rs | 138 ++++++++++++++++---- dmp-core/src/music_storage/playlist.rs | 40 +++++- src-tauri/src/commands.rs | 1 + src-tauri/src/lib.rs | 9 +- src-tauri/src/wrappers.rs | 21 ++- src/App.tsx | 47 ++++++- 8 files changed, 220 insertions(+), 55 deletions(-) delete mode 100644 dmp-core/src/config/other_settings.rs diff --git a/dmp-core/src/config/mod.rs b/dmp-core/src/config/mod.rs index 85bfffe..b11bb38 100644 --- a/dmp-core/src/config/mod.rs +++ b/dmp-core/src/config/mod.rs @@ -1,9 +1,7 @@ -pub mod other_settings; - use std::{ fs::{self, File, OpenOptions}, io::{Error, Read, Write}, - path::PathBuf, + path::{Path, PathBuf}, }; use serde::{Deserialize, Serialize}; @@ -100,8 +98,8 @@ pub struct Config { pub path: PathBuf, pub backup_folder: Option, pub libraries: ConfigLibraries, - pub volume: f32, pub connections: ConfigConnections, + pub state_path: PathBuf, } impl Config { @@ -160,9 +158,9 @@ impl Config { pub fn read_file(path: PathBuf) -> Result { let mut file: File = File::open(path)?; - let mut bun: String = String::new(); - _ = file.read_to_string(&mut bun); - let config: Config = serde_json::from_str::(&bun)?; + let mut buf: String = String::new(); + _ = file.read_to_string(&mut buf); + let config: Config = serde_json::from_str::(&buf)?; Ok(config) } diff --git a/dmp-core/src/config/other_settings.rs b/dmp-core/src/config/other_settings.rs deleted file mode 100644 index 1b80e94..0000000 --- a/dmp-core/src/config/other_settings.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub enum Setting { - String { name: String, value: String }, - Int { name: String, value: i32 }, - Bool { name: String, value: bool }, -} - -pub struct Form {} diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index 895a1bf..3ddb4c1 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -6,15 +6,19 @@ use itertools::Itertools; use kushi::{Queue, QueueItemType}; use kushi::{QueueError, QueueItem}; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; +use serde_json::to_string_pretty; use std::error::Error; +use std::fs::OpenOptions; +use std::io::Write; use std::marker::PhantomData; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; use thiserror::Error; use uuid::Uuid; -use crate::config::ConfigError; +use crate::config::{self, ConfigError}; use crate::music_player::player::{Player, PlayerError}; use crate::music_storage::library::Song; use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}; @@ -75,7 +79,7 @@ pub enum PlayerCommand { Pause, Play, Enqueue(usize), - SetVolume(f64), + SetVolume(f32), PlayNow(Uuid, PlayerLocation), } @@ -90,8 +94,11 @@ pub enum LibraryCommand { Song(Uuid), AllSongs, GetLibrary, + ExternalPlaylist(Uuid), Playlist(Uuid), - ImportM3UPlayList(PathBuf) + ImportM3UPlayList(PathBuf), + Save, + Playlists, } #[derive(Debug, Clone)] @@ -100,8 +107,10 @@ pub enum LibraryResponse { Song(Song, usize), AllSongs(Vec), Library(MusicLibrary), - Playlist(ExternalPlaylist), - ImportM3UPlayList(Uuid, String) + ExternalPlaylist(ExternalPlaylist), + Playlist(Playlist), + ImportM3UPlayList(Uuid, String), + Playlists(Vec<(Uuid, String)>), } #[derive(Debug, PartialEq, Clone)] @@ -170,6 +179,39 @@ impl ControllerHandle { } } +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct ControllerState { + path: PathBuf, + volume: f32, + now_playing: Uuid, +} + +impl ControllerState { + fn new(path: PathBuf) -> Self { + ControllerState { + path, + volume: 0.35, + ..Default::default() + } + } + + fn write_file(&self) -> Result<(), std::io::Error> { + OpenOptions::new() + .truncate(true) + .create(true) + .write(true) + .open(&self.path) + .unwrap() + .write_all(&to_string_pretty(self)?.into_bytes())?; + Ok(()) + } + + fn read_file(path: impl AsRef) -> Result { + let state = serde_json::from_str(&std::fs::read_to_string(path)?)?; + Ok(state) + } +} + #[allow(unused_variables)] impl<'c, P: Player + Send + Sync> Controller<'c, P> { pub async fn start( @@ -184,23 +226,22 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { where P: Player, { - //TODO: make a separate event loop for sccessing library that clones borrowed values from inner library loop? - let mut queue: Queue = Queue { + let queue: Queue = Queue { items: Vec::new(), played: Vec::new(), loop_: false, shuffle: None, }; - // for testing porpuses - // for song in &library.library { - // queue.add_item( - // QueueSong { - // song: song.clone(), - // location: PlayerLocation::Test, - // }, - // true, - // ); - // } + + let state = { + let path = &config.read().unwrap().state_path; + if let Ok(state) = ControllerState::read_file(path) { + state + } else { + ControllerState::new(path.clone()) + } + }; + let queue = queue; std::thread::scope(|scope| { @@ -220,19 +261,27 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { player_mail.1, queue_mail.0, _lib_mail, + state, ) .await .unwrap(); }); scope .spawn(async move { - Controller::

::player_event_loop(player, player_mail.0) + Controller::

::player_event_loop( + player, + player_mail.0 + ) .await .unwrap(); }); scope .spawn(async { - Controller::

::library_loop(lib_mail.1, &mut library) + Controller::

::library_loop( + lib_mail.1, + &mut library, + config, + ) .await .unwrap(); }); @@ -258,8 +307,14 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { player_mail: MailMan, queue_mail: MailMan, lib_mail: MailMan, + mut state: ControllerState, ) -> Result<(), ()> { let mut first = true; + { + let volume = state.volume as f64; + player.write().unwrap().set_volume(volume); + println!("volume set to {volume}"); + } while true { let _mail = player_mail.recv().await; if let Ok(mail) = _mail { @@ -282,9 +337,12 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { player_mail.send(PlayerResponse::Empty).await.unwrap(); } PlayerCommand::SetVolume(volume) => { - player.write().unwrap().set_volume(volume); + player.write().unwrap().set_volume(volume as f64); println!("volume set to {volume}"); player_mail.send(PlayerResponse::Empty).await.unwrap(); + + state.volume = volume; + _ = state.write_file() } PlayerCommand::NextSong => { queue_mail.send(QueueCommand::Next).await.unwrap(); @@ -384,11 +442,26 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { // ... // let's just pretend I figured that out already - lib_mail.send(LibraryCommand::AllSongs).await.unwrap(); - let LibraryResponse::AllSongs(songs) = lib_mail.recv().await.unwrap() else { - unreachable!() + let (songs, index) = match location { + PlayerLocation::Library => { + lib_mail.send(LibraryCommand::AllSongs).await.unwrap(); + let LibraryResponse::AllSongs(songs) = lib_mail.recv().await.unwrap() else { + unreachable!() + }; + (songs, index) + } + PlayerLocation::Playlist(uuid) => { + lib_mail.send(LibraryCommand::ExternalPlaylist(uuid)).await.unwrap(); + let LibraryResponse::ExternalPlaylist(list) = lib_mail.recv().await.unwrap() else { + unreachable!() + }; + let index = list.get_index(song.uuid).unwrap(); + (list.tracks, index) + } + _ => todo!("Got Location other than Library or Playlist") }; + for i in index+1..(index+50) { if let Some(song) = songs.get(i) { queue_mail.send(QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), false)).await.unwrap(); @@ -396,7 +469,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { unreachable!() }; } else { - println!("End of Library"); + println!("End of Library / Playlist"); break; } } @@ -414,6 +487,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { async fn library_loop( lib_mail: MailMan, library: &'c mut MusicLibrary, + config: Arc>, ) -> Result<(), ()> { while true { match lib_mail.recv().await.unwrap() { @@ -424,9 +498,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { LibraryCommand::AllSongs => { lib_mail.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap(); }, - LibraryCommand::Playlist(uuid) => { + LibraryCommand::ExternalPlaylist(uuid) => { let playlist = library.query_playlist_uuid(&uuid).unwrap(); - lib_mail.send(LibraryResponse::Playlist(ExternalPlaylist::from_playlist(playlist, &library))).await.unwrap(); + lib_mail.send(LibraryResponse::ExternalPlaylist(ExternalPlaylist::from_playlist(playlist, &library))).await.unwrap(); } LibraryCommand::ImportM3UPlayList(path) => { let playlist = Playlist::from_m3u(path, library).unwrap(); @@ -436,6 +510,16 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { lib_mail.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap(); } + LibraryCommand::Save => { + library.save({config.read().unwrap().libraries.get_library(&library.uuid).unwrap().path.clone()}).unwrap(); + lib_mail.send(LibraryResponse::Ok).await.unwrap(); + } + LibraryCommand::Playlists => { + let mut lists = vec![]; + library.playlists.lists_recursive().into_par_iter().map(|list| (list.uuid, list.title.clone())).collect_into_vec(&mut lists); + + lib_mail.send(LibraryResponse::Playlists(lists)).await.unwrap(); + } _ => { todo!() } } } diff --git a/dmp-core/src/music_storage/playlist.rs b/dmp-core/src/music_storage/playlist.rs index bc6c1e3..5307108 100644 --- a/dmp-core/src/music_storage/playlist.rs +++ b/dmp-core/src/music_storage/playlist.rs @@ -52,6 +52,17 @@ impl PlaylistFolder { } None } + + pub fn lists_recursive(&self) -> Vec<&Playlist> { + let mut vec = vec![]; + for item in &self.items { + match item { + PlaylistFolderItem::List(ref playlist) => vec.push(playlist), + PlaylistFolderItem::Folder(folder) => vec.append(&mut folder.lists_recursive()), + } + } + vec + } } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -107,13 +118,13 @@ impl Playlist { } pub fn get_index(&self, uuid: Uuid) -> Option { let mut i = 0; - if self.contains(uuid) { + if self.tracks.contains(&uuid) { for track in &self.tracks { - i += 1; if &uuid == track { dbg!("Index gotted! ", i); return Some(i); } + i += 1; } } None @@ -351,7 +362,9 @@ pub struct ExternalPlaylist { impl ExternalPlaylist { pub(crate) fn from_playlist(playlist: &Playlist, library: &MusicLibrary) -> Self { let tracks: Vec = playlist.tracks.iter().filter_map(|uuid| { - library.query_uuid(uuid).map(|res| res.0.clone()) + library.query_uuid(uuid).map(|res| { + res.0.clone() + }) }).collect_vec(); Self { @@ -363,6 +376,27 @@ impl ExternalPlaylist { play_time: playlist.play_time } } + + pub fn get_index(&self, uuid: Uuid) -> Option { + let mut i = 0; + if self.contains(uuid) { + for track in &self.tracks { + if &uuid == &track.uuid { + return Some(i); + } + i += 1; + } + } + None + } + pub fn contains(&self, uuid: Uuid) -> bool { + for track in &self.tracks { + if track.uuid == uuid { + return true; + } + } + false + } } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 8075af9..4e57ed1 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -9,6 +9,7 @@ use crate::wrappers::_Song; #[tauri::command] pub async fn add_song_to_queue(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> { + dbg!(&location); ctrl_handle.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(uuid)).await.unwrap(); let LibraryResponse::Song(song, _) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!() diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d11eaee..d51c41b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -6,7 +6,7 @@ use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{C use tauri::{http::Response, Emitter, Manager, State, Url, WebviewWindowBuilder, Wry}; use uuid::Uuid; -use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist}; +use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists}; pub mod wrappers; pub mod commands; @@ -76,6 +76,7 @@ pub fn run() { play_now, import_playlist, get_playlist, + get_playlists ]).manage(ConfigRx(rx)) .manage(LibRx(lib_rx)) .manage(HandleTx(handle_tx)) @@ -142,11 +143,15 @@ async fn get_config(state: State<'_, ConfigRx>) -> Result { // dbg!(&dir); - let config = if let Ok(c) = Config::read_file(PathBuf::from(path).join("config")) { + let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) { + if c.state_path == PathBuf::default() { + c.state_path = PathBuf::from(path).join("state"); + } c } else { let c = Config { path: PathBuf::from(path).join("config"), + state_path: PathBuf::from(path).join("state"), ..Default::default() }; c.write_file().unwrap(); diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index a56d6ce..12b5baa 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -39,7 +39,7 @@ pub async fn pause(app: AppHandle, ctrl_handle: State<'_, ControllerHandle> #[tauri::command] pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String) -> Result<(), String> { - let volume = volume.parse::().unwrap() / 1000.0; + let volume = volume.parse::().unwrap() / 1000.0; ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::SetVolume(volume)).await.unwrap(); let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else { unreachable!() @@ -142,14 +142,25 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result, uuid: Uuid) -> Result, String> { - ctrl_handle.lib_mail.send(LibraryCommand::Playlist(uuid)).await.unwrap(); - let LibraryResponse::Playlist(playlist) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") }; + ctrl_handle.lib_mail.send(LibraryCommand::ExternalPlaylist(uuid)).await.unwrap(); + let LibraryResponse::ExternalPlaylist(playlist) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") }; let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::>(); println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len()); Ok(songs) } +#[tauri::command] +pub async fn get_playlists(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { + println!("getting Playlists"); + ctrl_handle.lib_mail.send(LibraryCommand::Playlists).await.unwrap(); + let LibraryResponse::Playlists(lists) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!() }; + println!("gotten playlists"); + + app.emit("playlists_gotten", lists.into_iter().map(|(uuid, name)| PlaylistPayload { uuid, name }).collect_vec()).unwrap(); + Ok(()) +} + #[tauri::command] pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result { let file = rfd::AsyncFileDialog::new() @@ -161,11 +172,13 @@ pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result ctrl_handle.lib_mail.send(LibraryCommand::ImportM3UPlayList(PathBuf::from(file.path()))).await.unwrap(); let LibraryResponse::ImportM3UPlayList(uuid, name) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") }; + ctrl_handle.lib_mail.send(LibraryCommand::Save).await.unwrap(); + let LibraryResponse::Ok = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!() }; println!("Imported Playlist {name}"); Ok(PlaylistPayload {uuid, name}) } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct PlaylistPayload { uuid: Uuid, name: String diff --git a/src/App.tsx b/src/App.tsx index 8582336..aac11da 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -98,7 +98,40 @@ interface PlaylistHeadProps { function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: PlaylistHeadProps) { - let handle_import = () => { + useEffect(() => { + const unlisten = appWindow.listen("playlists_gotten", (_res) => { + // console.log(event); + let res = _res.payload; + + setPlaylists([ + ...res.map( (item) => { + return ( + + ) + }) + ]) + }) + return () => { unlisten.then((f) => f()) } + }, []); + let handle_import = () => { invoke('import_playlist').then((_res) => { let res = _res as any; @@ -114,6 +147,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play Library { playlists } - + ) } @@ -166,15 +201,16 @@ function MainView({ lib_ref, viewName }: MainViewProps) { useEffect(() => { const unlisten = appWindow.listen("library_loaded", (_) => { console.log("library_loaded"); + invoke('get_playlists').then(() => {}) invoke('get_library').then((lib) => { setLibrary([...(lib as any[]).map((song) => { - console.log(song); return ( { props.tags.AlbumArtist }

{ props.duration }

)