ran cargo fmt

This commit is contained in:
MrDulfin 2025-03-23 23:40:36 -04:00
parent d88c6e07d5
commit 8f30c38c4c
10 changed files with 211 additions and 108 deletions

View file

@ -1,9 +1,9 @@
#![allow(while_true)] #![allow(while_true)]
pub mod music_storage { pub mod music_storage {
pub mod library; pub mod library;
pub mod queue;
pub mod music_collection; pub mod music_collection;
pub mod playlist; pub mod playlist;
pub mod queue;
mod utils; mod utils;
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -2,7 +2,9 @@ use std::{
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
}, thread::sleep, time::{Duration, Instant, SystemTime, UNIX_EPOCH} },
thread::sleep,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
}; };
use chrono::TimeDelta; use chrono::TimeDelta;
@ -20,7 +22,6 @@ use crate::{
music_storage::library::{Song, Tag}, music_storage::library::{Song, Tag},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) enum ConnectionsNotification { pub(super) enum ConnectionsNotification {
Playback { Playback {
@ -31,7 +32,7 @@ pub(super) enum ConnectionsNotification {
SongChange(Song), SongChange(Song),
AboutToFinish, AboutToFinish,
EOS, EOS,
TryEnableConnection(TryConnectionType) TryEnableConnection(TryConnectionType),
} }
#[non_exhaustive] #[non_exhaustive]
@ -41,7 +42,7 @@ pub(super) enum TryConnectionType {
LastFM { LastFM {
api_key: String, api_key: String,
api_secret: String, api_secret: String,
auth: LastFMAuth auth: LastFMAuth,
}, },
ListenBrainz(String), ListenBrainz(String),
} }
@ -49,10 +50,7 @@ pub(super) enum TryConnectionType {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum LastFMAuth { pub enum LastFMAuth {
Session(Option<String>), Session(Option<String>),
UserPass { UserPass { username: String, password: String },
username: String,
password: String
}
} }
pub(super) struct ControllerConnections { pub(super) struct ControllerConnections {
@ -92,18 +90,29 @@ pub(super) fn handle_connections(
use ConnectionsNotification::*; use ConnectionsNotification::*;
while true { while true {
match notifications_tx.recv().unwrap() { 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)); _ = 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(position) = _position.map(|t| t.num_milliseconds()) else {
let Some(duration) = _duration.map(|t| t.num_milliseconds()) else { continue }; continue;
};
let Some(duration) = _duration.map(|t| t.num_milliseconds()) else {
continue;
};
// Scrobble at 50% or at 4 minutes // 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; 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) { if LB_ACTIVE.load(Ordering::Relaxed) {
lb_scrobble_rx.send(()).unwrap(); lb_scrobble_rx.send(()).unwrap();
} }
@ -130,60 +139,83 @@ pub(super) fn handle_connections(
last_now_playing_rx.send(song.clone()).unwrap(); last_now_playing_rx.send(song.clone()).unwrap();
} }
} }
EOS => { continue } EOS => continue,
AboutToFinish => { continue } AboutToFinish => continue,
TryEnableConnection(c) => { match c { TryEnableConnection(c) => {
TryConnectionType::Discord(client_id) => { match c {
let (dc_song_tx, dc_state_tx, dc_position_tx) = (dc_now_playing_tx.clone(), dc_state_tx.clone(), dc_position_tx.clone()); TryConnectionType::Discord(client_id) => {
std::thread::Builder::new() let (dc_song_tx, dc_state_tx, dc_position_tx) = (
.name("Discord RPC Handler".to_string()) dc_now_playing_tx.clone(),
.spawn(move || { 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 // TODO: add proper error handling here
discord_rpc(client_id, dc_song_tx, dc_state_tx, dc_position_tx); discord_rpc(client_id, dc_song_tx, dc_state_tx, dc_position_tx);
}) })
.unwrap(); .unwrap();
}, }
TryConnectionType::ListenBrainz(token) => { TryConnectionType::ListenBrainz(token) => {
let (lb_now_playing_tx, lb_scrobble_tx) = (lb_now_playing_tx.clone(), lb_scrobble_tx.clone()); let (lb_now_playing_tx, lb_scrobble_tx) =
std::thread::Builder::new() (lb_now_playing_tx.clone(), lb_scrobble_tx.clone());
.name("ListenBrainz Handler".to_string()) std::thread::Builder::new()
.spawn(move || { .name("ListenBrainz Handler".to_string())
listenbrainz_scrobble(&token, lb_now_playing_tx, lb_scrobble_tx); .spawn(move || {
}) listenbrainz_scrobble(&token, lb_now_playing_tx, lb_scrobble_tx);
.unwrap(); })
} .unwrap();
TryConnectionType::LastFM { api_key, api_secret, auth } => { }
let (config, notifications_rx) = (config.clone(), notifications_rx.clone()); TryConnectionType::LastFM {
let (last_now_playing_tx, last_scrobble_tx) = (last_now_playing_tx.clone(), last_scrobble_tx.clone()); api_key,
std::thread::Builder::new() api_secret,
.name("last.fm Handler".to_string()) auth,
.spawn(move || { } => {
let scrobbler = match auth { let (config, notifications_rx) = (config.clone(), notifications_rx.clone());
LastFMAuth::Session(key) => { let (last_now_playing_tx, last_scrobble_tx) =
if let Some(session) = key { (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); let mut scrobbler = Scrobbler::new(&api_key, &api_secret);
scrobbler.authenticate_with_session_key(&session); scrobbler.authenticate_with_session_key(&session);
Ok(scrobbler) Ok(scrobbler)
} else { } else {
last_fm_auth(config, notifications_rx, &api_key, &api_secret) last_fm_auth(
}.unwrap() config,
}, notifications_rx,
LastFMAuth::UserPass { username, password } => { &api_key,
let mut scrobbler = Scrobbler::new(&api_key, &api_secret); &api_secret,
scrobbler.authenticate_with_password(&username, &password).unwrap(); )
scrobbler }
} .unwrap(),
}; LastFMAuth::UserPass { username, password } => {
last_fm_scrobble(scrobbler, last_now_playing_tx, last_scrobble_tx); let mut scrobbler = Scrobbler::new(&api_key, &api_secret);
}) scrobbler
.unwrap(); .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<Song>, state_tx: Receiver<PrismState>, position_tx: Receiver<Option<TimeDelta>>) { fn discord_rpc(
client_id: u64,
song_tx: Receiver<Song>,
state_tx: Receiver<PrismState>,
position_tx: Receiver<Option<TimeDelta>>,
) {
let mut client = let mut client =
discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None); discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None);
client.start(); client.start();
@ -223,9 +255,10 @@ fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<Prism
if let Ok(Some(pos)) = position_tx.recv_timeout(Duration::from_millis(100)) { if let Ok(Some(pos)) = position_tx.recv_timeout(Duration::from_millis(100)) {
// set back the start position to where it would be if it hadn't been paused / seeked // set back the start position to where it would be if it hadn't been paused / seeked
now = SystemTime::now() now = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("Time went backwards?") .expect("Time went backwards?")
.as_secs() - u64::try_from(pos.num_seconds()).unwrap(); .as_secs()
- u64::try_from(pos.num_seconds()).unwrap();
} }
client client
@ -236,8 +269,7 @@ fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<Prism
"{}{}{}", "{}{}{}",
s.get_tag(&Tag::Artist) s.get_tag(&Tag::Artist)
.map_or(String::new(), |album| album.clone()), .map_or(String::new(), |album| album.clone()),
if s.get_tag(&Tag::Album).is_some() if s.get_tag(&Tag::Album).is_some() && s.get_tag(&Tag::Artist).is_some()
&& s.get_tag(&Tag::Artist).is_some()
{ {
" - " " - "
} else { } else {
@ -265,13 +297,15 @@ fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<Prism
} else { } else {
activity activity
} }
.assets(|a| a.large_text(match state { .assets(|a| {
Some(PrismState::Playing) => "Playing", a.large_text(match state {
Some(PrismState::Paused) => "Paused", Some(PrismState::Playing) => "Playing",
Some(PrismState::Stopped) => "Stopped", Some(PrismState::Paused) => "Paused",
None => "Started", Some(PrismState::Stopped) => "Stopped",
_ => "I'm Scared, Boss" None => "Started",
})) _ => "I'm Scared, Boss",
})
})
.instance(true) .instance(true)
}) })
.unwrap(); .unwrap();
@ -340,7 +374,7 @@ fn last_fm_auth(
config: Arc<RwLock<Config>>, config: Arc<RwLock<Config>>,
notifications_rx: Sender<ConnectionsNotification>, notifications_rx: Sender<ConnectionsNotification>,
api_key: &str, api_key: &str,
api_secret: &str api_secret: &str,
) -> Result<Scrobbler, Box<dyn std::error::Error>> { ) -> Result<Scrobbler, Box<dyn std::error::Error>> {
let token = { let token = {
tokio::runtime::Builder::new_current_thread() tokio::runtime::Builder::new_current_thread()
@ -361,7 +395,11 @@ fn last_fm_auth(
}; };
let mut scrobbler = Scrobbler::new(api_key, api_secret); let mut scrobbler = Scrobbler::new(api_key, api_secret);
println!("Token: {}", token.token); 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 { let session = loop {
if let Ok(session) = scrobbler.authenticate_with_token(&token.token) { if let Ok(session) = scrobbler.authenticate_with_token(&token.token) {
@ -378,7 +416,11 @@ fn last_fm_auth(
Ok(scrobbler) Ok(scrobbler)
} }
fn last_fm_scrobble(scrobbler: Scrobbler, now_playing_tx: Receiver<Song>, scrobble_tx: Receiver<()>) { fn last_fm_scrobble(
scrobbler: Scrobbler,
now_playing_tx: Receiver<Song>,
scrobble_tx: Receiver<()>,
) {
// TODO: Add support for scrobble storage for later // TODO: Add support for scrobble storage for later
let mut song: Option<Song> = None; let mut song: Option<Song> = None;
@ -446,8 +488,6 @@ fn last_fm_scrobble(scrobbler: Scrobbler, now_playing_tx: Receiver<Song>, scrobb
LAST_FM_ACTIVE.store(false, Ordering::Relaxed); LAST_FM_ACTIVE.store(false, Ordering::Relaxed);
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Token { pub struct Token {
token: String, token: String,

View file

@ -161,7 +161,7 @@ pub struct ControllerInput {
), ),
connections_mail: ( connections_mail: (
crossbeam_channel::Sender<ConnectionsNotification>, crossbeam_channel::Sender<ConnectionsNotification>,
crossbeam_channel::Receiver<ConnectionsNotification> crossbeam_channel::Receiver<ConnectionsNotification>,
), ),
library: MusicLibrary, library: MusicLibrary,
config: Arc<RwLock<Config>>, config: Arc<RwLock<Config>>,

View file

@ -3,7 +3,11 @@ use std::path::PathBuf;
use async_channel::{Receiver, Sender}; use async_channel::{Receiver, Sender};
use uuid::Uuid; 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::{ use super::{
controller::{ controller::{
@ -178,18 +182,43 @@ impl ControllerHandle {
// The Connections Section // The Connections Section
pub fn discord_rpc(&self, client_id: u64) { 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) { 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) { pub fn last_fm_scrobble_auth(
self.connections_rx.send(super::connections::ConnectionsNotification::TryEnableConnection(super::connections::TryConnectionType::LastFM { api_key, api_secret, auth })).unwrap(); &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 { pub(super) struct LibraryCommandInput {

View file

@ -2,10 +2,13 @@ use chrono::TimeDelta;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use prismriver::{Prismriver, Volume}; use prismriver::{Prismriver, Volume};
use crate::{music_controller::{ use crate::{
controller::{LibraryCommand, LibraryResponse}, music_controller::{
queue::QueueSong, controller::{LibraryCommand, LibraryResponse},
}, music_storage::queue::{QueueItem, QueueItemType}}; queue::QueueSong,
},
music_storage::queue::{QueueItem, QueueItemType},
};
use super::{ use super::{
connections::ConnectionsNotification, connections::ConnectionsNotification,

View file

@ -52,7 +52,9 @@ impl Controller {
futures::executor::block_on(async { futures::executor::block_on(async {
while true { while true {
_ = about_to_finish_tx.recv(); _ = about_to_finish_tx.recv();
notify_connections.send(ConnectionsNotification::AboutToFinish).unwrap(); notify_connections
.send(ConnectionsNotification::AboutToFinish)
.unwrap();
println!("About to Finish"); println!("About to Finish");
} }
}) })
@ -79,8 +81,8 @@ impl Controller {
} }
notify_connections notify_connections
.send(ConnectionsNotification::EOS) .send(ConnectionsNotification::EOS)
.unwrap(); .unwrap();
println!("End of song"); println!("End of song");
} }
}); });

View file

@ -1,8 +1,13 @@
use std::{fs::OpenOptions, io::Write}; use std::{fs::OpenOptions, io::Write};
use dmp_core::{music_controller::{ use dmp_core::{
connections::LastFMAuth, controller::{ControllerHandle, PlayerLocation}, queue::QueueSong music_controller::{
}, music_storage::queue::{QueueItem, QueueItemType}}; connections::LastFMAuth,
controller::{ControllerHandle, PlayerLocation},
queue::QueueSong,
},
music_storage::queue::{QueueItem, QueueItemType},
};
use tauri::{AppHandle, Emitter, State, Wry}; use tauri::{AppHandle, Emitter, State, Wry};
use tempfile::TempDir; use tempfile::TempDir;
use uuid::Uuid; use uuid::Uuid;
@ -81,6 +86,10 @@ pub async fn display_album_art(
#[tauri::command] #[tauri::command]
pub async fn last_fm_init_auth(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { 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(()) Ok(())
} }

View file

@ -5,7 +5,11 @@ use tauri::{State, WebviewWindowBuilder, Window, Wry};
#[tauri::command] #[tauri::command]
pub async fn open_config_window(app: tauri::AppHandle<Wry>) -> Result<(), String> { pub async fn open_config_window(app: tauri::AppHandle<Wry>) -> 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") .title("Edit Dango Music Player")
.build() .build()
.unwrap(); .unwrap();
@ -18,10 +22,18 @@ pub async fn get_config(ctrl_handle: State<'_, ControllerHandle>) -> Result<Conf
} }
#[tauri::command] #[tauri::command]
pub async fn save_config(ctrl_handle: State<'_, ControllerHandle>, 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(); 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(); let token = config.connections.listenbrainz_token.clone().unwrap();
ctrl_handle.listenbrainz_scrobble_auth(dbg!(token)); ctrl_handle.listenbrainz_scrobble_auth(dbg!(token));
} }

View file

@ -12,7 +12,10 @@ use config::{close_window, get_config, open_config_window, save_config};
use crossbeam::channel::{bounded, unbounded, Receiver, Sender}; use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
use dmp_core::{ use dmp_core::{
config::{Config, ConfigLibrary}, config::{Config, ConfigLibrary},
music_controller::{connections::LastFMAuth, controller::{Controller, ControllerHandle, PlaybackInfo}}, music_controller::{
connections::LastFMAuth,
controller::{Controller, ControllerHandle, PlaybackInfo},
},
music_storage::library::{MusicLibrary, Song}, music_storage::library::{MusicLibrary, Song},
}; };
use parking_lot::RwLock; 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}; use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now};
pub mod commands; pub mod commands;
pub mod wrappers;
pub mod config; pub mod config;
pub mod wrappers;
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png"); 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 last_fm_session = config.connections.last_fm_session.clone();
let listenbrainz_token = config.connections.listenbrainz_token.clone(); let listenbrainz_token = config.connections.listenbrainz_token.clone();
let (handle, input, playback_info, next_song_notification) = ControllerHandle::new( let (handle, input, playback_info, next_song_notification) =
library, ControllerHandle::new(library, std::sync::Arc::new(RwLock::new(config)));
std::sync::Arc::new(RwLock::new(config))
);
handle.discord_rpc(DISCORD_CLIENT_ID); handle.discord_rpc(DISCORD_CLIENT_ID);
if let Some(token) = listenbrainz_token { if let Some(token) = listenbrainz_token {
@ -114,7 +115,11 @@ pub fn run() {
println!("No ListenBrainz token found"); println!("No ListenBrainz token found");
} }
if let Some(session) = last_fm_session { 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(); handle_rx.send(handle).unwrap();

View file

@ -4,7 +4,10 @@ use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
use crossbeam::channel::Sender; use crossbeam::channel::Sender;
use dmp_core::{ use dmp_core::{
music_controller::controller::{ControllerHandle, PlayerLocation}, 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 itertools::Itertools;
use serde::Serialize; use serde::Serialize;