use crate::{ audio::TrackInfo, database::MusicIndex, music_scanner::MusicScanner, Error, FatalError, }; use flow::{ok, Flow}; use std::{ sync::{ mpsc::{channel, Receiver, RecvTimeoutError, Sender}, Arc, }, thread, thread::JoinHandle, time::{Duration, Instant}, }; fn scan_frequency() -> Duration { Duration::from_secs(60) } pub enum ControlMsg { Exit, } pub enum TrackMsg { UpdateInProgress, UpdateComplete, } pub enum PlaybackMsg { PositionUpdate, Playing, Pausing, Stopping, } pub struct Core { db: Arc, _track_handle: JoinHandle<()>, _track_rx: Receiver, _playback_handle: JoinHandle<()>, _playback_rx: Receiver, control_tx: Sender, } impl Core { pub fn new( db: Arc, scanner: impl MusicScanner + 'static, ) -> Flow { let (control_tx, control_rx) = channel::(); let db = db; let (_track_handle, _track_rx) = { let (track_tx, track_rx) = channel(); let db = db.clone(); let track_handle = thread::spawn(move || { let mut next_scan = Instant::now(); loop { if Instant::now() >= next_scan { let _ = track_tx.send(TrackMsg::UpdateInProgress); for track in scanner.scan() { match track { Ok(track) => db.add_track(track), Err(_) => ok(()), }; } 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 = thread::spawn(move || {}); (playback_handle, playback_rx) }; ok(Core { db, _track_handle, _track_rx, _playback_handle, _playback_rx, control_tx, }) } pub fn list_tracks<'a>(&'a self) -> Flow, FatalError, Error> { self.db.list_tracks().map_err(Error::DatabaseError) } pub fn exit(&self) { let _ = self.control_tx.send(ControlMsg::Exit); /* self.track_handle.join(); self.playback_handle.join(); */ } } #[cfg(test)] mod test { use super::*; use crate::{audio::TrackId, database::MemoryIndex, music_scanner::factories::MockScanner}; use std::collections::HashSet; fn with_example_index(f: F) where F: Fn(Core), { let index = MemoryIndex::new(); 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), } } #[test] fn it_lists_tracks() { with_example_index(|core| match core.list_tracks() { Flow::Ok(tracks) => { let track_ids = tracks .iter() .map(|t| t.id.clone()) .collect::>(); assert_eq!(track_ids.len(), 5); assert_eq!( track_ids, HashSet::from([ TrackId::from("/home/savanni/Track 1.mp3".to_owned()), TrackId::from("/home/savanni/Track 2.mp3".to_owned()), TrackId::from("/home/savanni/Track 3.mp3".to_owned()), TrackId::from("/home/savanni/Track 4.mp3".to_owned()), TrackId::from("/home/savanni/Track 5.mp3".to_owned()), ]) ); } Flow::Fatal(err) => panic!("fatal error: {:?}", err), Flow::Err(err) => panic!("error: {:?}", err), }) } }