diff --git a/dmp-core/src/lib.rs b/dmp-core/src/lib.rs index a72538d..f70e938 100644 --- a/dmp-core/src/lib.rs +++ b/dmp-core/src/lib.rs @@ -1,9 +1,9 @@ #![allow(while_true)] pub mod music_storage { pub mod library; - pub mod queue; pub mod music_collection; pub mod playlist; + pub mod queue; mod utils; #[allow(dead_code)] diff --git a/dmp-core/src/music_controller/connections.rs b/dmp-core/src/music_controller/connections.rs index 0c3abfb..89dbfa8 100644 --- a/dmp-core/src/music_controller/connections.rs +++ b/dmp-core/src/music_controller/connections.rs @@ -2,7 +2,9 @@ use std::{ sync::{ atomic::{AtomicBool, Ordering}, Arc, - }, thread::sleep, time::{Duration, Instant, SystemTime, UNIX_EPOCH} + }, + thread::sleep, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; use chrono::TimeDelta; @@ -20,7 +22,6 @@ use crate::{ music_storage::library::{Song, Tag}, }; - #[derive(Debug, Clone)] pub(super) enum ConnectionsNotification { Playback { @@ -31,7 +32,7 @@ pub(super) enum ConnectionsNotification { SongChange(Song), AboutToFinish, EOS, - TryEnableConnection(TryConnectionType) + TryEnableConnection(TryConnectionType), } #[non_exhaustive] @@ -41,7 +42,7 @@ pub(super) enum TryConnectionType { LastFM { api_key: String, api_secret: String, - auth: LastFMAuth + auth: LastFMAuth, }, ListenBrainz(String), } @@ -49,10 +50,7 @@ pub(super) enum TryConnectionType { #[derive(Debug, Clone)] pub enum LastFMAuth { Session(Option), - UserPass { - username: String, - password: String - } + UserPass { username: String, password: String }, } pub(super) struct ControllerConnections { @@ -92,18 +90,29 @@ pub(super) fn handle_connections( use ConnectionsNotification::*; while true { match notifications_tx.recv().unwrap() { - Playback { position: _position, duration: _duration } => { + Playback { + position: _position, + duration: _duration, + } => { _ = dc_position_rx.send_timeout(_position.clone(), Duration::from_millis(0)); - if song_scrobbled { continue } + if song_scrobbled { + continue; + } - let Some(position) = _position.map(|t| t.num_milliseconds()) else { continue }; - let Some(duration) = _duration.map(|t| t.num_milliseconds()) else { continue }; + let Some(position) = _position.map(|t| t.num_milliseconds()) else { + continue; + }; + let Some(duration) = _duration.map(|t| t.num_milliseconds()) else { + continue; + }; // Scrobble at 50% or at 4 minutes - if duration < 30000 || position == 0 { continue } + if duration < 30000 || position == 0 { + continue; + } let percent_played = position as f32 / duration as f32; - if percent_played != 0.0 && (percent_played > 0.5 || position >= 240000) { + if percent_played != 0.0 && (percent_played > 0.5 || position >= 240000) { if LB_ACTIVE.load(Ordering::Relaxed) { lb_scrobble_rx.send(()).unwrap(); } @@ -130,60 +139,83 @@ pub(super) fn handle_connections( last_now_playing_rx.send(song.clone()).unwrap(); } } - EOS => { continue } - AboutToFinish => { continue } - TryEnableConnection(c) => { match c { - TryConnectionType::Discord(client_id) => { - let (dc_song_tx, dc_state_tx, dc_position_tx) = (dc_now_playing_tx.clone(), dc_state_tx.clone(), dc_position_tx.clone()); - std::thread::Builder::new() - .name("Discord RPC Handler".to_string()) - .spawn(move || { + EOS => continue, + AboutToFinish => continue, + TryEnableConnection(c) => { + match c { + TryConnectionType::Discord(client_id) => { + let (dc_song_tx, dc_state_tx, dc_position_tx) = ( + dc_now_playing_tx.clone(), + dc_state_tx.clone(), + dc_position_tx.clone(), + ); + std::thread::Builder::new() + .name("Discord RPC Handler".to_string()) + .spawn(move || { // TODO: add proper error handling here discord_rpc(client_id, dc_song_tx, dc_state_tx, dc_position_tx); - }) - .unwrap(); - }, - TryConnectionType::ListenBrainz(token) => { - let (lb_now_playing_tx, lb_scrobble_tx) = (lb_now_playing_tx.clone(), lb_scrobble_tx.clone()); - std::thread::Builder::new() - .name("ListenBrainz Handler".to_string()) - .spawn(move || { - listenbrainz_scrobble(&token, lb_now_playing_tx, lb_scrobble_tx); - }) - .unwrap(); - } - TryConnectionType::LastFM { api_key, api_secret, auth } => { - let (config, notifications_rx) = (config.clone(), notifications_rx.clone()); - let (last_now_playing_tx, last_scrobble_tx) = (last_now_playing_tx.clone(), last_scrobble_tx.clone()); - std::thread::Builder::new() - .name("last.fm Handler".to_string()) - .spawn(move || { - let scrobbler = match auth { - LastFMAuth::Session(key) => { - if let Some(session) = key { + }) + .unwrap(); + } + TryConnectionType::ListenBrainz(token) => { + let (lb_now_playing_tx, lb_scrobble_tx) = + (lb_now_playing_tx.clone(), lb_scrobble_tx.clone()); + std::thread::Builder::new() + .name("ListenBrainz Handler".to_string()) + .spawn(move || { + listenbrainz_scrobble(&token, lb_now_playing_tx, lb_scrobble_tx); + }) + .unwrap(); + } + TryConnectionType::LastFM { + api_key, + api_secret, + auth, + } => { + let (config, notifications_rx) = (config.clone(), notifications_rx.clone()); + let (last_now_playing_tx, last_scrobble_tx) = + (last_now_playing_tx.clone(), last_scrobble_tx.clone()); + std::thread::Builder::new() + .name("last.fm Handler".to_string()) + .spawn(move || { + let scrobbler = match auth { + LastFMAuth::Session(key) => if let Some(session) = key { let mut scrobbler = Scrobbler::new(&api_key, &api_secret); scrobbler.authenticate_with_session_key(&session); Ok(scrobbler) } else { - last_fm_auth(config, notifications_rx, &api_key, &api_secret) - }.unwrap() - }, - LastFMAuth::UserPass { username, password } => { - let mut scrobbler = Scrobbler::new(&api_key, &api_secret); - scrobbler.authenticate_with_password(&username, &password).unwrap(); - scrobbler - } - }; - last_fm_scrobble(scrobbler, last_now_playing_tx, last_scrobble_tx); - }) - .unwrap(); + last_fm_auth( + config, + notifications_rx, + &api_key, + &api_secret, + ) + } + .unwrap(), + LastFMAuth::UserPass { username, password } => { + let mut scrobbler = Scrobbler::new(&api_key, &api_secret); + scrobbler + .authenticate_with_password(&username, &password) + .unwrap(); + scrobbler + } + }; + last_fm_scrobble(scrobbler, last_now_playing_tx, last_scrobble_tx); + }) + .unwrap(); + } } - }} + } } } } -fn discord_rpc(client_id: u64, song_tx: Receiver, state_tx: Receiver, position_tx: Receiver>) { +fn discord_rpc( + client_id: u64, + song_tx: Receiver, + state_tx: Receiver, + position_tx: Receiver>, +) { let mut client = discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None); client.start(); @@ -223,9 +255,10 @@ fn discord_rpc(client_id: u64, song_tx: Receiver, state_tx: Receiver, state_tx: Receiver, state_tx: Receiver "Playing", - Some(PrismState::Paused) => "Paused", - Some(PrismState::Stopped) => "Stopped", - None => "Started", - _ => "I'm Scared, Boss" - })) + .assets(|a| { + a.large_text(match state { + Some(PrismState::Playing) => "Playing", + Some(PrismState::Paused) => "Paused", + Some(PrismState::Stopped) => "Stopped", + None => "Started", + _ => "I'm Scared, Boss", + }) + }) .instance(true) }) .unwrap(); @@ -340,7 +374,7 @@ fn last_fm_auth( config: Arc>, notifications_rx: Sender, api_key: &str, - api_secret: &str + api_secret: &str, ) -> Result> { let token = { tokio::runtime::Builder::new_current_thread() @@ -361,7 +395,11 @@ fn last_fm_auth( }; let mut scrobbler = Scrobbler::new(api_key, api_secret); println!("Token: {}", token.token); - opener::open_browser(format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", token.token)).unwrap(); + opener::open_browser(format!( + "http://www.last.fm/api/auth/?api_key={api_key}&token={}", + token.token + )) + .unwrap(); let session = loop { if let Ok(session) = scrobbler.authenticate_with_token(&token.token) { @@ -378,7 +416,11 @@ fn last_fm_auth( Ok(scrobbler) } -fn last_fm_scrobble(scrobbler: Scrobbler, now_playing_tx: Receiver, scrobble_tx: Receiver<()>) { +fn last_fm_scrobble( + scrobbler: Scrobbler, + now_playing_tx: Receiver, + scrobble_tx: Receiver<()>, +) { // TODO: Add support for scrobble storage for later let mut song: Option = None; @@ -446,9 +488,7 @@ fn last_fm_scrobble(scrobbler: Scrobbler, now_playing_tx: Receiver, scrobb LAST_FM_ACTIVE.store(false, Ordering::Relaxed); } - - #[derive(Deserialize)] pub struct Token { token: String, -} \ 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 5d129b4..c227f7e 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -161,7 +161,7 @@ pub struct ControllerInput { ), connections_mail: ( crossbeam_channel::Sender, - crossbeam_channel::Receiver + crossbeam_channel::Receiver, ), library: MusicLibrary, config: Arc>, diff --git a/dmp-core/src/music_controller/controller_handle.rs b/dmp-core/src/music_controller/controller_handle.rs index 4e4e61f..146fec4 100644 --- a/dmp-core/src/music_controller/controller_handle.rs +++ b/dmp-core/src/music_controller/controller_handle.rs @@ -3,7 +3,11 @@ use std::path::PathBuf; use async_channel::{Receiver, Sender}; use uuid::Uuid; -use crate::music_storage::{library::Song, playlist::ExternalPlaylist, queue::{QueueError, QueueItem}}; +use crate::music_storage::{ + library::Song, + playlist::ExternalPlaylist, + queue::{QueueError, QueueItem}, +}; use super::{ controller::{ @@ -178,18 +182,43 @@ impl ControllerHandle { // The Connections Section pub fn discord_rpc(&self, client_id: u64) { - self.connections_rx.send(super::connections::ConnectionsNotification::TryEnableConnection(super::connections::TryConnectionType::Discord(client_id))).unwrap(); + self.connections_rx + .send( + super::connections::ConnectionsNotification::TryEnableConnection( + super::connections::TryConnectionType::Discord(client_id), + ), + ) + .unwrap(); } pub fn listenbrainz_scrobble_auth(&self, token: String) { - self.connections_rx.send(super::connections::ConnectionsNotification::TryEnableConnection(super::connections::TryConnectionType::ListenBrainz(token))).unwrap(); + self.connections_rx + .send( + super::connections::ConnectionsNotification::TryEnableConnection( + super::connections::TryConnectionType::ListenBrainz(token), + ), + ) + .unwrap(); } - pub fn last_fm_scrobble_auth(&self, api_key: String, api_secret: String, auth: super::connections::LastFMAuth) { - self.connections_rx.send(super::connections::ConnectionsNotification::TryEnableConnection(super::connections::TryConnectionType::LastFM { api_key, api_secret, auth })).unwrap(); + pub fn last_fm_scrobble_auth( + &self, + api_key: String, + api_secret: String, + auth: super::connections::LastFMAuth, + ) { + self.connections_rx + .send( + super::connections::ConnectionsNotification::TryEnableConnection( + super::connections::TryConnectionType::LastFM { + api_key, + api_secret, + auth, + }, + ), + ) + .unwrap(); } - - } pub(super) struct LibraryCommandInput { diff --git a/dmp-core/src/music_controller/player_command.rs b/dmp-core/src/music_controller/player_command.rs index 3e40d83..4ea18b9 100644 --- a/dmp-core/src/music_controller/player_command.rs +++ b/dmp-core/src/music_controller/player_command.rs @@ -2,10 +2,13 @@ use chrono::TimeDelta; use crossbeam_channel::Sender; use prismriver::{Prismriver, Volume}; -use crate::{music_controller::{ - controller::{LibraryCommand, LibraryResponse}, - queue::QueueSong, -}, music_storage::queue::{QueueItem, QueueItemType}}; +use crate::{ + music_controller::{ + controller::{LibraryCommand, LibraryResponse}, + queue::QueueSong, + }, + music_storage::queue::{QueueItem, QueueItemType}, +}; use super::{ connections::ConnectionsNotification, diff --git a/dmp-core/src/music_controller/player_monitor.rs b/dmp-core/src/music_controller/player_monitor.rs index 9a6c124..9156784 100644 --- a/dmp-core/src/music_controller/player_monitor.rs +++ b/dmp-core/src/music_controller/player_monitor.rs @@ -52,7 +52,9 @@ impl Controller { futures::executor::block_on(async { while true { _ = about_to_finish_tx.recv(); - notify_connections.send(ConnectionsNotification::AboutToFinish).unwrap(); + notify_connections + .send(ConnectionsNotification::AboutToFinish) + .unwrap(); println!("About to Finish"); } }) @@ -79,8 +81,8 @@ impl Controller { } notify_connections - .send(ConnectionsNotification::EOS) - .unwrap(); + .send(ConnectionsNotification::EOS) + .unwrap(); println!("End of song"); } }); diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 250693d..ba0a436 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,8 +1,13 @@ use std::{fs::OpenOptions, io::Write}; -use dmp_core::{music_controller::{ - connections::LastFMAuth, controller::{ControllerHandle, PlayerLocation}, queue::QueueSong -}, music_storage::queue::{QueueItem, QueueItemType}}; +use dmp_core::{ + music_controller::{ + connections::LastFMAuth, + controller::{ControllerHandle, PlayerLocation}, + queue::QueueSong, + }, + music_storage::queue::{QueueItem, QueueItemType}, +}; use tauri::{AppHandle, Emitter, State, Wry}; use tempfile::TempDir; use uuid::Uuid; @@ -81,6 +86,10 @@ pub async fn display_album_art( #[tauri::command] pub async fn last_fm_init_auth(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { - ctrl_handle.last_fm_scrobble_auth(LAST_FM_API_KEY.to_string(), LAST_FM_API_SECRET.to_string(), LastFMAuth::Session(None)); + ctrl_handle.last_fm_scrobble_auth( + LAST_FM_API_KEY.to_string(), + LAST_FM_API_SECRET.to_string(), + LastFMAuth::Session(None), + ); Ok(()) -} \ No newline at end of file +} diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index e769bdd..d68672d 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -5,7 +5,11 @@ use tauri::{State, WebviewWindowBuilder, Window, Wry}; #[tauri::command] pub async fn open_config_window(app: tauri::AppHandle) -> Result<(), String> { - WebviewWindowBuilder::new(&app, "config", tauri::WebviewUrl::App(PathBuf::from("src/config/index.html"))) + WebviewWindowBuilder::new( + &app, + "config", + tauri::WebviewUrl::App(PathBuf::from("src/config/index.html")), + ) .title("Edit Dango Music Player") .build() .unwrap(); @@ -18,10 +22,18 @@ pub async fn get_config(ctrl_handle: State<'_, ControllerHandle>) -> Result, config: Config) -> Result<(), String> { +pub async fn save_config( + ctrl_handle: State<'_, ControllerHandle>, + config: Config, +) -> Result<(), String> { let config_original = ctrl_handle.config.read().clone(); - if config.connections.listenbrainz_token.as_ref().is_some_and(|t| Some(t) != config_original.connections.listenbrainz_token.as_ref()) { + if config + .connections + .listenbrainz_token + .as_ref() + .is_some_and(|t| Some(t) != config_original.connections.listenbrainz_token.as_ref()) + { let token = config.connections.listenbrainz_token.clone().unwrap(); ctrl_handle.listenbrainz_scrobble_auth(dbg!(token)); } @@ -35,4 +47,4 @@ pub async fn save_config(ctrl_handle: State<'_, ControllerHandle>, config: Confi pub async fn close_window(window: Window) -> Result<(), String> { window.close().unwrap(); Ok(()) -} \ No newline at end of file +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c38b223..83be107 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,7 +12,10 @@ use config::{close_window, get_config, open_config_window, save_config}; use crossbeam::channel::{bounded, unbounded, Receiver, Sender}; use dmp_core::{ config::{Config, ConfigLibrary}, - music_controller::{connections::LastFMAuth, controller::{Controller, ControllerHandle, PlaybackInfo}}, + music_controller::{ + connections::LastFMAuth, + controller::{Controller, ControllerHandle, PlaybackInfo}, + }, music_storage::library::{MusicLibrary, Song}, }; use parking_lot::RwLock; @@ -27,8 +30,8 @@ use crate::wrappers::{ use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now}; pub mod commands; -pub mod wrappers; pub mod config; +pub mod wrappers; const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png"); @@ -102,10 +105,8 @@ pub fn run() { let last_fm_session = config.connections.last_fm_session.clone(); let listenbrainz_token = config.connections.listenbrainz_token.clone(); - let (handle, input, playback_info, next_song_notification) = ControllerHandle::new( - library, - std::sync::Arc::new(RwLock::new(config)) - ); + let (handle, input, playback_info, next_song_notification) = + ControllerHandle::new(library, std::sync::Arc::new(RwLock::new(config))); handle.discord_rpc(DISCORD_CLIENT_ID); if let Some(token) = listenbrainz_token { @@ -114,7 +115,11 @@ pub fn run() { println!("No ListenBrainz token found"); } if let Some(session) = last_fm_session { - handle.last_fm_scrobble_auth(LAST_FM_API_KEY.to_string(), LAST_FM_API_SECRET.to_string(), LastFMAuth::Session(Some(session))); + handle.last_fm_scrobble_auth( + LAST_FM_API_KEY.to_string(), + LAST_FM_API_SECRET.to_string(), + LastFMAuth::Session(Some(session)), + ); } handle_rx.send(handle).unwrap(); diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index ede3429..e03f4fb 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -4,7 +4,10 @@ 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}, queue::QueueItemType}, + music_storage::{ + library::{Song, Tag, URI}, + queue::QueueItemType, + }, }; use itertools::Itertools; use serde::Serialize;