diff --git a/music-player/server/src/bin/server.rs b/music-player/server/src/bin/server.rs index 519ac16..e6c8208 100644 --- a/music-player/server/src/bin/server.rs +++ b/music-player/server/src/bin/server.rs @@ -50,6 +50,7 @@ fn tracks() -> Vec { #[tokio::main] pub async fn main() { + /* match Core::new(Arc::new(MemoryIndex::new())) { Flow::Ok(core) => { let mut buf = String::new(); @@ -59,6 +60,7 @@ pub async fn main() { Flow::Err(err) => println!("non-fatal error: {:?}", err), Flow::Fatal(err) => println!("fatal error: {:?}", err), } + */ /* let connection = Connection::new_session().expect("to connect to dbus"); diff --git a/music-player/server/src/core.rs b/music-player/server/src/core.rs index 60f614c..69215c6 100644 --- a/music-player/server/src/core.rs +++ b/music-player/server/src/core.rs @@ -1,11 +1,8 @@ use crate::{ - audio::TrackInfo, - database::{Database, MemoryIndex, MusicIndex}, - Error, FatalError, + audio::TrackInfo, database::MusicIndex, music_scanner::MusicScanner, Error, FatalError, }; -use flow::{error, fatal, ok, return_error, return_fatal, Flow}; +use flow::{ok, Flow}; use std::{ - path::{Path, PathBuf}, sync::{ mpsc::{channel, Receiver, RecvTimeoutError, Sender}, Arc, @@ -15,12 +12,17 @@ use std::{ time::{Duration, Instant}, }; +fn scan_frequency() -> Duration { + Duration::from_secs(60) +} + pub enum ControlMsg { Exit, } pub enum TrackMsg { - DbUpdate, + UpdateInProgress, + UpdateComplete, } pub enum PlaybackMsg { @@ -32,36 +34,58 @@ pub enum PlaybackMsg { pub struct Core { db: Arc, - track_handle: JoinHandle<()>, - track_rx: Receiver, - playback_handle: JoinHandle<()>, - playback_rx: Receiver, + _track_handle: JoinHandle<()>, + _track_rx: Receiver, + _playback_handle: JoinHandle<()>, + _playback_rx: Receiver, control_tx: Sender, } impl Core { - pub fn new(db: Arc) -> Flow { + pub fn new( + db: Arc, + scanner: impl MusicScanner + 'static, + ) -> Flow { let (control_tx, control_rx) = channel::(); - let (track_handle, track_rx) = { + let (_track_handle, _track_rx) = { let (track_tx, track_rx) = channel(); let db = db.clone(); - let track_handle = thread::spawn(move || {}); + let track_handle = thread::spawn(move || { + println!("tracker thread started"); + let mut next_scan = Instant::now(); + loop { + if Instant::now() >= next_scan { + let _ = track_tx.send(TrackMsg::UpdateInProgress); + for track in scanner.scan() { + println!("scanning {:?}", track); + db.add_track(track); + } + let _ = track_tx.send(TrackMsg::UpdateComplete); + next_scan = Instant::now() + scan_frequency(); + } + match control_rx.recv_timeout(Duration::from_millis(1000)) { + Ok(ControlMsg::Exit) => return, + Err(RecvTimeoutError::Timeout) => (), + Err(RecvTimeoutError::Disconnected) => return, + } + } + }); (track_handle, track_rx) }; - let (playback_handle, playback_rx) = { - let (playback_tx, playback_rx) = channel(); + let (_playback_handle, _playback_rx) = { + let (_playback_tx, playback_rx) = channel(); let playback_handle = thread::spawn(move || {}); (playback_handle, playback_rx) }; ok(Core { db, - track_handle, - track_rx, - playback_handle, - playback_rx, + _track_handle, + _track_rx, + _playback_handle, + _playback_rx, control_tx, }) } @@ -82,7 +106,7 @@ impl Core { #[cfg(test)] mod test { use super::*; - use crate::audio::{TrackId, TrackInfo}; + use crate::{audio::TrackId, database::MemoryIndex, music_scanner::factories::MockScanner}; use std::collections::HashSet; fn with_example_index(f: F) @@ -90,43 +114,12 @@ mod test { F: Fn(Core), { let index = MemoryIndex::new(); - index.add_track(TrackInfo { - id: TrackId::from("/home/savanni/Track 1.mp3".to_owned()), - track_number: None, - name: None, - album: None, - artist: None, - }); - index.add_track(TrackInfo { - id: TrackId::from("/home/savanni/Track 2.mp3".to_owned()), - track_number: None, - name: None, - album: None, - artist: None, - }); - index.add_track(TrackInfo { - id: TrackId::from("/home/savanni/Track 3.mp3".to_owned()), - track_number: None, - name: None, - album: None, - artist: None, - }); - index.add_track(TrackInfo { - id: TrackId::from("/home/savanni/Track 4.mp3".to_owned()), - track_number: None, - name: None, - album: None, - artist: None, - }); - index.add_track(TrackInfo { - id: TrackId::from("/home/savanni/Track 5.mp3".to_owned()), - track_number: None, - name: None, - album: None, - artist: None, - }); - match Core::new(Arc::new(index)) { - Flow::Ok(core) => f(core), + let scanner = MockScanner::new(); + match Core::new(Arc::new(index), scanner) { + Flow::Ok(core) => { + thread::sleep(Duration::from_millis(10)); + f(core) + } Flow::Err(error) => panic!("{:?}", error), Flow::Fatal(error) => panic!("{:?}", error), } diff --git a/music-player/server/src/music_scanner.rs b/music-player/server/src/music_scanner.rs index d22480e..57d071e 100644 --- a/music-player/server/src/music_scanner.rs +++ b/music-player/server/src/music_scanner.rs @@ -1,4 +1,5 @@ use crate::{ + audio::TrackInfo, core::{ControlMsg, TrackMsg}, database::MusicIndex, FatalError, @@ -14,10 +15,6 @@ use std::{ }; use thiserror::Error; -fn scan_frequency() -> Duration { - Duration::from_secs(60) -} - #[derive(Debug, Error)] pub enum ScannerError { #[error("Cannot scan {0}")] @@ -32,50 +29,26 @@ impl From for ScannerError { } } -pub trait MusicScanner { - fn scan(&self); +pub trait MusicScanner: Send { + fn scan<'a>(&'a self) -> Box + 'a>; } +/* pub struct FileScanner { db: Arc, - control_rx: Receiver, tracker_tx: Sender, - next_scan: Instant, music_directories: Vec, } impl FileScanner { - fn new( - db: Arc, - roots: Vec, - control_rx: Receiver, - tracker_tx: Sender, - ) -> Self { + fn new(db: Arc, roots: Vec, tracker_tx: Sender) -> Self { Self { db, - control_rx, tracker_tx, - next_scan: Instant::now(), music_directories: roots, } } - fn scan(&mut self) { - loop { - match self.control_rx.recv_timeout(Duration::from_millis(100)) { - Ok(ControlMsg::Exit) => return, - Err(RecvTimeoutError::Timeout) => (), - Err(RecvTimeoutError::Disconnected) => return, - } - if Instant::now() >= self.next_scan { - for root in self.music_directories.iter() { - self.scan_dir(vec![root.clone()]); - } - self.next_scan = Instant::now() + scan_frequency(); - } - } - } - fn scan_dir(&self, mut paths: Vec) -> Flow<(), FatalError, ScannerError> { while let Some(dir) = paths.pop() { println!("scanning {:?}", dir); @@ -112,3 +85,85 @@ impl FileScanner { ok(()) } } + +impl MusicScanner for FileScanner { + fn scan<'a>(&'a self) -> Box<&'a dyn Iterator> { + unimplemented!() + /* + loop { + match self.control_rx.recv_timeout(Duration::from_millis(100)) { + Ok(ControlMsg::Exit) => return, + Err(RecvTimeoutError::Timeout) => (), + Err(RecvTimeoutError::Disconnected) => return, + } + if Instant::now() >= self.next_scan { + for root in self.music_directories.iter() { + self.scan_dir(vec![root.clone()]); + } + self.next_scan = Instant::now() + scan_frequency(); + } + } + */ + } +} +*/ + +#[cfg(test)] +pub mod factories { + use super::*; + use crate::audio::TrackId; + + pub struct MockScanner { + data: Vec, + } + + impl MockScanner { + pub fn new() -> Self { + Self { + data: vec![ + TrackInfo { + id: TrackId::from("/home/savanni/Track 1.mp3".to_owned()), + track_number: Some(1), + name: Some("Track 1".to_owned()), + album: Some("Savanni's Demo".to_owned()), + artist: Some("Savanni".to_owned()), + }, + TrackInfo { + id: TrackId::from("/home/savanni/Track 2.mp3".to_owned()), + track_number: Some(2), + name: Some("Track 2".to_owned()), + album: Some("Savanni's Demo".to_owned()), + artist: Some("Savanni".to_owned()), + }, + TrackInfo { + id: TrackId::from("/home/savanni/Track 3.mp3".to_owned()), + track_number: Some(3), + name: Some("Track 3".to_owned()), + album: Some("Savanni's Demo".to_owned()), + artist: Some("Savanni".to_owned()), + }, + TrackInfo { + id: TrackId::from("/home/savanni/Track 4.mp3".to_owned()), + track_number: Some(4), + name: Some("Track 4".to_owned()), + album: Some("Savanni's Demo".to_owned()), + artist: Some("Savanni".to_owned()), + }, + TrackInfo { + id: TrackId::from("/home/savanni/Track 5.mp3".to_owned()), + track_number: Some(5), + name: Some("Track 5".to_owned()), + album: Some("Savanni's Demo".to_owned()), + artist: Some("Savanni".to_owned()), + }, + ], + } + } + } + + impl MusicScanner for MockScanner { + fn scan<'a>(&'a self) -> Box + 'a> { + Box::new(self.data.iter().map(|t| t.clone())) + } + } +}