From 2ac3acdf5ab15558f5bca62981ff0de88b2c6482 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Wed, 1 Jan 2025 04:51:24 -0500 Subject: [PATCH] ran `cargo fmt` --- dmp-core/src/config/mod.rs | 9 +- dmp-core/src/lib.rs | 46 +- dmp-core/src/music_controller/connections.rs | 182 +++-- dmp-core/src/music_controller/controller.rs | 719 +++++++++--------- .../src/music_controller/controller_handle.rs | 40 +- .../src/music_controller/library_command.rs | 72 +- .../src/music_controller/player_command.rs | 231 ++++-- .../src/music_controller/player_monitor.rs | 68 +- .../src/music_controller/queue_command.rs | 46 +- .../music_storage/db_reader/itunes/reader.rs | 13 +- dmp-core/src/music_storage/library.rs | 51 +- dmp-core/src/music_storage/playlist.rs | 54 +- kushi-queue/src/lib.rs | 67 +- src-tauri/src/commands.rs | 141 ++-- src-tauri/src/lib.rs | 592 +++++++------- src-tauri/src/main.rs | 1 - src-tauri/src/wrappers.rs | 156 ++-- 17 files changed, 1427 insertions(+), 1061 deletions(-) diff --git a/dmp-core/src/config/mod.rs b/dmp-core/src/config/mod.rs index 8a12053..931830b 100644 --- a/dmp-core/src/config/mod.rs +++ b/dmp-core/src/config/mod.rs @@ -29,7 +29,12 @@ impl Default for ConfigLibrary { } impl ConfigLibrary { - pub fn new(path: PathBuf, name: String, scan_folders: Option>, uuid: Option) -> Self { + pub fn new( + path: PathBuf, + name: String, + scan_folders: Option>, + uuid: Option, + ) -> Self { ConfigLibrary { name, path, @@ -197,7 +202,7 @@ pub mod tests { PathBuf::from("test-config/library"), String::from("library"), None, - None + None, ); let mut config = Config { path: PathBuf::from("test-config/config_test.json"), diff --git a/dmp-core/src/lib.rs b/dmp-core/src/lib.rs index 0ddd71a..9f433f4 100644 --- a/dmp-core/src/lib.rs +++ b/dmp-core/src/lib.rs @@ -1,23 +1,23 @@ -#![allow(while_true)] -pub mod music_storage { - pub mod library; - pub mod music_collection; - pub mod playlist; - mod utils; - - #[allow(dead_code)] - pub mod db_reader; -} - -pub mod music_controller { - pub mod connections; - pub mod controller; - pub mod controller_handle; - pub mod queue; - pub mod player_command; - pub mod player_monitor; - pub mod queue_command; - pub mod library_command; -} - -pub mod config; +#![allow(while_true)] +pub mod music_storage { + pub mod library; + pub mod music_collection; + pub mod playlist; + mod utils; + + #[allow(dead_code)] + pub mod db_reader; +} + +pub mod music_controller { + pub mod connections; + pub mod controller; + pub mod controller_handle; + pub mod library_command; + pub mod player_command; + pub mod player_monitor; + pub mod queue; + pub mod queue_command; +} + +pub mod config; diff --git a/dmp-core/src/music_controller/connections.rs b/dmp-core/src/music_controller/connections.rs index 26bc120..52afdc4 100644 --- a/dmp-core/src/music_controller/connections.rs +++ b/dmp-core/src/music_controller/connections.rs @@ -1,4 +1,11 @@ -use std::{sync::{atomic::{AtomicBool, Ordering}, Arc}, thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}}; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread::sleep, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; use chrono::TimeDelta; use crossbeam::{scope, select}; @@ -8,7 +15,10 @@ use listenbrainz::ListenBrainz; use parking_lot::RwLock; use prismriver::State as PrismState; -use crate::{config::Config, music_storage::library::{Song, Tag}}; +use crate::{ + config::Config, + music_storage::library::{Song, Tag}, +}; use super::controller::Controller; @@ -16,7 +26,7 @@ use super::controller::Controller; pub(super) enum ConnectionsNotification { Playback { position: Option, - duration: Option + duration: Option, }, StateChange(PrismState), SongChange(Song), @@ -30,10 +40,9 @@ pub struct ConnectionsInput { pub(super) struct ControllerConnections { pub notifications_tx: Receiver, - pub inner: ConnectionsInput + pub inner: ConnectionsInput, } - static DC_ACTIVE: AtomicBool = AtomicBool::new(false); static LB_ACTIVE: AtomicBool = AtomicBool::new(false); @@ -41,11 +50,11 @@ impl Controller { pub(super) fn handle_connections( config: Arc>, ControllerConnections { - notifications_tx, - inner: ConnectionsInput { - discord_rpc_client_id - }, - }: ControllerConnections + notifications_tx, + inner: ConnectionsInput { + discord_rpc_client_id, + }, + }: ControllerConnections, ) { let (dc_state_rx, dc_state_tx) = bounded::(1); let (dc_song_rx, dc_song_tx) = bounded::(1); @@ -53,49 +62,73 @@ impl Controller { let (lb_eos_rx, lb_eos_tx) = bounded::<()>(1); scope(|s| { - s.builder().name("Notifications Sorter".to_string()).spawn(|_| { - use ConnectionsNotification::*; - while true { - match notifications_tx.recv().unwrap() { - Playback { .. } => {} - StateChange(state) => { - if DC_ACTIVE.load(Ordering::Relaxed) { dc_state_rx.send(state.clone()).unwrap(); } - } - SongChange(song) => { - if DC_ACTIVE.load(Ordering::Relaxed) { dc_song_rx.send(song.clone()).unwrap(); } - if LB_ACTIVE.load(Ordering::Relaxed) { lb_song_rx.send(song).unwrap(); } - } - EOS => { - if LB_ACTIVE.load(Ordering::Relaxed) { lb_eos_rx.send(()).unwrap(); } + s.builder() + .name("Notifications Sorter".to_string()) + .spawn(|_| { + use ConnectionsNotification::*; + while true { + match notifications_tx.recv().unwrap() { + Playback { .. } => {} + StateChange(state) => { + if DC_ACTIVE.load(Ordering::Relaxed) { + dc_state_rx.send(state.clone()).unwrap(); + } + } + SongChange(song) => { + if DC_ACTIVE.load(Ordering::Relaxed) { + dc_song_rx.send(song.clone()).unwrap(); + } + if LB_ACTIVE.load(Ordering::Relaxed) { + lb_song_rx.send(song).unwrap(); + } + } + EOS => { + if LB_ACTIVE.load(Ordering::Relaxed) { + lb_eos_rx.send(()).unwrap(); + } + } } } - } - }).unwrap(); + }) + .unwrap(); if let Some(client_id) = discord_rpc_client_id { - s.builder().name("Discord RPC Handler".to_string()).spawn(move |_| { - Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx); - }).unwrap(); + s.builder() + .name("Discord RPC Handler".to_string()) + .spawn(move |_| { + Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx); + }) + .unwrap(); }; if let Some(token) = config.read().connections.listenbrainz_token.clone() { - s.builder().name("ListenBrainz Handler".to_string()).spawn(move |_| { - Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx); - }).unwrap(); + s.builder() + .name("ListenBrainz Handler".to_string()) + .spawn(move |_| { + Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx); + }) + .unwrap(); } - }).unwrap(); + }) + .unwrap(); } fn discord_rpc(client_id: u64, song_tx: Receiver, state_tx: Receiver) { // TODO: Handle seeking position change and pause - let mut client = discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None); + let mut client = + discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None); client.start(); - while !Client::is_ready() { sleep(Duration::from_millis(100)); } + while !Client::is_ready() { + sleep(Duration::from_millis(100)); + } println!("discord connected"); let mut state = "Started".to_string(); let mut song: Option = None; - let mut now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?").as_secs(); + let mut now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards?") + .as_secs(); DC_ACTIVE.store(true, Ordering::Relaxed); while true { @@ -121,38 +154,47 @@ impl Controller { default(Duration::from_millis(99)) => () } - client.set_activity(|activity| { - let a = activity.state( - song.as_ref().map_or(String::new(), |s| format!( - "{}{}{}", - s.get_tag(&Tag::Artist).map_or(String::new(), |album| album.clone()), - if s.get_tag(&Tag::Album).is_some() && s.get_tag(&Tag::Artist).is_some() { " - " } else { "" }, - s.get_tag(&Tag::Album).map_or(String::new(), |album| album.clone()) - ) - ) - )._type(discord_presence::models::ActivityType::Listening) - .details( - if let Some(song) = song { - song.get_tag(&Tag::Title).map_or(String::from("Unknown Title"), |title| title.clone()) - } else { - String::new() - } - ); - if let Some(s) = song { - if state.as_str() == "Playing" { - a.timestamps(|timestamps| { - timestamps.start(now) - .end(now + s.duration.as_secs()) - }) + client + .set_activity(|activity| { + let a = activity + .state(song.as_ref().map_or(String::new(), |s| { + format!( + "{}{}{}", + s.get_tag(&Tag::Artist) + .map_or(String::new(), |album| album.clone()), + if s.get_tag(&Tag::Album).is_some() + && s.get_tag(&Tag::Artist).is_some() + { + " - " + } else { + "" + }, + s.get_tag(&Tag::Album) + .map_or(String::new(), |album| album.clone()) + ) + })) + ._type(discord_presence::models::ActivityType::Listening) + .details(if let Some(song) = song { + song.get_tag(&Tag::Title) + .map_or(String::from("Unknown Title"), |title| title.clone()) + } else { + String::new() + }); + if let Some(s) = song { + if state.as_str() == "Playing" { + a.timestamps(|timestamps| { + timestamps.start(now).end(now + s.duration.as_secs()) + }) + } else { + a + } } else { a } - } else { - a - }.assets(|a| { - a.large_text(state.clone()) - }).instance(true) - }).unwrap(); + .assets(|a| a.large_text(state.clone())) + .instance(true) + }) + .unwrap(); } DC_ACTIVE.store(false, Ordering::Relaxed); } @@ -225,13 +267,17 @@ mod test_super { let (song_rx, song_tx) = unbounded(); let (eos_rx, eos_tx) = unbounded(); - let (config, lib ) = read_config_lib(); + let (config, lib) = read_config_lib(); song_rx.send(lib.library[0].clone()).unwrap(); spawn(|| { - Controller::listenbrainz_scrobble(config.connections.listenbrainz_token.unwrap().as_str(), song_tx, eos_tx); + Controller::listenbrainz_scrobble( + config.connections.listenbrainz_token.unwrap().as_str(), + song_tx, + eos_tx, + ); }); sleep(Duration::from_secs(10)); eos_rx.send(()).unwrap(); sleep(Duration::from_secs(10)); } -} \ No newline at end of file +} diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index bdc9151..3259787 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -1,357 +1,362 @@ -//! The [Controller] is the input and output for the entire -//! player. It manages queues, playback, library access, and -//! other functions - -use chrono::TimeDelta; -use crossbeam::atomic::AtomicCell; -use crossbeam_channel::{Receiver, Sender}; -use kushi::Queue; -use kushi::{QueueError, QueueItem}; -use parking_lot::RwLock; -use prismriver::{Error as PrismError, Prismriver}; -use serde::{Deserialize, Serialize}; -use serde_json::to_string_pretty; -use std::error::Error; -use std::fs::OpenOptions; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use thiserror::Error; -use uuid::Uuid; - -use crate::config::ConfigError; -use crate::music_storage::library::Song; -use crate::music_storage::playlist::{ExternalPlaylist, Playlist}; -use crate::{config::Config, music_storage::library::MusicLibrary}; - -use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections}; -use super::controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}; -use super::queue::{QueueAlbum, QueueSong}; - -pub struct Controller(); - -type QueueItem_ = QueueItem; - -#[derive(Error, Debug)] -pub enum ControllerError { - #[error("{0:?}")] - QueueError(#[from] QueueError), - #[error("{0:?}")] - PlayerError(#[from] prismriver::Error), - #[error("{0:?}")] - ConfigError(#[from] ConfigError), -} - -// TODO: move this to a different location to be used elsewhere -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] -#[non_exhaustive] -pub enum PlayerLocation { - Test, - Library, - Playlist(Uuid), - File, - Custom, -} - -#[derive(Debug, Clone)] -pub struct MailMan { - tx: async_channel::Sender, - rx: async_channel::Receiver, -} - -impl MailMan { - pub fn double() -> (MailMan, MailMan) { - let (tx, rx) = async_channel::unbounded::(); - let (tx1, rx1) = async_channel::unbounded::(); - - (MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx }) - } - - pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError> { - self.tx.send(mail).await - } - - pub async fn recv(&self) -> Result { - self.rx.recv().await - } -} - -#[derive(Debug, PartialEq, PartialOrd, Clone)] -pub enum PlayerCommand { - NextSong, - PrevSong, - Pause, - Play, - Stop, - Seek(i64), - Enqueue(usize), - SetVolume(f32), - PlayNow(Uuid, PlayerLocation), -} - -#[derive(Debug, PartialEq, Clone)] -pub enum PlayerResponse { - Empty(Result<(), PlayerError>), - NowPlaying(Result) -} - -#[derive(Error, Debug, PartialEq, Clone)] -pub enum PlayerError { - #[error("{0}")] - QueueError(#[from] QueueError), - #[error("{0}")] - Prismriver(#[from] PrismError), -} - -#[derive(Debug, PartialEq, PartialOrd, Clone)] -pub enum LibraryCommand { - Song(Uuid), - AllSongs, - GetLibrary, - ExternalPlaylist(Uuid), - Playlist(Uuid), - ImportM3UPlayList(PathBuf), - Save, - Playlists, -} - -#[derive(Debug, Clone)] -pub enum LibraryResponse { - Ok, - Song(Song, usize), - AllSongs(Vec), - Library(MusicLibrary), - ExternalPlaylist(ExternalPlaylist), - Playlist(Playlist), - ImportM3UPlayList(Uuid, String), - Playlists(Vec<(Uuid, String)>), -} - -#[derive(Debug, PartialEq, Clone)] -pub enum QueueCommand { - Append(QueueItem_, bool), - Next, - Prev, - GetIndex(usize), - NowPlaying, - Get, - Clear, - Remove(usize), -} - -#[derive(Debug, PartialEq, Clone)] -pub enum QueueResponse { - Empty(Result<(), QueueError>), - Item(Result), - GetAll(Vec), -} - - -pub struct ControllerInput { - player_mail: ( - async_channel::Sender, - async_channel::Receiver - ), - lib_mail: ( - async_channel::Sender, - async_channel::Receiver - ), - queue_mail: ( - async_channel::Sender, - async_channel::Receiver - ), - library: MusicLibrary, - config: Arc>, - playback_info: Arc>, - notify_next_song: Sender, - connections: Option -} - -pub struct ControllerHandle { - pub(super) lib_mail_rx: async_channel::Sender, - pub(super) player_mail_rx: async_channel::Sender, - pub(super) queue_mail_rx: async_channel::Sender, -} - -impl ControllerHandle { - pub fn new(library: MusicLibrary, config: Arc>, connections: Option) -> (Self, ControllerInput, Arc>, Receiver) { - let (lib_mail_rx, lib_mail_tx) = async_channel::unbounded(); - let (player_mail_rx, player_mail_tx) = async_channel::unbounded(); - let (queue_mail_rx, queue_mail_tx)= async_channel::unbounded(); - let playback_info = Arc::new(AtomicCell::new(PlaybackInfo::default())); - let notify_next_song = crossbeam::channel::unbounded::(); - ( - ControllerHandle { - lib_mail_rx: lib_mail_rx.clone(), - player_mail_rx: player_mail_rx.clone(), - queue_mail_rx: queue_mail_rx.clone() - }, - ControllerInput { - player_mail: (player_mail_rx, player_mail_tx), - lib_mail: (lib_mail_rx, lib_mail_tx), - queue_mail: (queue_mail_rx, queue_mail_tx), - library, - config, - playback_info: Arc::clone(&playback_info), - notify_next_song: notify_next_song.0, - connections, - }, - playback_info, - notify_next_song.1 - ) - } -} - -#[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub struct ControllerState { - pub(super) path: PathBuf, - pub(super) volume: f32, - pub(super) now_playing: Uuid, -} - -impl ControllerState { - pub(super) fn new(path: PathBuf) -> Self { - ControllerState { - path, - volume: 0.35, - ..Default::default() - } - } - - pub(super) 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(()) - } - - pub(super) 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 Controller { - pub async fn start( - ControllerInput { - player_mail, - lib_mail, - queue_mail, - mut library, - config, - playback_info, - notify_next_song, - connections, - }: ControllerInput - ) -> Result<(), Box> { - let queue: Queue = Queue { - items: Vec::new(), - played: Vec::new(), - loop_: false, - shuffle: None, - }; - - let state = { - let path = &config.read().state_path; - if let Ok(state) = ControllerState::read_file(path) { - state - } else { - ControllerState::new(path.clone()) - } - }; - - std::thread::scope(|scope| { - let player = Prismriver::new(); - let player_state = player.state.clone(); - let player_timing = player.get_timing_recv(); - let finished_tx = player.get_finished_recv(); - let (notifications_rx, notifications_tx) = crossbeam_channel::unbounded::(); - - let a = scope.spawn({ - let queue_mail = queue_mail.clone(); - let _notifications_rx = notifications_rx.clone(); - let _config = config.clone(); - move || { - futures::executor::block_on(async { - moro::async_scope!(|scope| { - println!("async scope created"); - - let _lib_mail = lib_mail.0.clone(); - let _queue_mail = queue_mail.0.clone(); - scope - .spawn(async move { - Controller::player_command_loop( - player, - player_mail.1, - _queue_mail, - _lib_mail, - _notifications_rx, - state, - ) - .await - .unwrap(); - }); - scope - .spawn(async { - Controller::library_loop( - lib_mail.1, - &mut library, - _config, - ) - .await - .unwrap(); - }); - }) - .await; - }) - } - }); - - let b = scope.spawn(|| { - futures::executor::block_on(async { - Controller::queue_loop(queue, queue_mail.1).await; - }) - }); - - let c = scope.spawn(|| { - Controller::player_monitor_loop( - player_state, - player_timing, - finished_tx, - player_mail.0, - notify_next_song, - notifications_rx, - playback_info, - ).unwrap(); - }); - - if let Some(inner) = connections { - dbg!(&inner); - let d = scope.spawn(|| { - Controller::handle_connections( - config, - ControllerConnections { - notifications_tx, - inner, - }); - }); - } - a.join().unwrap(); - b.join().unwrap(); - c.join().unwrap(); - }); - - Ok(()) - } -} - -#[derive(Debug, Default, Serialize, Clone)] -pub struct PlaybackInfo { - pub position: Option, - pub duration: Option, -} +//! The [Controller] is the input and output for the entire +//! player. It manages queues, playback, library access, and +//! other functions + +use chrono::TimeDelta; +use crossbeam::atomic::AtomicCell; +use crossbeam_channel::{Receiver, Sender}; +use kushi::Queue; +use kushi::{QueueError, QueueItem}; +use parking_lot::RwLock; +use prismriver::{Error as PrismError, Prismriver}; +use serde::{Deserialize, Serialize}; +use serde_json::to_string_pretty; +use std::error::Error; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use thiserror::Error; +use uuid::Uuid; + +use crate::config::ConfigError; +use crate::music_storage::library::Song; +use crate::music_storage::playlist::{ExternalPlaylist, Playlist}; +use crate::{config::Config, music_storage::library::MusicLibrary}; + +use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections}; +use super::controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}; +use super::queue::{QueueAlbum, QueueSong}; + +pub struct Controller(); + +type QueueItem_ = QueueItem; + +#[derive(Error, Debug)] +pub enum ControllerError { + #[error("{0:?}")] + QueueError(#[from] QueueError), + #[error("{0:?}")] + PlayerError(#[from] prismriver::Error), + #[error("{0:?}")] + ConfigError(#[from] ConfigError), +} + +// TODO: move this to a different location to be used elsewhere +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] +#[non_exhaustive] +pub enum PlayerLocation { + Test, + Library, + Playlist(Uuid), + File, + Custom, +} + +#[derive(Debug, Clone)] +pub struct MailMan { + tx: async_channel::Sender, + rx: async_channel::Receiver, +} + +impl MailMan { + pub fn double() -> (MailMan, MailMan) { + let (tx, rx) = async_channel::unbounded::(); + let (tx1, rx1) = async_channel::unbounded::(); + + (MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx }) + } + + pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError> { + self.tx.send(mail).await + } + + pub async fn recv(&self) -> Result { + self.rx.recv().await + } +} + +#[derive(Debug, PartialEq, PartialOrd, Clone)] +pub enum PlayerCommand { + NextSong, + PrevSong, + Pause, + Play, + Stop, + Seek(i64), + Enqueue(usize), + SetVolume(f32), + PlayNow(Uuid, PlayerLocation), +} + +#[derive(Debug, PartialEq, Clone)] +pub enum PlayerResponse { + Empty(Result<(), PlayerError>), + NowPlaying(Result), +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum PlayerError { + #[error("{0}")] + QueueError(#[from] QueueError), + #[error("{0}")] + Prismriver(#[from] PrismError), +} + +#[derive(Debug, PartialEq, PartialOrd, Clone)] +pub enum LibraryCommand { + Song(Uuid), + AllSongs, + GetLibrary, + ExternalPlaylist(Uuid), + Playlist(Uuid), + ImportM3UPlayList(PathBuf), + Save, + Playlists, +} + +#[derive(Debug, Clone)] +pub enum LibraryResponse { + Ok, + Song(Song, usize), + AllSongs(Vec), + Library(MusicLibrary), + ExternalPlaylist(ExternalPlaylist), + Playlist(Playlist), + ImportM3UPlayList(Uuid, String), + Playlists(Vec<(Uuid, String)>), +} + +#[derive(Debug, PartialEq, Clone)] +pub enum QueueCommand { + Append(QueueItem_, bool), + Next, + Prev, + GetIndex(usize), + NowPlaying, + Get, + Clear, + Remove(usize), +} + +#[derive(Debug, PartialEq, Clone)] +pub enum QueueResponse { + Empty(Result<(), QueueError>), + Item(Result), + GetAll(Vec), +} + +pub struct ControllerInput { + player_mail: ( + async_channel::Sender, + async_channel::Receiver, + ), + lib_mail: ( + async_channel::Sender, + async_channel::Receiver, + ), + queue_mail: ( + async_channel::Sender, + async_channel::Receiver, + ), + library: MusicLibrary, + config: Arc>, + playback_info: Arc>, + notify_next_song: Sender, + connections: Option, +} + +pub struct ControllerHandle { + pub(super) lib_mail_rx: async_channel::Sender, + pub(super) player_mail_rx: async_channel::Sender, + pub(super) queue_mail_rx: async_channel::Sender, +} + +impl ControllerHandle { + pub fn new( + library: MusicLibrary, + config: Arc>, + connections: Option, + ) -> ( + Self, + ControllerInput, + Arc>, + Receiver, + ) { + let (lib_mail_rx, lib_mail_tx) = async_channel::unbounded(); + let (player_mail_rx, player_mail_tx) = async_channel::unbounded(); + let (queue_mail_rx, queue_mail_tx) = async_channel::unbounded(); + let playback_info = Arc::new(AtomicCell::new(PlaybackInfo::default())); + let notify_next_song = crossbeam::channel::unbounded::(); + ( + ControllerHandle { + lib_mail_rx: lib_mail_rx.clone(), + player_mail_rx: player_mail_rx.clone(), + queue_mail_rx: queue_mail_rx.clone(), + }, + ControllerInput { + player_mail: (player_mail_rx, player_mail_tx), + lib_mail: (lib_mail_rx, lib_mail_tx), + queue_mail: (queue_mail_rx, queue_mail_tx), + library, + config, + playback_info: Arc::clone(&playback_info), + notify_next_song: notify_next_song.0, + connections, + }, + playback_info, + notify_next_song.1, + ) + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct ControllerState { + pub(super) path: PathBuf, + pub(super) volume: f32, + pub(super) now_playing: Uuid, +} + +impl ControllerState { + pub(super) fn new(path: PathBuf) -> Self { + ControllerState { + path, + volume: 0.35, + ..Default::default() + } + } + + pub(super) 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(()) + } + + pub(super) 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 Controller { + pub async fn start( + ControllerInput { + player_mail, + lib_mail, + queue_mail, + mut library, + config, + playback_info, + notify_next_song, + connections, + }: ControllerInput, + ) -> Result<(), Box> { + let queue: Queue = Queue { + items: Vec::new(), + played: Vec::new(), + loop_: false, + shuffle: None, + }; + + let state = { + let path = &config.read().state_path; + if let Ok(state) = ControllerState::read_file(path) { + state + } else { + ControllerState::new(path.clone()) + } + }; + + std::thread::scope(|scope| { + let player = Prismriver::new(); + let player_state = player.state.clone(); + let player_timing = player.get_timing_recv(); + let finished_tx = player.get_finished_recv(); + let (notifications_rx, notifications_tx) = + crossbeam_channel::unbounded::(); + + let a = scope.spawn({ + let queue_mail = queue_mail.clone(); + let _notifications_rx = notifications_rx.clone(); + let _config = config.clone(); + move || { + futures::executor::block_on(async { + moro::async_scope!(|scope| { + println!("async scope created"); + + let _lib_mail = lib_mail.0.clone(); + let _queue_mail = queue_mail.0.clone(); + scope.spawn(async move { + Controller::player_command_loop( + player, + player_mail.1, + _queue_mail, + _lib_mail, + _notifications_rx, + state, + ) + .await + .unwrap(); + }); + scope.spawn(async { + Controller::library_loop(lib_mail.1, &mut library, _config) + .await + .unwrap(); + }); + }) + .await; + }) + } + }); + + let b = scope.spawn(|| { + futures::executor::block_on(async { + Controller::queue_loop(queue, queue_mail.1).await; + }) + }); + + let c = scope.spawn(|| { + Controller::player_monitor_loop( + player_state, + player_timing, + finished_tx, + player_mail.0, + notify_next_song, + notifications_rx, + playback_info, + ) + .unwrap(); + }); + + if let Some(inner) = connections { + dbg!(&inner); + let d = scope.spawn(|| { + Controller::handle_connections( + config, + ControllerConnections { + notifications_tx, + inner, + }, + ); + }); + } + a.join().unwrap(); + b.join().unwrap(); + c.join().unwrap(); + }); + + Ok(()) + } +} + +#[derive(Debug, Default, Serialize, Clone)] +pub struct PlaybackInfo { + pub position: Option, + pub duration: Option, +} diff --git a/dmp-core/src/music_controller/controller_handle.rs b/dmp-core/src/music_controller/controller_handle.rs index ad3796a..5c518aa 100644 --- a/dmp-core/src/music_controller/controller_handle.rs +++ b/dmp-core/src/music_controller/controller_handle.rs @@ -6,7 +6,13 @@ use uuid::Uuid; use crate::music_storage::{library::Song, playlist::ExternalPlaylist}; -use super::{controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerCommand, PlayerError, PlayerLocation, PlayerResponse, QueueCommand, QueueResponse}, queue::{QueueAlbum, QueueSong}}; +use super::{ + controller::{ + ControllerHandle, LibraryCommand, LibraryResponse, PlayerCommand, PlayerError, + PlayerLocation, PlayerResponse, QueueCommand, QueueResponse, + }, + queue::{QueueAlbum, QueueSong}, +}; impl ControllerHandle { // The Library Section @@ -59,12 +65,17 @@ impl ControllerHandle { pub async fn playlist_import_m3u(&self, path: PathBuf) -> Result<(Uuid, String), ()> { let (command, tx) = LibraryCommandInput::command(LibraryCommand::ImportM3UPlayList(path)); self.lib_mail_rx.send(command).await.unwrap(); - let LibraryResponse::ImportM3UPlayList(uuid, name) = tx.recv().await.unwrap() else { unreachable!("It has been reached") }; + let LibraryResponse::ImportM3UPlayList(uuid, name) = tx.recv().await.unwrap() else { + unreachable!("It has been reached") + }; Ok((uuid, name)) } // The Queue Section - pub async fn queue_append(&self, item: QueueItem) -> Result<(), QueueError> { + pub async fn queue_append( + &self, + item: QueueItem, + ) -> Result<(), QueueError> { let (command, tx) = QueueCommandInput::command(QueueCommand::Append(item, true)); self.queue_mail_rx.send(command).await.unwrap(); let QueueResponse::Empty(res) = tx.recv().await.unwrap() else { @@ -73,7 +84,10 @@ impl ControllerHandle { res } - pub async fn queue_remove(&self, index: usize) -> Result, QueueError> { + pub async fn queue_remove( + &self, + index: usize, + ) -> Result, QueueError> { let (command, tx) = QueueCommandInput::command(QueueCommand::Remove(index)); self.queue_mail_rx.send(command).await.unwrap(); let QueueResponse::Item(res) = tx.recv().await.unwrap() else { @@ -166,7 +180,7 @@ impl ControllerHandle { pub(super) struct LibraryCommandInput { pub res_rx: Sender, - pub command: LibraryCommand + pub command: LibraryCommand, } impl LibraryCommandInput { @@ -175,16 +189,16 @@ impl LibraryCommandInput { ( Self { res_rx: rx, - command + command, }, - tx + tx, ) } } pub(super) struct QueueCommandInput { pub res_rx: Sender, - pub command: QueueCommand + pub command: QueueCommand, } impl QueueCommandInput { @@ -193,16 +207,16 @@ impl QueueCommandInput { ( Self { res_rx: rx, - command + command, }, - tx + tx, ) } } pub(super) struct PlayerCommandInput { pub res_rx: Sender, - pub command: PlayerCommand + pub command: PlayerCommand, } impl PlayerCommandInput { @@ -211,9 +225,9 @@ impl PlayerCommandInput { ( Self { res_rx: rx, - command + command, }, - tx + tx, ) } } diff --git a/dmp-core/src/music_controller/library_command.rs b/dmp-core/src/music_controller/library_command.rs index 8b30ea8..22031f4 100644 --- a/dmp-core/src/music_controller/library_command.rs +++ b/dmp-core/src/music_controller/library_command.rs @@ -3,9 +3,18 @@ use std::sync::Arc; use parking_lot::RwLock; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; -use crate::{config::Config, music_storage::{library::MusicLibrary, playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}}}; +use crate::{ + config::Config, + music_storage::{ + library::MusicLibrary, + playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}, + }, +}; -use super::{controller::{Controller, LibraryCommand, LibraryResponse}, controller_handle::LibraryCommandInput}; +use super::{ + controller::{Controller, LibraryCommand, LibraryResponse}, + controller_handle::LibraryCommandInput, +}; impl Controller { pub(super) async fn library_loop( @@ -18,36 +27,73 @@ impl Controller { match command { LibraryCommand::Song(uuid) => { let (song, i) = library.query_uuid(&uuid).unwrap(); - res_rx.send(LibraryResponse::Song(song.clone(), i)).await.unwrap(); + res_rx + .send(LibraryResponse::Song(song.clone(), i)) + .await + .unwrap(); } LibraryCommand::AllSongs => { - res_rx.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap(); - }, + res_rx + .send(LibraryResponse::AllSongs(library.library.clone())) + .await + .unwrap(); + } LibraryCommand::ExternalPlaylist(uuid) => { let playlist = library.query_playlist_uuid(&uuid).unwrap(); - res_rx.send(LibraryResponse::ExternalPlaylist(ExternalPlaylist::from_playlist(playlist, library))).await.unwrap(); + res_rx + .send(LibraryResponse::ExternalPlaylist( + ExternalPlaylist::from_playlist(playlist, library), + )) + .await + .unwrap(); } LibraryCommand::ImportM3UPlayList(path) => { let playlist = Playlist::from_m3u(path, library).unwrap(); let uuid = playlist.uuid; let name = playlist.title.clone(); - library.playlists.items.push(PlaylistFolderItem::List(playlist)); + library + .playlists + .items + .push(PlaylistFolderItem::List(playlist)); - res_rx.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap(); + res_rx + .send(LibraryResponse::ImportM3UPlayList(uuid, name)) + .await + .unwrap(); } LibraryCommand::Save => { - library.save(config.read().libraries.get_library(&library.uuid).unwrap().path.clone()).unwrap(); + library + .save( + config + .read() + .libraries + .get_library(&library.uuid) + .unwrap() + .path + .clone(), + ) + .unwrap(); res_rx.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); + library + .playlists + .lists_recursive() + .into_par_iter() + .map(|list| (list.uuid, list.title.clone())) + .collect_into_vec(&mut lists); - res_rx.send(LibraryResponse::Playlists(lists)).await.unwrap(); + res_rx + .send(LibraryResponse::Playlists(lists)) + .await + .unwrap(); + } + _ => { + todo!() } - _ => { todo!() } } } Ok(()) } -} \ No newline at end of file +} diff --git a/dmp-core/src/music_controller/player_command.rs b/dmp-core/src/music_controller/player_command.rs index 3ab465a..0871088 100644 --- a/dmp-core/src/music_controller/player_command.rs +++ b/dmp-core/src/music_controller/player_command.rs @@ -3,9 +3,19 @@ use crossbeam_channel::Sender; use kushi::{QueueItem, QueueItemType}; use prismriver::{Prismriver, Volume}; -use crate::music_controller::{controller::{LibraryCommand, LibraryResponse}, queue::QueueSong}; +use crate::music_controller::{ + controller::{LibraryCommand, LibraryResponse}, + queue::QueueSong, +}; -use super::{connections::ConnectionsNotification, controller::{Controller, ControllerState, PlayerCommand, PlayerLocation, PlayerResponse, QueueCommand, QueueResponse}, controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}}; +use super::{ + connections::ConnectionsNotification, + controller::{ + Controller, ControllerState, PlayerCommand, PlayerLocation, PlayerResponse, QueueCommand, + QueueResponse, + }, + controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}, +}; impl Controller { pub(super) async fn player_command_loop( @@ -19,7 +29,7 @@ impl Controller { player.set_volume(Volume::new(state.volume)); 'outer: while true { let _mail = player_mail.recv().await; - if let Ok(PlayerCommandInput {res_rx, command}) = _mail { + if let Ok(PlayerCommandInput { res_rx, command }) = _mail { match command { PlayerCommand::Play => { player.play(); @@ -38,7 +48,10 @@ impl Controller { PlayerCommand::Seek(time) => { let res = player.seek_to(TimeDelta::milliseconds(time)); - res_rx.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap(); + res_rx + .send(PlayerResponse::Empty(res.map_err(|e| e.into()))) + .await + .unwrap(); } PlayerCommand::SetVolume(volume) => { @@ -57,63 +70,79 @@ impl Controller { match tx.recv().await.unwrap() { QueueResponse::Item(Ok(item)) => { let uri = match &item.item { - QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, + QueueItemType::Single(song) => { + song.song.primary_uri().unwrap().0 + } _ => unimplemented!(), }; - let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap(); + let prism_uri = + prismriver::utils::path_to_uri(&uri.as_path().unwrap()) + .unwrap(); println!("Playing song at path: {:?}", prism_uri); // handle error here for unknown formats player.load_new(&prism_uri).unwrap(); player.play(); - let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; + let QueueItemType::Single(np_song) = item.item else { + panic!("This is temporary, handle queueItemTypes at some point") + }; - let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs); + let (command, tx) = + LibraryCommandInput::command(LibraryCommand::AllSongs); // Append next song in library lib_mail.send(command).await.unwrap(); - let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else { + let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() + else { continue; }; - let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(np_song.song.uuid)); + let (command, tx) = LibraryCommandInput::command( + LibraryCommand::Song(np_song.song.uuid), + ); lib_mail.send(command).await.unwrap(); let LibraryResponse::Song(_, i) = tx.recv().await.unwrap() else { unreachable!() }; if let Some(song) = songs.get(i + 49) { - let (command, tx) = QueueCommandInput::command( - QueueCommand::Append( - QueueItem::from_item_type( - QueueItemType::Single( - QueueSong { - song: song.clone(), - location: np_song.location - } - ) - ), - false - ) - ); - queue_mail.send(command).await - .unwrap(); - let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap() else { + let (command, tx) = + QueueCommandInput::command(QueueCommand::Append( + QueueItem::from_item_type(QueueItemType::Single( + QueueSong { + song: song.clone(), + location: np_song.location, + }, + )), + false, + )); + queue_mail.send(command).await.unwrap(); + let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap() + else { unreachable!() }; } else { println!("Library Empty"); } - res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap(); + res_rx + .send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))) + .await + .unwrap(); state.now_playing = np_song.song.uuid; _ = state.write_file(); - notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap(); - } QueueResponse::Item(Err(e)) => { - res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); + notify_connections_ + .send(ConnectionsNotification::SongChange(np_song.song)) + .unwrap(); } - _ => continue + QueueResponse::Item(Err(e)) => { + res_rx + .send(PlayerResponse::NowPlaying(Err(e.into()))) + .await + .unwrap(); + } + _ => continue, } } @@ -123,60 +152,87 @@ impl Controller { match tx.recv().await.unwrap() { QueueResponse::Item(Ok(item)) => { let uri = match &item.item { - QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, + QueueItemType::Single(song) => { + song.song.primary_uri().unwrap().0 + } _ => unimplemented!(), }; - let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap(); + let prism_uri = + prismriver::utils::path_to_uri(&uri.as_path().unwrap()) + .unwrap(); player.load_new(&prism_uri).unwrap(); player.play(); - let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; - res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap(); + let QueueItemType::Single(np_song) = item.item else { + panic!("This is temporary, handle queueItemTypes at some point") + }; + res_rx + .send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))) + .await + .unwrap(); state.now_playing = np_song.song.uuid; _ = state.write_file(); - notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap(); + notify_connections_ + .send(ConnectionsNotification::SongChange(np_song.song)) + .unwrap(); } QueueResponse::Item(Err(e)) => { - res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); + res_rx + .send(PlayerResponse::NowPlaying(Err(e.into()))) + .await + .unwrap(); } - _ => continue + _ => continue, } } PlayerCommand::Enqueue(index) => { - let (command, tx) = QueueCommandInput::command(QueueCommand::GetIndex(index)); - queue_mail - .send(command) - .await - .unwrap(); + let (command, tx) = + QueueCommandInput::command(QueueCommand::GetIndex(index)); + queue_mail.send(command).await.unwrap(); match tx.recv().await.unwrap() { QueueResponse::Item(Ok(item)) => { match item.item { QueueItemType::Single(np_song) => { - let prism_uri = prismriver::utils::path_to_uri(&np_song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap(); + let prism_uri = prismriver::utils::path_to_uri( + &np_song + .song + .primary_uri() + .unwrap() + .0 + .as_path() + .unwrap(), + ) + .unwrap(); player.load_new(&prism_uri).unwrap(); player.play(); state.now_playing = np_song.song.uuid; _ = state.write_file(); - notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap(); + notify_connections_ + .send(ConnectionsNotification::SongChange(np_song.song)) + .unwrap(); } _ => unimplemented!(), } res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); } QueueResponse::Item(Err(e)) => { - res_rx.send(PlayerResponse::Empty(Err(e.into()))).await.unwrap(); + res_rx + .send(PlayerResponse::Empty(Err(e.into()))) + .await + .unwrap(); } - _ => continue + _ => continue, } } PlayerCommand::PlayNow(uuid, location) => { // TODO: This assumes the uuid doesn't point to an album. we've been over this. - let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(uuid)); + let (command, tx) = + LibraryCommandInput::command(LibraryCommand::Song(uuid)); lib_mail.send(command).await.unwrap(); let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else { unreachable!() @@ -187,27 +243,40 @@ impl Controller { match tx.recv().await.unwrap() { QueueResponse::Empty(Ok(())) => (), QueueResponse::Empty(Err(e)) => { - res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); + res_rx + .send(PlayerResponse::NowPlaying(Err(e.into()))) + .await + .unwrap(); continue; } - _ => unreachable!() + _ => unreachable!(), } - let (command, tx) = QueueCommandInput::command( - QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: np_song.clone(), location })), true) - ); + let (command, tx) = QueueCommandInput::command(QueueCommand::Append( + QueueItem::from_item_type(QueueItemType::Single(QueueSong { + song: np_song.clone(), + location, + })), + true, + )); queue_mail.send(command).await.unwrap(); match tx.recv().await.unwrap() { QueueResponse::Empty(Ok(())) => (), QueueResponse::Empty(Err(e)) => { - res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); + res_rx + .send(PlayerResponse::NowPlaying(Err(e.into()))) + .await + .unwrap(); continue; } - _ => unreachable!() + _ => unreachable!(), } // TODO: Handle non Local URIs here, and whenever `load_new()` or `load_gapless()` is called - let prism_uri = prismriver::utils::path_to_uri(&np_song.primary_uri().unwrap().0.as_path().unwrap()).unwrap(); + let prism_uri = prismriver::utils::path_to_uri( + &np_song.primary_uri().unwrap().0.as_path().unwrap(), + ) + .unwrap(); player.load_new(&prism_uri).unwrap(); player.play(); @@ -217,39 +286,54 @@ impl Controller { let (songs, index) = match location { PlayerLocation::Library => { - let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs); + let (command, tx) = + LibraryCommandInput::command(LibraryCommand::AllSongs); lib_mail.send(command).await.unwrap(); - let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else { + let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() + else { unreachable!() }; (songs, index) } PlayerLocation::Playlist(uuid) => { - let (command, tx) = LibraryCommandInput::command(LibraryCommand::ExternalPlaylist(uuid)); + let (command, tx) = LibraryCommandInput::command( + LibraryCommand::ExternalPlaylist(uuid), + ); lib_mail.send(command).await.unwrap(); - let LibraryResponse::ExternalPlaylist(list) = tx.recv().await.unwrap() else { + let LibraryResponse::ExternalPlaylist(list) = + tx.recv().await.unwrap() + else { unreachable!() }; let index = list.get_index(np_song.uuid).unwrap(); (list.tracks, index) } - _ => todo!("Got Location other than Library or Playlist") + _ => todo!("Got Location other than Library or Playlist"), }; - - for i in index+1..(index+50) { + for i in index + 1..(index + 50) { if let Some(song) = songs.get(i) { - let (command, tx) = QueueCommandInput::command( - QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), false) - ); + let (command, tx) = + QueueCommandInput::command(QueueCommand::Append( + QueueItem::from_item_type(QueueItemType::Single( + QueueSong { + song: song.clone(), + location, + }, + )), + false, + )); queue_mail.send(command).await.unwrap(); match tx.recv().await.unwrap() { QueueResponse::Empty(Ok(())) => (), QueueResponse::Empty(Err(e)) => { - res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); + res_rx + .send(PlayerResponse::NowPlaying(Err(e.into()))) + .await + .unwrap(); continue 'outer; } - _ => unreachable!() + _ => unreachable!(), } } else { println!("End of Library / Playlist"); @@ -257,11 +341,16 @@ impl Controller { } } // ^ This be my solution for now ^ - res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.clone()))).await.unwrap(); + res_rx + .send(PlayerResponse::NowPlaying(Ok(np_song.clone()))) + .await + .unwrap(); state.now_playing = np_song.uuid; _ = state.write_file(); - notify_connections_.send(ConnectionsNotification::SongChange(np_song)).unwrap(); + notify_connections_ + .send(ConnectionsNotification::SongChange(np_song)) + .unwrap(); } } } else { @@ -270,4 +359,4 @@ impl Controller { } Ok(()) } -} \ No newline at end of file +} diff --git a/dmp-core/src/music_controller/player_monitor.rs b/dmp-core/src/music_controller/player_monitor.rs index 5923f08..4ad4b95 100644 --- a/dmp-core/src/music_controller/player_monitor.rs +++ b/dmp-core/src/music_controller/player_monitor.rs @@ -5,9 +5,16 @@ use crossbeam::atomic::AtomicCell; use crossbeam_channel::{Receiver, Sender}; use prismriver::State as PrismState; -use crate::{music_controller::controller::{PlayerCommand, PlayerResponse}, music_storage::library::Song}; +use crate::{ + music_controller::controller::{PlayerCommand, PlayerResponse}, + music_storage::library::Song, +}; -use super::{connections::ConnectionsNotification, controller::{Controller, PlaybackInfo}, controller_handle::PlayerCommandInput}; +use super::{ + connections::ConnectionsNotification, + controller::{Controller, PlaybackInfo}, + controller_handle::PlayerCommandInput, +}; impl Controller { pub(super) fn player_monitor_loop( @@ -17,7 +24,7 @@ impl Controller { player_mail: async_channel::Sender, notify_next_song: Sender, notify_connections_: Sender, - playback_info: Arc> + playback_info: Arc>, ) -> Result<(), ()> { std::thread::scope(|s| { // Thread for timing and metadata @@ -27,7 +34,12 @@ impl Controller { println!("playback monitor started"); while true { let (position, duration) = playback_time_tx.recv().unwrap(); - notify_connections.send(ConnectionsNotification::Playback { position: position.clone(), duration: duration.clone() }).unwrap(); + notify_connections + .send(ConnectionsNotification::Playback { + position: position.clone(), + duration: duration.clone(), + }) + .unwrap(); playback_info.store(PlaybackInfo { position, duration }); } } @@ -35,35 +47,43 @@ impl Controller { // Thread for End of Track let notify_connections = notify_connections_.clone(); - s.spawn(move || { futures::executor::block_on(async { - println!("EOS monitor started"); - while true { - let _ = finished_recv.recv(); - println!("End of song"); + s.spawn(move || { + futures::executor::block_on(async { + println!("EOS monitor started"); + while true { + let _ = finished_recv.recv(); + println!("End of song"); - let (command, tx) = PlayerCommandInput::command(PlayerCommand::NextSong); - player_mail.send(command).await.unwrap(); - let PlayerResponse::NowPlaying(res) = tx.recv().await.unwrap() else { - unreachable!() - }; - if let Ok(song) = res { - notify_next_song.send(song.clone()).unwrap(); - notify_connections.send(ConnectionsNotification::SongChange(song)).unwrap(); - notify_connections.send(ConnectionsNotification::EOS).unwrap(); + let (command, tx) = PlayerCommandInput::command(PlayerCommand::NextSong); + player_mail.send(command).await.unwrap(); + let PlayerResponse::NowPlaying(res) = tx.recv().await.unwrap() else { + unreachable!() + }; + if let Ok(song) = res { + notify_next_song.send(song.clone()).unwrap(); + notify_connections + .send(ConnectionsNotification::SongChange(song)) + .unwrap(); + notify_connections + .send(ConnectionsNotification::EOS) + .unwrap(); + } } - } - std::thread::sleep(Duration::from_millis(100)); - });}); + std::thread::sleep(Duration::from_millis(100)); + }); + }); let notify_connections = notify_connections_.clone(); s.spawn(move || { let mut state = PrismState::Stopped; while true { let _state = playback_state.read().unwrap().to_owned(); - if _state != state { + if _state != state { state = _state; println!("State Changed to {state:?}"); - notify_connections.send(ConnectionsNotification::StateChange(state.clone())).unwrap(); + notify_connections + .send(ConnectionsNotification::StateChange(state.clone())) + .unwrap(); } std::thread::sleep(Duration::from_millis(100)); } @@ -72,4 +92,4 @@ impl Controller { println!("Monitor Loops Ended"); Ok(()) } -} \ No newline at end of file +} diff --git a/dmp-core/src/music_controller/queue_command.rs b/dmp-core/src/music_controller/queue_command.rs index fcd6849..75b5e50 100644 --- a/dmp-core/src/music_controller/queue_command.rs +++ b/dmp-core/src/music_controller/queue_command.rs @@ -1,6 +1,10 @@ use kushi::{Queue, QueueError, QueueItemType}; -use super::{controller::{Controller, QueueCommand, QueueResponse}, controller_handle::QueueCommandInput, queue::{QueueAlbum, QueueSong}}; +use super::{ + controller::{Controller, QueueCommand, QueueResponse}, + controller_handle::QueueCommandInput, + queue::{QueueAlbum, QueueSong}, +}; impl Controller { pub(super) async fn queue_loop( @@ -15,47 +19,57 @@ impl Controller { QueueItemType::Single(song) => queue.add_item(song, by_human), _ => unimplemented!(), } - res_rx - .send(QueueResponse::Empty(Ok(()))) - .await - .unwrap(); - }, + res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap(); + } QueueCommand::Next => { - let next = queue.next().map_or( Err(QueueError::NoNext), |s| Ok(s.clone())); + let next = queue + .next() + .map_or(Err(QueueError::NoNext), |s| Ok(s.clone())); res_rx .send(QueueResponse::Item(next.clone())) .await .unwrap(); } QueueCommand::Prev => { - let prev = queue.prev().map_or( Err(QueueError::EmptyPlayed), |s| Ok(s.clone())); + let prev = queue + .prev() + .map_or(Err(QueueError::EmptyPlayed), |s| Ok(s.clone())); res_rx .send(QueueResponse::Item(prev.clone())) .await .unwrap(); } QueueCommand::GetIndex(index) => { - let item = queue.items.get(index).map_or( Err(QueueError::OutOfBounds { index, len: queue.items.len() }), |s| Ok(s.clone())); + let item = queue.items.get(index).map_or( + Err(QueueError::OutOfBounds { + index, + len: queue.items.len(), + }), + |s| Ok(s.clone()), + ); res_rx.send(QueueResponse::Item(item)).await.unwrap(); } QueueCommand::NowPlaying => { let item = queue.current().map(|t| t.clone()); - res_rx - .send(QueueResponse::Item(item)) - .await - .unwrap(); + res_rx.send(QueueResponse::Item(item)).await.unwrap(); } QueueCommand::Get => { - res_rx.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap(); + res_rx + .send(QueueResponse::GetAll(queue.items.clone())) + .await + .unwrap(); } QueueCommand::Clear => { queue.clear(); res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap(); } QueueCommand::Remove(index) => { - res_rx.send(QueueResponse::Item(queue.remove_item(index))).await.unwrap(); + res_rx + .send(QueueResponse::Item(queue.remove_item(index))) + .await + .unwrap(); } } } } -} \ No newline at end of file +} diff --git a/dmp-core/src/music_storage/db_reader/itunes/reader.rs b/dmp-core/src/music_storage/db_reader/itunes/reader.rs index 1941ec5..3c9b8e3 100644 --- a/dmp-core/src/music_storage/db_reader/itunes/reader.rs +++ b/dmp-core/src/music_storage/db_reader/itunes/reader.rs @@ -70,9 +70,7 @@ impl ExternalLibrary for ITunesLibrary { if song_tags.contains_key("Location") { count3 += 1; //check for skipped IDs - if &count3.to_string() - != song_tags.get_key_value("Track ID").unwrap().1 - { + if &count3.to_string() != song_tags.get_key_value("Track ID").unwrap().1 { count3 += 1; count4 += 1; } @@ -232,7 +230,8 @@ fn get_art(file: &Path) -> Result, lofty::error::LoftyError> { let mut album_art: Vec = Vec::new(); let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2); - let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed); + let normal_options = + lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed); let tagged_file: lofty::file::TaggedFile; let tag = match Probe::open(file)?.options(normal_options).read() { @@ -284,7 +283,9 @@ impl ITunesSong { Default::default() } - fn from_hashmap(map: &mut HashMap) -> Result { + fn from_hashmap( + map: &mut HashMap, + ) -> Result { let mut song = ITunesSong::new(); //get the path with the first bit chopped off let path_: String = map.get_key_value("Location").unwrap().1.clone(); @@ -351,7 +352,7 @@ mod tests { PathBuf::from("test-config/library2"), String::from("library2"), None, - None + None, ); config.libraries.libraries.push(config_lib.clone()); diff --git a/dmp-core/src/music_storage/library.rs b/dmp-core/src/music_storage/library.rs index 89f37d5..b7e1060 100644 --- a/dmp-core/src/music_storage/library.rs +++ b/dmp-core/src/music_storage/library.rs @@ -66,7 +66,6 @@ pub enum Tag { Field(String), } - impl Display for Tag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let path_str: String = match self { @@ -228,7 +227,8 @@ impl Song { /// Creates a [`Song`] from a music file pub fn from_file>(target_file: &P) -> Result> { - let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed); + let normal_options = + lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed); let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2); let tagged_file: lofty::file::TaggedFile; @@ -480,8 +480,12 @@ impl Song { match art { AlbumArt::Embedded(j) => { let file = lofty::read_from_path(self.primary_uri()?.0.path())?; - Ok(Some(file.tag(file.primary_tag_type()).unwrap().pictures()[*j].data().to_vec())) - }, + Ok(Some( + file.tag(file.primary_tag_type()).unwrap().pictures()[*j] + .data() + .to_vec(), + )) + } AlbumArt::External(ref path) => { let mut buf = vec![]; std::fs::File::open(path.path())?.read_to_end(&mut buf)?; @@ -491,7 +495,6 @@ impl Song { } else { Ok(None) } - } } @@ -1236,15 +1239,12 @@ impl MusicLibrary { #[cfg(test)] mod test { - use std::{path::PathBuf, time::Instant}; use crate::music_storage::library::Tag; + use std::{path::PathBuf, time::Instant}; use uuid::Uuid; - use crate::{ - config::Config, - music_storage::library::MusicLibrary, - }; + use crate::{config::Config, music_storage::library::MusicLibrary}; #[test] fn library_init() { @@ -1263,20 +1263,27 @@ mod test { let lib = MusicLibrary::init( PathBuf::from("/media/g2/Storage4/Media-Files/Music/Albums/library.dlib"), Uuid::new_v4(), - ).unwrap(); + ) + .unwrap(); let timer = Instant::now(); - let result = lib.query_tracks( - &String::from(""), - &vec![], - &vec![ - Tag::Field("location".to_string()), - Tag::Album, - Tag::Disk, - Tag::Track, - ], - ).unwrap(); - println!("{} songs in {}ms", result.len(), timer.elapsed().as_millis()); + let result = lib + .query_tracks( + &String::from(""), + &vec![], + &vec![ + Tag::Field("location".to_string()), + Tag::Album, + Tag::Disk, + Tag::Track, + ], + ) + .unwrap(); + println!( + "{} songs in {}ms", + result.len(), + timer.elapsed().as_millis() + ); /* for song in result { diff --git a/dmp-core/src/music_storage/playlist.rs b/dmp-core/src/music_storage/playlist.rs index a33d08b..ff6b5f7 100644 --- a/dmp-core/src/music_storage/playlist.rs +++ b/dmp-core/src/music_storage/playlist.rs @@ -45,8 +45,10 @@ impl PlaylistFolder { for item in &self.items { match item { PlaylistFolderItem::Folder(folder) => return folder.query_uuid(uuid), - PlaylistFolderItem::List(ref playlist) => if &playlist.uuid == uuid { - return Some(playlist); + PlaylistFolderItem::List(ref playlist) => { + if &playlist.uuid == uuid { + return Some(playlist); + } } } } @@ -225,17 +227,18 @@ impl Playlist { continue; }; - let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) { - song.uuid - } else { - let song_: Song = match Song::from_file(&song_path) { - Ok(s) => s, - Err(e) => panic!("{e}\npath: {}", song_path.display()) + let uuid = + if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) { + song.uuid + } else { + let song_: Song = match Song::from_file(&song_path) { + Ok(s) => s, + Err(e) => panic!("{e}\npath: {}", song_path.display()), + }; + let uuid = song_.uuid.to_owned(); + _ = lib.add_song(song_); // TODO: Add proper error handling with Library + uuid }; - let uuid = song_.uuid.to_owned(); - _ = lib.add_song(song_); // TODO: Add proper error handling with Library - uuid - }; uuids.push(uuid); } let mut playlist = Playlist::new(); @@ -348,7 +351,6 @@ impl Default for Playlist { } } - #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ExternalPlaylist { pub uuid: Uuid, @@ -361,11 +363,11 @@ 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() - }) - }).collect_vec(); + let tracks: Vec = playlist + .tracks + .iter() + .filter_map(|uuid| library.query_uuid(uuid).map(|res| res.0.clone())) + .collect_vec(); Self { uuid: playlist.uuid, @@ -373,7 +375,7 @@ impl ExternalPlaylist { tracks, sort_order: playlist.sort_order.clone(), play_count: playlist.play_count, - play_time: playlist.play_time + play_time: playlist.play_time, } } @@ -399,7 +401,6 @@ impl ExternalPlaylist { } } - #[cfg(test)] mod test_super { use super::*; @@ -412,18 +413,19 @@ mod test_super { let tracks = lib.library.iter().map(|track| track.uuid).collect(); playlist.set_tracks(tracks); - playlist.to_m3u( - Arc::new(RwLock::from(lib)), - ".\\test-config\\playlists\\playlist.m3u", - ).unwrap(); + playlist + .to_m3u( + Arc::new(RwLock::from(lib)), + ".\\test-config\\playlists\\playlist.m3u", + ) + .unwrap(); } #[test] fn m3u_to_list() { let (_, mut lib) = read_config_lib(); - let playlist = - Playlist::from_m3u(".\\test-config\\playlists\\playlist", &mut lib).unwrap(); + let playlist = Playlist::from_m3u(".\\test-config\\playlists\\playlist", &mut lib).unwrap(); _ = playlist.to_file(".\\test-config\\playlists\\playlist"); dbg!(&playlist, playlist.tracks.len()); diff --git a/kushi-queue/src/lib.rs b/kushi-queue/src/lib.rs index 0562540..8e4b929 100644 --- a/kushi-queue/src/lib.rs +++ b/kushi-queue/src/lib.rs @@ -11,7 +11,7 @@ pub enum QueueState { #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub struct QueueItem< - T: Debug + Clone + PartialEq, // T: The Singular Item Type + T: Debug + Clone + PartialEq, // T: The Singular Item Type U: Debug + PartialEq + Clone + IntoIterator, // U: an Iterator > { pub item: QueueItemType, @@ -22,17 +22,18 @@ pub struct QueueItem< #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum QueueItemType< - T: Debug + Clone + PartialEq, // T: The Singular Item Type + T: Debug + Clone + PartialEq, // T: The Singular Item Type U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items > { Single(T), - Multi(U) + Multi(U), } impl< - T: Debug + Clone + PartialEq, // T: The Singular Item Type - U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items -> QueueItemType { + T: Debug + Clone + PartialEq, // T: The Singular Item Type + U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items + > QueueItemType +{ pub fn from_single(item: T) -> Self { QueueItemType::Single(item) } @@ -42,12 +43,7 @@ impl< } } - -impl< - T: Debug + Clone + PartialEq, - U: Debug + PartialEq + Clone + IntoIterator, -> -QueueItem { +impl QueueItem { pub fn from_item_type(item: QueueItemType) -> Self { QueueItem { item, @@ -59,7 +55,7 @@ QueueItem { #[derive(Debug, Clone, Default)] pub struct Queue< - T: Debug + Clone + PartialEq, // T: The Singular Item Type + T: Debug + Clone + PartialEq, // T: The Singular Item Type U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items > { pub items: Vec>, @@ -69,10 +65,7 @@ pub struct Queue< } // TODO: HAndle the First QueueState[looping] and shuffle -impl< - T: Debug + Clone + PartialEq, - U: Debug + PartialEq + Clone + IntoIterator, -> Queue { +impl Queue { fn has_addhere(&self) -> bool { for item in &self.items { if item.state == QueueState::AddHere { @@ -98,7 +91,7 @@ impl< items: Vec::new(), played: Vec::new(), loop_, - shuffle + shuffle, } } @@ -130,7 +123,10 @@ impl< let empty = self.items.is_empty(); if !empty { - self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState; + self.items + .get_mut(i) + .expect("There should be an item at index {i}") + .state = QueueState::NoState; } if by_human { @@ -143,13 +139,11 @@ impl< }, ); } else { - self.items.push( - QueueItem { - item, - state: QueueState::NoState, - by_human, - } - ); + self.items.push(QueueItem { + item, + state: QueueState::NoState, + by_human, + }); } } @@ -196,7 +190,10 @@ impl< let empty = self.items.is_empty(); if !empty { - self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState; + self.items + .get_mut(i) + .expect("There should be an item at index {i}") + .state = QueueState::NoState; } let len = items.len(); @@ -211,13 +208,11 @@ impl< }, ); } else { - self.items.push( - QueueItem { - item, - state: QueueState::NoState, - by_human, // false - }, - ); + self.items.push(QueueItem { + item, + state: QueueState::NoState, + by_human, // false + }); } } self.items[i + len - if empty { 1 } else { 0 }].state = QueueState::AddHere; @@ -418,7 +413,8 @@ impl< if let QueueItemType::Multi(_) = self.items[0].item { unimplemented!(); // TODO: Handle Multi items here? - } if let QueueItemType::Multi(_) = item.item { + } + if let QueueItemType::Multi(_) = item.item { unimplemented!(); // TODO: Handle Multi items here? } @@ -447,7 +443,6 @@ impl< } } - use thiserror::Error; #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)] diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index e31df82..984e8e4 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,59 +1,82 @@ -use std::{fs::OpenOptions, io::Write}; - -use dmp_core::music_controller::{controller::{ControllerHandle, PlayerLocation}, queue::QueueSong}; -use kushi::QueueItem; -use tauri::{AppHandle, Emitter, State, Wry}; -use tempfile::TempDir; -use uuid::Uuid; - -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); - let (song, _) = ctrl_handle.lib_get_song(uuid).await; - match ctrl_handle.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location }))).await { - Ok(()) => (), - Err(e) => return Err(e.to_string()) - } - app.emit("queue_updated", ()).unwrap(); - Ok(()) -} - -#[tauri::command] -pub async fn play_now(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> { - let song = match ctrl_handle.play_now(uuid, location).await { - Ok(song) => song, - Err(e) => return Err(e.to_string()) - }; - app.emit("queue_updated", ()).unwrap(); - app.emit("now_playing_change", _Song::from(&song)).unwrap(); - app.emit("playing", ()).unwrap(); - Ok(()) -} - -#[tauri::command] -pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_dir: State<'_, TempDir>, uuid: Uuid) -> Result<(), String> { - match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) { - Ok(art) => { - let mut art = art.unwrap(); - let path = temp_dir.path().join(format!("CoverArt_{uuid}.{}", file_format::FileFormat::from_bytes(&art).extension())); - if !path.exists() { - // TODO: This can be optimised later - let mut file = OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .read(true) - .open(path.clone()) - .unwrap(); - file.write_all(&mut art).unwrap(); - } - opener::open(path).unwrap(); - } - Err(e) => return Err(e.to_string()) - }; - Ok(()) -} \ No newline at end of file +use std::{fs::OpenOptions, io::Write}; + +use dmp_core::music_controller::{ + controller::{ControllerHandle, PlayerLocation}, + queue::QueueSong, +}; +use kushi::QueueItem; +use tauri::{AppHandle, Emitter, State, Wry}; +use tempfile::TempDir; +use uuid::Uuid; + +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); + let (song, _) = ctrl_handle.lib_get_song(uuid).await; + match ctrl_handle + .queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single( + QueueSong { song, location }, + ))) + .await + { + Ok(()) => (), + Err(e) => return Err(e.to_string()), + } + app.emit("queue_updated", ()).unwrap(); + Ok(()) +} + +#[tauri::command] +pub async fn play_now( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, + uuid: Uuid, + location: PlayerLocation, +) -> Result<(), String> { + let song = match ctrl_handle.play_now(uuid, location).await { + Ok(song) => song, + Err(e) => return Err(e.to_string()), + }; + app.emit("queue_updated", ()).unwrap(); + app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("playing", ()).unwrap(); + Ok(()) +} + +#[tauri::command] +pub async fn display_album_art( + ctrl_handle: State<'_, ControllerHandle>, + temp_dir: State<'_, TempDir>, + uuid: Uuid, +) -> Result<(), String> { + match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) { + Ok(art) => { + let mut art = art.unwrap(); + let path = temp_dir.path().join(format!( + "CoverArt_{uuid}.{}", + file_format::FileFormat::from_bytes(&art).extension() + )); + if !path.exists() { + // TODO: This can be optimised later + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .read(true) + .open(path.clone()) + .unwrap(); + file.write_all(&mut art).unwrap(); + } + opener::open(path).unwrap(); + } + Err(e) => return Err(e.to_string()), + }; + Ok(()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f5bdd9d..03da998 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,277 +1,315 @@ -#![allow(while_true)] - -use std::{fs, path::PathBuf, sync::Arc, thread::{scope, spawn}, time::Duration}; - -use crossbeam::channel::{bounded, unbounded, Receiver, Sender}; -use dmp_core::{config::{Config, ConfigLibrary}, music_controller::{connections::ConnectionsInput, controller::{Controller, ControllerHandle, PlaybackInfo}}, music_storage::library::{MusicLibrary, Song}}; -use futures::channel::oneshot; -use parking_lot::RwLock; -use tauri::{http::Response, Emitter, Manager, State, Wry}; -use uuid::Uuid; -use wrappers::{_Song, stop}; - -use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists, remove_from_queue, seek}; -use commands::{add_song_to_queue, play_now, display_album_art}; - - -pub mod wrappers; -pub mod commands; - -const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png"); - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - let (rx, tx) = unbounded::(); - let (lib_rx, lib_tx) = unbounded::>(); - let (handle_rx, handle_tx) = unbounded::(); - let (playback_info_rx, playback_info_tx) = bounded(1); - let (next_rx, next_tx) = bounded(1); - - let _controller_thread = spawn(move || { - let mut config = { tx.recv().unwrap() } ; - let scan_path = { lib_tx.recv().unwrap() }; - let _temp_config = ConfigLibrary::default(); - let _lib = config.libraries.get_default().unwrap_or(&_temp_config); - - let save_path = if _lib.path == PathBuf::default() { - let p = scan_path.as_ref().unwrap().clone().canonicalize().unwrap(); - - if cfg!(windows) { - p.join("library_windows.dlib") - } else if cfg!(unix) { - p.join("library_unix.dlib") - } else { - p.join("library.dlib") - } - } else { - _lib.path.clone() - }; - println!("save_path: {}\nscan_path:{scan_path:?}", save_path.display()); - - let mut library = MusicLibrary::init( - save_path.clone(), - _lib.uuid - ).unwrap(); - - let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone()); - - if config.libraries.get_default().is_err() { - library.scan_folder(&scan_path).unwrap(); - config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()]), Some(library.uuid))); - } - if library.library.is_empty() { - println!("library is empty"); - } else { - config.write_file().unwrap(); - } - println!("scan_path: {}", scan_path.display()); - - library.save(save_path).unwrap(); - - let ( - handle, - input, - playback_info, - next_song_notification, - ) = ControllerHandle::new( - library, - std::sync::Arc::new(RwLock::new(config)), - Some(ConnectionsInput { - discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID").map(|id| id.parse::().unwrap()), - }), - ); - - handle_rx.send(handle).unwrap(); - playback_info_rx.send(playback_info).unwrap(); - next_rx.send(next_song_notification).unwrap(); - - let _controller = futures::executor::block_on(Controller::start(input)).unwrap(); - - - - }); - let app = tauri::Builder::default() - .plugin(tauri_plugin_shell::init()) - .invoke_handler(tauri::generate_handler![ - get_config, - create_new_library, - get_library, - play, - pause, - stop, - set_volume, - next, - prev, - get_song, - lib_already_created, - get_queue, - add_song_to_queue, - play_now, - import_playlist, - get_playlist, - get_playlists, - remove_from_queue, - display_album_art, - seek, - ]).manage(ConfigRx(rx)) - .manage(LibRx(lib_rx)) - .manage(HandleTx(handle_tx)) - .manage(tempfile::TempDir::new().unwrap()) - .setup(|app| { - let _app = app.handle().clone(); - let app = _app.clone(); - - std::thread::Builder::new() - .name("PlaybackInfo handler".to_string()) - .spawn(move || { - let mut _info: Arc> = Arc::new(RwLock::new(PlaybackInfo::default())); - let mut _now_playing: Arc>> = Arc::new(RwLock::new(None)); - - scope(|s| { - let info = _info.clone(); - s.spawn(|| { - let info = info; - let playback_info = playback_info_tx.recv().unwrap(); - while true { - let i = playback_info.take(); - app.emit("playback_info", i.clone()).unwrap(); - *info.write() = i; - std::thread::sleep(Duration::from_millis(100)); - } - }); - - let now_playing = _now_playing.clone(); - s.spawn(|| { - let now_playing = now_playing; - let next_song_notification = next_tx.recv().unwrap(); - while true { - let song = next_song_notification.recv().unwrap(); - app.emit("now_playing_change", _Song::from(&song)).unwrap(); - app.emit("queue_updated", ()).unwrap(); - app.emit("playing", ()).unwrap(); - _ = now_playing.write().insert(song); - } - }); - }); - }).unwrap(); - - Ok(()) - }) - .register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| { - let query = req - .clone() - .uri() - .clone() - .into_parts() - .path_and_query - .unwrap() - .query() - .unwrap() - .to_string(); - - let bytes = if query.as_str() == "default" { - Some(DEFAULT_IMAGE.to_vec()) - } else {futures::executor::block_on(async move { - let controller = ctx.app_handle().state::(); - let song = controller.lib_get_song(Uuid::parse_str(query.as_str()).unwrap()).await.0; - Some(song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())) - })}; - - res.respond( - Response::builder() - .header("Origin", "*") - .header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len()) - .status(200) - .body(bytes.unwrap_or_default()) - .unwrap() - ); - }) - .build(tauri::generate_context!()) - .expect("error while building tauri application"); - - app - .run(|_app_handle, event| match event { - tauri::RunEvent::ExitRequested { .. } => { - // api.prevent_exit(); - //panic!("does this kill the player?") - } - _ => {} - }); -} - -struct ConfigRx(Sender); - -struct LibRx(Sender>); -struct HandleTx(Receiver); - - -#[tauri::command] -async fn get_config(state: State<'_, ConfigRx>) -> Result { - if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") { - let path = dir.config_dir(); - fs::create_dir_all(path).or_else(|err| { - if err.kind() == std::io::ErrorKind::AlreadyExists { - Ok(()) - } else { - Err(err) - } - }).unwrap(); - - 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(); - c - }; - - state.inner().0.send(config.clone()).unwrap(); - - Ok(config) - } else { - panic!("No config dir for DMP") - } -} - -#[tauri::command] -async fn create_new_library( - app: tauri::AppHandle, - lib_rx: State<'_, LibRx>, - handle_tx: State<'_, HandleTx>, -) -> Result<(), String> { - let dir = rfd::AsyncFileDialog::new() - .set_title("Pick a library path") - .pick_folder() - .await - .unwrap(); - - let path = dir.path().canonicalize().unwrap(); - println!("{}", path.display()); - - if !path.exists() { - panic!("Path {} does not exist!", path.display()) - } else if !path.is_dir() { - panic!("Path {} is not a directory!", path.display()) - } - - lib_rx.inner().0.send(Some(path)).unwrap(); - app.manage(handle_tx.inner().0.recv().unwrap()); - app.emit("library_loaded", ()).unwrap(); - Ok(()) -} - -#[tauri::command] - async fn lib_already_created(app: tauri::AppHandle, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> { - println!("lib already created"); - lib_rx.inner().0.send(None).unwrap(); - app.manage(handle_tx.inner().0.recv().unwrap()); - app.emit("library_loaded", ()).unwrap(); - Ok(()) -} +#![allow(while_true)] + +use std::{ + fs, + path::PathBuf, + sync::Arc, + thread::{scope, spawn}, + time::Duration, +}; + +use crossbeam::channel::{bounded, unbounded, Receiver, Sender}; +use dmp_core::{ + config::{Config, ConfigLibrary}, + music_controller::{ + connections::ConnectionsInput, + controller::{Controller, ControllerHandle, PlaybackInfo}, + }, + music_storage::library::{MusicLibrary, Song}, +}; +use futures::channel::oneshot; +use parking_lot::RwLock; +use tauri::{http::Response, Emitter, Manager, State, Wry}; +use uuid::Uuid; +use wrappers::{_Song, stop}; + +use crate::wrappers::{ + get_library, get_playlist, get_playlists, get_queue, get_song, import_playlist, next, pause, + play, prev, remove_from_queue, seek, set_volume, +}; +use commands::{add_song_to_queue, display_album_art, play_now}; + +pub mod commands; +pub mod wrappers; + +const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png"); + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + let (rx, tx) = unbounded::(); + let (lib_rx, lib_tx) = unbounded::>(); + let (handle_rx, handle_tx) = unbounded::(); + let (playback_info_rx, playback_info_tx) = bounded(1); + let (next_rx, next_tx) = bounded(1); + + let _controller_thread = spawn(move || { + let mut config = { tx.recv().unwrap() }; + let scan_path = { lib_tx.recv().unwrap() }; + let _temp_config = ConfigLibrary::default(); + let _lib = config.libraries.get_default().unwrap_or(&_temp_config); + + let save_path = if _lib.path == PathBuf::default() { + let p = scan_path.as_ref().unwrap().clone().canonicalize().unwrap(); + + if cfg!(windows) { + p.join("library_windows.dlib") + } else if cfg!(unix) { + p.join("library_unix.dlib") + } else { + p.join("library.dlib") + } + } else { + _lib.path.clone() + }; + println!( + "save_path: {}\nscan_path:{scan_path:?}", + save_path.display() + ); + + let mut library = MusicLibrary::init(save_path.clone(), _lib.uuid).unwrap(); + + let scan_path = scan_path.unwrap_or_else(|| { + config + .libraries + .get_default() + .unwrap() + .scan_folders + .as_ref() + .unwrap()[0] + .clone() + }); + + if config.libraries.get_default().is_err() { + library.scan_folder(&scan_path).unwrap(); + config.push_library(ConfigLibrary::new( + save_path.clone(), + String::from("Library"), + Some(vec![scan_path.clone()]), + Some(library.uuid), + )); + } + if library.library.is_empty() { + println!("library is empty"); + } else { + config.write_file().unwrap(); + } + println!("scan_path: {}", scan_path.display()); + + library.save(save_path).unwrap(); + + let (handle, input, playback_info, next_song_notification) = ControllerHandle::new( + library, + std::sync::Arc::new(RwLock::new(config)), + Some(ConnectionsInput { + discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID") + .map(|id| id.parse::().unwrap()), + }), + ); + + handle_rx.send(handle).unwrap(); + playback_info_rx.send(playback_info).unwrap(); + next_rx.send(next_song_notification).unwrap(); + + let _controller = futures::executor::block_on(Controller::start(input)).unwrap(); + }); + let app = tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .invoke_handler(tauri::generate_handler![ + get_config, + create_new_library, + get_library, + play, + pause, + stop, + set_volume, + next, + prev, + get_song, + lib_already_created, + get_queue, + add_song_to_queue, + play_now, + import_playlist, + get_playlist, + get_playlists, + remove_from_queue, + display_album_art, + seek, + ]) + .manage(ConfigRx(rx)) + .manage(LibRx(lib_rx)) + .manage(HandleTx(handle_tx)) + .manage(tempfile::TempDir::new().unwrap()) + .setup(|app| { + let _app = app.handle().clone(); + let app = _app.clone(); + + std::thread::Builder::new() + .name("PlaybackInfo handler".to_string()) + .spawn(move || { + let mut _info: Arc> = + Arc::new(RwLock::new(PlaybackInfo::default())); + let mut _now_playing: Arc>> = Arc::new(RwLock::new(None)); + + scope(|s| { + let info = _info.clone(); + s.spawn(|| { + let info = info; + let playback_info = playback_info_tx.recv().unwrap(); + while true { + let i = playback_info.take(); + app.emit("playback_info", i.clone()).unwrap(); + *info.write() = i; + std::thread::sleep(Duration::from_millis(100)); + } + }); + + let now_playing = _now_playing.clone(); + s.spawn(|| { + let now_playing = now_playing; + let next_song_notification = next_tx.recv().unwrap(); + while true { + let song = next_song_notification.recv().unwrap(); + app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("queue_updated", ()).unwrap(); + app.emit("playing", ()).unwrap(); + _ = now_playing.write().insert(song); + } + }); + }); + }) + .unwrap(); + + Ok(()) + }) + .register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| { + let query = req + .clone() + .uri() + .clone() + .into_parts() + .path_and_query + .unwrap() + .query() + .unwrap() + .to_string(); + + let bytes = if query.as_str() == "default" { + Some(DEFAULT_IMAGE.to_vec()) + } else { + futures::executor::block_on(async move { + let controller = ctx.app_handle().state::(); + let song = controller + .lib_get_song(Uuid::parse_str(query.as_str()).unwrap()) + .await + .0; + Some( + song.album_art(0) + .unwrap_or_else(|_| None) + .unwrap_or(DEFAULT_IMAGE.to_vec()), + ) + }) + }; + + res.respond( + Response::builder() + .header("Origin", "*") + .header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len()) + .status(200) + .body(bytes.unwrap_or_default()) + .unwrap(), + ); + }) + .build(tauri::generate_context!()) + .expect("error while building tauri application"); + + app.run(|_app_handle, event| match event { + tauri::RunEvent::ExitRequested { .. } => { + // api.prevent_exit(); + //panic!("does this kill the player?") + } + _ => {} + }); +} + +struct ConfigRx(Sender); + +struct LibRx(Sender>); +struct HandleTx(Receiver); + +#[tauri::command] +async fn get_config(state: State<'_, ConfigRx>) -> Result { + if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") { + let path = dir.config_dir(); + fs::create_dir_all(path) + .or_else(|err| { + if err.kind() == std::io::ErrorKind::AlreadyExists { + Ok(()) + } else { + Err(err) + } + }) + .unwrap(); + + 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(); + c + }; + + state.inner().0.send(config.clone()).unwrap(); + + Ok(config) + } else { + panic!("No config dir for DMP") + } +} + +#[tauri::command] +async fn create_new_library( + app: tauri::AppHandle, + lib_rx: State<'_, LibRx>, + handle_tx: State<'_, HandleTx>, +) -> Result<(), String> { + let dir = rfd::AsyncFileDialog::new() + .set_title("Pick a library path") + .pick_folder() + .await + .unwrap(); + + let path = dir.path().canonicalize().unwrap(); + println!("{}", path.display()); + + if !path.exists() { + panic!("Path {} does not exist!", path.display()) + } else if !path.is_dir() { + panic!("Path {} is not a directory!", path.display()) + } + + lib_rx.inner().0.send(Some(path)).unwrap(); + app.manage(handle_tx.inner().0.recv().unwrap()); + app.emit("library_loaded", ()).unwrap(); + Ok(()) +} + +#[tauri::command] +async fn lib_already_created( + app: tauri::AppHandle, + lib_rx: State<'_, LibRx>, + handle_tx: State<'_, HandleTx>, +) -> Result<(), String> { + println!("lib already created"); + lib_rx.inner().0.send(None).unwrap(); + app.manage(handle_tx.inner().0.recv().unwrap()); + app.emit("library_loaded", ()).unwrap(); + Ok(()) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 63318ee..5329e02 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,7 +1,6 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - fn main() { colog::init(); diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index ffaa750..36d68e9 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -1,8 +1,11 @@ use std::{collections::BTreeMap, path::PathBuf}; -use chrono::{DateTime, Utc, serde::ts_milliseconds_option}; +use chrono::{serde::ts_milliseconds_option, DateTime, Utc}; use crossbeam::channel::Sender; -use dmp_core::{music_controller::controller::{ControllerHandle, PlayerLocation}, music_storage::library::{Song, Tag, URI}}; +use dmp_core::{ + music_controller::controller::{ControllerHandle, PlayerLocation}, + music_storage::library::{Song, Tag, URI}, +}; use itertools::Itertools; use kushi::QueueItemType; use serde::Serialize; @@ -12,40 +15,52 @@ use uuid::Uuid; pub struct ArtworkRx(pub Sender>); #[tauri::command] -pub async fn play(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn play( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, +) -> Result<(), String> { match ctrl_handle.play().await { Ok(()) => { app.emit("playing", ()).unwrap(); Ok(()) - }, - Err(e) => Err(e.to_string()) + } + Err(e) => Err(e.to_string()), } } #[tauri::command] -pub async fn pause(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn pause( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, +) -> Result<(), String> { match ctrl_handle.pause().await { Ok(()) => { app.emit("paused", ()).unwrap(); Ok(()) } - Err(e) => Err(e.to_string()) + Err(e) => Err(e.to_string()), } } #[tauri::command] -pub async fn stop(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn stop( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, +) -> Result<(), String> { match ctrl_handle.stop().await { Ok(()) => { app.emit("stop", ()).unwrap(); Ok(()) } - Err(e) => Err(e.to_string()) + Err(e) => Err(e.to_string()), } } #[tauri::command] -pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String) -> Result<(), String> { +pub async fn set_volume( + ctrl_handle: State<'_, ControllerHandle>, + volume: String, +) -> Result<(), String> { let volume = volume.parse::().unwrap() / 100.0; ctrl_handle.set_volume(volume).await; Ok(()) @@ -57,10 +72,13 @@ pub async fn get_volume(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), } #[tauri::command] -pub async fn next(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn next( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, +) -> Result<(), String> { let song = match ctrl_handle.next().await { Ok(s) => s, - Err(e) => return Err(e.to_string()) + Err(e) => return Err(e.to_string()), }; app.emit("now_playing_change", _Song::from(&song)).unwrap(); app.emit("queue_updated", ()).unwrap(); @@ -69,10 +87,13 @@ pub async fn next(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) } #[tauri::command] -pub async fn prev(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn prev( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, +) -> Result<(), String> { let song = match ctrl_handle.prev().await { Ok(s) => s, - Err(e) => return Err(e.to_string()) + Err(e) => return Err(e.to_string()), }; println!("prev"); app.emit("now_playing_change", _Song::from(&song)).unwrap(); @@ -82,33 +103,38 @@ pub async fn prev(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) #[tauri::command] pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { - Ok(()) } #[tauri::command] -pub async fn get_queue(ctrl_handle: State<'_, ControllerHandle>) -> Result, String> { - Ok( - ctrl_handle - .queue_get_all() - .await - .into_iter() - .map(|item| { - let QueueItemType::Single(song) = item.item else { unreachable!("There should be no albums in the queue right now") }; - (_Song::from(&song.song), song.location) - } - ).collect_vec() - ) +pub async fn get_queue( + ctrl_handle: State<'_, ControllerHandle>, +) -> Result, String> { + Ok(ctrl_handle + .queue_get_all() + .await + .into_iter() + .map(|item| { + let QueueItemType::Single(song) = item.item else { + unreachable!("There should be no albums in the queue right now") + }; + (_Song::from(&song.song), song.location) + }) + .collect_vec()) } #[tauri::command] -pub async fn remove_from_queue(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>, index: usize) -> Result<(), String> { +pub async fn remove_from_queue( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, + index: usize, +) -> Result<(), String> { match ctrl_handle.queue_remove(index).await { Ok(_) => { app.emit("queue_updated", ()).unwrap(); Ok(()) } - Err(e) => Err(e.to_string()) + Err(e) => Err(e.to_string()), } } @@ -140,7 +166,11 @@ impl From<&Song> for _Song { last_played: value.last_played, date_added: value.date_added, date_modified: value.date_modified, - tags: value.tags.iter().map(|(k, v)| (k.to_string(), v.clone())).collect() + tags: value + .tags + .iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect(), } } } @@ -157,50 +187,82 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result, uuid: Uuid) -> Result, String> { +pub async fn get_playlist( + ctrl_handle: State<'_, ControllerHandle>, + uuid: Uuid, +) -> Result, String> { let playlist = match ctrl_handle.playlist_get(uuid).await { Ok(list) => list, - Err(_) => todo!() + Err(_) => todo!(), }; - let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::>(); - println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len()); + 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> { +pub async fn get_playlists( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, +) -> Result<(), String> { let lists = ctrl_handle.playlist_get_all().await; - app.emit("playlists_gotten", lists.into_iter().map(|(uuid, name)| PlaylistPayload { uuid, name }).collect_vec()).unwrap(); + 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 { +pub async fn import_playlist( + ctrl_handle: State<'_, ControllerHandle>, +) -> Result { let file = rfd::AsyncFileDialog::new() - .add_filter("m3u8 Playlist", &["m3u8", "m3u"]) - .set_title("Import a Playlist") - .pick_file() - .await - .unwrap(); + .add_filter("m3u8 Playlist", &["m3u8", "m3u"]) + .set_title("Import a Playlist") + .pick_file() + .await + .unwrap(); - let (uuid, name) = ctrl_handle.playlist_import_m3u(PathBuf::from(file.path())).await.unwrap(); + let (uuid, name) = ctrl_handle + .playlist_import_m3u(PathBuf::from(file.path())) + .await + .unwrap(); ctrl_handle.lib_save().await; println!("Imported Playlist {name}"); - Ok(PlaylistPayload {uuid, name}) + Ok(PlaylistPayload { uuid, name }) } #[derive(Serialize, Clone)] pub struct PlaylistPayload { uuid: Uuid, - name: String + name: String, } #[tauri::command] -pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<_Song, String> { +pub async fn get_song( + ctrl_handle: State<'_, ControllerHandle>, + uuid: Uuid, +) -> Result<_Song, String> { let song = ctrl_handle.lib_get_song(uuid).await.0; - println!("got song {}", &song.tags.get(&Tag::Title).unwrap_or(&String::new())); + println!( + "got song {}", + &song.tags.get(&Tag::Title).unwrap_or(&String::new()) + ); Ok(_Song::from(&song)) }