use crate::{ database::MusicIndex, media::{TrackId, TrackInfo}, playback::{Playback, PlaybackControl, PlaybackStatus}, scanner::MusicScanner, Error, FatalError, }; use flow::{ok, return_error, Flow}; use gstreamer::{format::ClockTime, prelude::*, MessageView}; use std::{ sync::{Arc, Mutex, RwLock}, thread, time::{Duration, Instant}, }; use tokio::{ sync::mpsc::{channel, Receiver, Sender}, task::AbortHandle, }; fn scan_frequency() -> Duration { Duration::from_secs(60) } #[derive(Clone, Debug)] pub enum ControlMsg { PlayTrack(TrackId), Exit, } #[derive(Clone, Debug)] pub enum TrackMsg { UpdateInProgress, UpdateComplete, } #[derive(Clone, Debug)] pub enum PlaybackMsg { PositionUpdate, Playing, Pausing, Stopping, } pub struct Core { db: Arc, playback_controller: Option, next_scan: Arc>, scanner_handle: thread::JoinHandle<()>, } impl Core { pub fn new( db: Arc, scanner: Arc, ) -> Result { let db = db; let next_scan = Arc::new(RwLock::new(Instant::now())); let scanner_handle = { let db = db.clone(); let next_scan = next_scan.clone(); thread::spawn(move || scan_loop(scanner, db, next_scan)) }; Ok(Core { db, playback_controller: None, next_scan, scanner_handle, }) } pub fn list_tracks<'a>(&'a self) -> Flow, FatalError, Error> { self.db.list_tracks().map_err(Error::DatabaseError) } pub fn play_track<'a>(&'a mut self, id: TrackId) -> Result<(), Error> { self.stop_playback()?; self.playback_controller = Some(Playback::new(id)?); Ok(()) } pub fn stop_playback<'a>(&'a mut self) -> Result<(), Error> { match self.playback_controller { Some(ref controller) => controller.stop()?, None => (), } self.playback_controller = None; Ok(()) } } pub fn scan_loop( scanner: Arc, db: Arc, next_scan: Arc>, ) { loop { if Instant::now() >= *next_scan.read().unwrap() { let scan_start = Instant::now(); let mut counter = 0; for track in scanner.scan() { counter += 1; match track { Ok(track) => db.add_track(track), Err(_) => ok(()), }; } *next_scan.write().unwrap() = Instant::now() + scan_frequency(); println!( "scanned {} files in {:?}", counter, Instant::now() - scan_start ); } thread::sleep(Duration::from_secs(1)); } } #[cfg(test)] mod test { use super::*; use crate::{database::MemoryIndex, media::TrackId, 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), Arc::new(scanner)) { Ok(core) => { thread::sleep(Duration::from_millis(10)); f(core) } Err(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), }) } }