use crate::{ database::{Database, MemoryIndex, MusicIndex}, Error, FatalError, }; use flow::{error, fatal, ok, return_error, return_fatal, Flow}; use std::{ path::{Path, PathBuf}, sync::{ mpsc::{channel, Receiver, RecvTimeoutError, Sender}, Arc, }, thread, thread::JoinHandle, time::{Duration, Instant}, }; #[derive(Debug, Error)] pub enum ScannerError { #[error("Cannot scan {0}")] CannotScan(PathBuf), #[error("IO error {0}")] IO(std::io::Error), } impl From for ScannerError { fn from(err: std::io::Error) -> Self { Self::IO(err) } } pub enum ControlMsg { Exit, } pub enum TrackMsg { DbUpdate, } 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, } fn scan_frequency() -> Duration { Duration::from_secs(60) } 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 { 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); return_error!(self.scan_dir_(&mut paths, dir)); } ok(()) } fn scan_dir_( &self, paths: &mut Vec, dir: PathBuf, ) -> Flow<(), FatalError, ScannerError> { let dir_iter = return_error!(Flow::from(dir.read_dir().map_err(ScannerError::from))); for entry in dir_iter { match entry { Ok(entry) if entry.path().is_dir() => paths.push(entry.path()), Ok(entry) => { let _ = return_fatal!(self.scan_file(entry.path()).or_else(|err| { println!("scan_file failed: {:?}", err); ok::<(), FatalError, ScannerError>(()) })); () } Err(err) => { println!("scan_dir could not read path: ({:?})", err); } } } ok(()) } fn scan_file(&self, path: PathBuf) -> Flow<(), FatalError, ScannerError> { ok(()) } } impl Core { pub fn new(db: Arc) -> Flow { let (control_tx, control_rx) = channel::(); let (track_handle, track_rx) = { let (track_tx, track_rx) = channel(); let db = db.clone(); let track_handle = thread::spawn(move || { FileScanner::new( db, vec![PathBuf::from("/home/savanni/Music/")], control_rx, track_tx, ) .scan(); }); (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 exit(&self) { let _ = self.control_tx.send(ControlMsg::Exit); /* self.track_handle.join(); self.playback_handle.join(); */ } } #[cfg(test)] mod test {}