From 04da9c88f359d8bb592b5809f9bd3bb5eed7d7d4 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@dangoware.org>
Date: Thu, 20 Mar 2025 18:52:58 -0400
Subject: [PATCH] more changes to connections

---
 dmp-core/src/music_controller/connections.rs  | 119 +++++++++---------
 dmp-core/src/music_controller/controller.rs   |  21 ++--
 .../src/music_controller/controller_handle.rs |  15 +++
 src-tauri/src/commands.rs                     |  11 +-
 4 files changed, 99 insertions(+), 67 deletions(-)

diff --git a/dmp-core/src/music_controller/connections.rs b/dmp-core/src/music_controller/connections.rs
index 140ec16..d6dccb5 100644
--- a/dmp-core/src/music_controller/connections.rs
+++ b/dmp-core/src/music_controller/connections.rs
@@ -36,6 +36,7 @@ pub(super) enum ConnectionsNotification {
     TryEnableConnection(TryConnectionType)
 }
 
+#[non_exhaustive]
 #[derive(Debug, Clone)]
 pub(super) enum TryConnectionType {
     Discord(u64),
@@ -45,11 +46,10 @@ pub(super) enum TryConnectionType {
         auth: LastFMAuth
     },
     ListenBrainz(String),
-    Custom(String)
 }
 
 #[derive(Debug, Clone)]
-pub(super) enum LastFMAuth {
+pub enum LastFMAuth {
     Session(Option<String>),
     UserPass {
         username: String,
@@ -74,45 +74,59 @@ pub(super) fn handle_connections(
     }: ControllerConnections,
 ) {
     let (dc_state_rx, dc_state_tx) = unbounded::<PrismState>();
-    let (dc_song_rx, dc_song_tx) = unbounded::<Song>();
-    let (lb_song_rx, lb_song_tx) = unbounded::<Song>();
-    let (lb_abt_fin_rx, lb_abt_fin_tx) = unbounded::<()>();
-    let (lb_eos_rx, lb_eos_tx) = unbounded::<()>();
-    let (last_song_rx, last_song_tx) = unbounded::<Song>();
-    let (last_abt_fin_rx, last_abt_fin_tx) = unbounded::<()>();
-    let (last_eos_rx, last_eos_tx) = unbounded::<()>();
+    let (dc_now_playing_rx, dc_now_playing_tx) = unbounded::<Song>();
+    let (lb_now_playing_rx, lb_now_playing_tx) = unbounded::<Song>();
+    let (lb_scrobble_rx, lb_scrobble_tx) = unbounded::<()>();
+    let (last_now_playing_rx, last_now_playing_tx) = unbounded::<Song>();
+    let (last_scrobble_rx, last_scrobble_tx) = unbounded::<()>();
 
+    let mut song_scrobbled = false;
 
     use ConnectionsNotification::*;
     while true {
         match notifications_tx.recv().unwrap() {
-            Playback { .. } => {}
+            Playback { position: _position, duration: _duration } => {
+                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 };
+
+                // Scrobble at 50% or at 4 minutes
+                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 LB_ACTIVE.load(Ordering::Relaxed) {
+                        lb_scrobble_rx.send(()).unwrap();
+                    }
+                    if LAST_FM_ACTIVE.load(Ordering::Relaxed) {
+                        last_scrobble_rx.send(()).unwrap();
+                    }
+                    song_scrobbled = true;
+                }
+            }
             StateChange(state) => {
                 if DC_ACTIVE.load(Ordering::Relaxed) {
                     dc_state_rx.send(state.clone()).unwrap();
                 }
             }
             SongChange(song) => {
+                song_scrobbled = false;
                 if DC_ACTIVE.load(Ordering::Relaxed) {
-                    dc_song_rx.send(song.clone()).unwrap();
+                    dc_now_playing_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();
-                }
-            }
-            AboutToFinish => {
-                if LB_ACTIVE.load(Ordering::Relaxed) {
-                    lb_abt_fin_rx.send(()).unwrap();
+                    lb_now_playing_rx.send(song.clone()).unwrap();
+                }
+                if LAST_FM_ACTIVE.load(Ordering::Relaxed) {
+                    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_song_tx.clone(), dc_state_tx.clone());
+                    let (dc_song_tx, dc_state_tx) = (dc_now_playing_tx.clone(), dc_state_tx.clone());
                     std::thread::Builder::new()
                         .name("Discord RPC Handler".to_string())
                         .spawn(move || {
@@ -122,17 +136,17 @@ pub(super) fn handle_connections(
                         .unwrap();
                 },
                 TryConnectionType::ListenBrainz(token) => {
-                    let (lb_song_tx, lb_abt_fin_tx, lb_eos_tx) = (lb_song_tx.clone(), lb_abt_fin_tx.clone(), lb_eos_tx.clone());
+                    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_song_tx, lb_abt_fin_tx, lb_eos_tx);
+                            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_song_tx, last_abt_fin_tx, last_eos_tx) = (last_song_tx.clone(), last_abt_fin_tx.clone(), last_eos_tx.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 || {
@@ -152,15 +166,13 @@ pub(super) fn handle_connections(
                                     scrobbler
                                 }
                             };
-                            last_fm_scrobble(scrobbler, last_song_tx, last_abt_fin_tx, last_eos_tx);
+                            last_fm_scrobble(scrobbler, last_now_playing_tx, last_scrobble_tx);
                         })
                         .unwrap();
                 }
-                TryConnectionType::Custom(_) => unimplemented!()
             }}
         }
     }
-
 }
 
 fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<PrismState>) {
@@ -249,7 +261,7 @@ fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<Prism
     DC_ACTIVE.store(false, Ordering::Relaxed);
 }
 
-fn listenbrainz_scrobble(token: &str, song_tx: Receiver<Song>, abt_fn_tx: Receiver<()>, eos_tx: Receiver<()>) {
+fn listenbrainz_scrobble(token: &str, now_playing_tx: Receiver<Song>, scrobble_tx: Receiver<()>) {
     let mut client = ListenBrainz::new();
     client.authenticate(token).unwrap();
     if !client.is_authenticated() {
@@ -257,17 +269,15 @@ fn listenbrainz_scrobble(token: &str, song_tx: Receiver<Song>, abt_fn_tx: Receiv
     }
 
     let mut song: Option<Song> = None;
-    let mut last_song: Option<Song> = None;
     LB_ACTIVE.store(true, Ordering::Relaxed);
     println!("ListenBrainz connected");
 
     while true {
-        let song = &mut song;
-        let last_song = &mut last_song;
+        let now_playing = &mut song;
 
         let client = &client;
         select! {
-            recv(song_tx) -> res => {
+            recv(now_playing_tx) -> res => {
                 if let Ok(_song) = res {
                     let artist = if let Some(tag) = _song.get_tag(&Tag::Artist) {
                         tag.as_str()
@@ -283,15 +293,11 @@ fn listenbrainz_scrobble(token: &str, song_tx: Receiver<Song>, abt_fn_tx: Receiv
 
                     client.playing_now(artist, title, release).unwrap();
                     println!("Song Listening = {artist} - {title}");
-                    *song = Some(_song);
+                    *now_playing = Some(_song);
                 }
             },
-            recv(abt_fn_tx) -> _ => {
-                *last_song = song.take();
-                println!("song = {:?}", last_song.as_ref().map(|s| s.get_tag(&Tag::Title).map_or("No Title", |t| t.as_str())));
-            },
-            recv(eos_tx) -> _ => {
-                if let Some(song) = last_song {
+            recv(scrobble_tx) -> _ => {
+                if let Some(song) = now_playing.take() {
                     let artist = if let Some(tag) = song.get_tag(&Tag::Artist) {
                         tag.as_str()
                     } else {
@@ -305,7 +311,7 @@ fn listenbrainz_scrobble(token: &str, song_tx: Receiver<Song>, abt_fn_tx: Receiv
                     let release = song.get_tag(&Tag::Key(String::from("MusicBrainzReleaseId"))).map(|id| id.as_str());
 
                     client.listen(artist, title, release).unwrap();
-                    println!("Song Scrobbled");
+                    println!("Song {title} Listened");
                 }
             }
         }
@@ -320,6 +326,7 @@ fn last_fm_auth(
 ) -> Result<Scrobbler, Box<dyn std::error::Error>> {
     let token = {
         tokio::runtime::Builder::new_current_thread()
+        .enable_all()
         .build()
         .unwrap()
         .block_on(
@@ -345,27 +352,29 @@ fn last_fm_auth(
         sleep(Duration::from_millis(1000));
     };
     println!("Session: {}", session.key);
-
-    config.write().connections.last_fm_session = Some(session.key);
+    {
+        let mut config = config.write();
+        config.connections.last_fm_session = Some(session.key);
+        config.write_file().unwrap();
+    }
     Ok(scrobbler)
 }
 
-fn last_fm_scrobble(scrobbler: Scrobbler, song_tx: Receiver<Song>, abt_fn_tx: Receiver<()>, eos_tx: Receiver<()>) {
+fn last_fm_scrobble(scrobbler: Scrobbler, now_playing_tx: Receiver<Song>, scrobble_tx: Receiver<()>) {
     // TODO: Add support for scrobble storage for later
 
     let mut song: Option<Song> = None;
     let mut last_song: Option<Song> = None;
     LAST_FM_ACTIVE.store(true, Ordering::Relaxed);
-    println!("ListenBrainz connected");
+    println!("last.fm connected");
 
     while true {
-        let song = &mut song;
-        let last_song = &mut last_song;
+        let now_playing = &mut song;
 
         let scrobbler = &scrobbler;
 
         select! {
-            recv(song_tx) -> res => {
+            recv(now_playing_tx) -> res => {
                 if let Ok(_song) = res {
                     let title = if let Some(tag) = _song.get_tag(&Tag::Title) {
                         tag.as_str()
@@ -388,15 +397,11 @@ fn last_fm_scrobble(scrobbler: Scrobbler, song_tx: Receiver<Song>, abt_fn_tx: Re
                         Err(e) => println!("Error at last.fm now playing:\n{e}")
                     };
 
-                    *song = Some(_song);
+                    *now_playing = Some(_song);
                 }
             },
-            recv(abt_fn_tx) -> _ => {
-                *last_song = song.take();
-                println!("song = {:?}", last_song.as_ref().map(|s| s.get_tag(&Tag::Title).map_or("No Title", |t| t.as_str())));
-            },
-            recv(eos_tx) -> _ => {
-                if let Some(song) = last_song {
+            recv(scrobble_tx) -> _ => {
+                if let Some(song) = now_playing.take() {
                     let title = if let Some(tag) = song.get_tag(&Tag::Title) {
                         tag.as_str()
                     } else {
@@ -414,7 +419,7 @@ fn last_fm_scrobble(scrobbler: Scrobbler, song_tx: Receiver<Song>, abt_fn_tx: Re
                     };
 
                     match scrobbler.scrobble(&Scrobble::new(artist, title, album)) {
-                        Ok(_) => println!("Song Scrobbled"),
+                        Ok(_) => println!("Song {title} Scrobbled"),
                         Err(e) => println!("Error at last.fm scrobbler:\n{e:?}")
                     }
                 }
diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs
index aed4ece..261d633 100644
--- a/dmp-core/src/music_controller/controller.rs
+++ b/dmp-core/src/music_controller/controller.rs
@@ -160,6 +160,10 @@ pub struct ControllerInput {
         async_channel::Sender<QueueCommandInput>,
         async_channel::Receiver<QueueCommandInput>,
     ),
+    connections_mail: (
+        crossbeam_channel::Sender<ConnectionsNotification>,
+        crossbeam_channel::Receiver<ConnectionsNotification>
+    ),
     library: MusicLibrary,
     config: Arc<RwLock<Config>>,
     playback_info: Arc<AtomicCell<PlaybackInfo>>,
@@ -170,6 +174,7 @@ pub struct ControllerHandle {
     pub(super) lib_mail_rx: async_channel::Sender<LibraryCommandInput>,
     pub(super) player_mail_rx: async_channel::Sender<PlayerCommandInput>,
     pub(super) queue_mail_rx: async_channel::Sender<QueueCommandInput>,
+    pub(super) connections_rx: crossbeam_channel::Sender<ConnectionsNotification>,
 }
 
 impl ControllerHandle {
@@ -185,6 +190,7 @@ impl ControllerHandle {
         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 (connections_mail_rx, connections_mail_tx) = crossbeam_channel::unbounded();
         let playback_info = Arc::new(AtomicCell::new(PlaybackInfo::default()));
         let notify_next_song = crossbeam::channel::unbounded::<Song>();
         (
@@ -192,11 +198,13 @@ impl ControllerHandle {
                 lib_mail_rx: lib_mail_rx.clone(),
                 player_mail_rx: player_mail_rx.clone(),
                 queue_mail_rx: queue_mail_rx.clone(),
+                connections_rx: connections_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),
+                connections_mail: (connections_mail_rx, connections_mail_tx),
                 library,
                 config,
                 playback_info: Arc::clone(&playback_info),
@@ -241,13 +249,14 @@ impl ControllerState {
     }
 }
 
-#[allow(unused_variables)]
+// #[allow(unused_variables)]
 impl Controller {
     pub async fn start(
         ControllerInput {
             player_mail,
             lib_mail,
             queue_mail,
+            connections_mail,
             mut library,
             config,
             playback_info,
@@ -276,12 +285,10 @@ impl Controller {
             let player_timing = player.get_timing_recv();
             let about_to_finish_tx = player.get_about_to_finish_recv();
             let finished_tx = player.get_finished_recv();
-            let (notifications_rx, notifications_tx) =
-                crossbeam_channel::unbounded::<ConnectionsNotification>();
 
             let a = scope.spawn({
                 let queue_mail = queue_mail.clone();
-                let _notifications_rx = notifications_rx.clone();
+                let _notifications_rx = connections_mail.0.clone();
                 let _config = config.clone();
                 move || {
                     futures::executor::block_on(async {
@@ -319,7 +326,7 @@ impl Controller {
                 })
             });
 
-            let _notifications_rx = notifications_rx.clone();
+            let _notifications_rx = connections_mail.0.clone();
             let c = scope.spawn(|| {
                 Controller::player_monitor_loop(
                     player_state,
@@ -338,8 +345,8 @@ impl Controller {
                 handle_connections(
                     config,
                     ControllerConnections {
-                        notifications_rx,
-                        notifications_tx,
+                        notifications_rx: connections_mail.0,
+                        notifications_tx: connections_mail.1,
                     },
                 );
             });
diff --git a/dmp-core/src/music_controller/controller_handle.rs b/dmp-core/src/music_controller/controller_handle.rs
index 5c518aa..e31ed04 100644
--- a/dmp-core/src/music_controller/controller_handle.rs
+++ b/dmp-core/src/music_controller/controller_handle.rs
@@ -176,6 +176,21 @@ impl ControllerHandle {
         };
         res
     }
+
+    // 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();
+    }
+
+    pub fn listenbrainz_scrobble(&self, token: String) {
+        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(super) struct LibraryCommandInput {
diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
index 984e8e4..03deecc 100644
--- a/src-tauri/src/commands.rs
+++ b/src-tauri/src/commands.rs
@@ -1,15 +1,14 @@
 use std::{fs::OpenOptions, io::Write};
 
 use dmp_core::music_controller::{
-    controller::{ControllerHandle, PlayerLocation},
-    queue::QueueSong,
+    connections::LastFMAuth, controller::{ControllerHandle, PlayerLocation}, queue::QueueSong
 };
 use kushi::QueueItem;
 use tauri::{AppHandle, Emitter, State, Wry};
 use tempfile::TempDir;
 use uuid::Uuid;
 
-use crate::wrappers::_Song;
+use crate::{wrappers::_Song, LAST_FM_API_KEY, LAST_FM_API_SECRET};
 
 #[tauri::command]
 pub async fn add_song_to_queue(
@@ -80,3 +79,9 @@ pub async fn display_album_art(
     };
     Ok(())
 }
+
+#[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));
+    Ok(())
+}
\ No newline at end of file