Render a file list from the filesystem #24

Merged
savanni merged 9 commits from track-list into main 2023-02-26 03:17:01 +00:00
3 changed files with 139 additions and 89 deletions
Showing only changes of commit 95853809b5 - Show all commits

View File

@ -50,6 +50,7 @@ fn tracks() -> Vec<Track> {
#[tokio::main] #[tokio::main]
pub async fn main() { pub async fn main() {
/*
match Core::new(Arc::new(MemoryIndex::new())) { match Core::new(Arc::new(MemoryIndex::new())) {
Flow::Ok(core) => { Flow::Ok(core) => {
let mut buf = String::new(); let mut buf = String::new();
@ -59,6 +60,7 @@ pub async fn main() {
Flow::Err(err) => println!("non-fatal error: {:?}", err), Flow::Err(err) => println!("non-fatal error: {:?}", err),
Flow::Fatal(err) => println!("fatal error: {:?}", err), Flow::Fatal(err) => println!("fatal error: {:?}", err),
} }
*/
/* /*
let connection = Connection::new_session().expect("to connect to dbus"); let connection = Connection::new_session().expect("to connect to dbus");

View File

@ -1,11 +1,8 @@
use crate::{ use crate::{
audio::TrackInfo, audio::TrackInfo, database::MusicIndex, music_scanner::MusicScanner, Error, FatalError,
database::{Database, MemoryIndex, MusicIndex},
Error, FatalError,
}; };
use flow::{error, fatal, ok, return_error, return_fatal, Flow}; use flow::{ok, Flow};
use std::{ use std::{
path::{Path, PathBuf},
sync::{ sync::{
mpsc::{channel, Receiver, RecvTimeoutError, Sender}, mpsc::{channel, Receiver, RecvTimeoutError, Sender},
Arc, Arc,
@ -15,12 +12,17 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
fn scan_frequency() -> Duration {
Duration::from_secs(60)
}
pub enum ControlMsg { pub enum ControlMsg {
Exit, Exit,
} }
pub enum TrackMsg { pub enum TrackMsg {
DbUpdate, UpdateInProgress,
UpdateComplete,
} }
pub enum PlaybackMsg { pub enum PlaybackMsg {
@ -32,36 +34,58 @@ pub enum PlaybackMsg {
pub struct Core { pub struct Core {
db: Arc<dyn MusicIndex>, db: Arc<dyn MusicIndex>,
track_handle: JoinHandle<()>, _track_handle: JoinHandle<()>,
track_rx: Receiver<TrackMsg>, _track_rx: Receiver<TrackMsg>,
playback_handle: JoinHandle<()>, _playback_handle: JoinHandle<()>,
playback_rx: Receiver<PlaybackMsg>, _playback_rx: Receiver<PlaybackMsg>,
control_tx: Sender<ControlMsg>, control_tx: Sender<ControlMsg>,
} }
impl Core { impl Core {
pub fn new(db: Arc<dyn MusicIndex>) -> Flow<Core, FatalError, Error> { pub fn new(
db: Arc<dyn MusicIndex>,
scanner: impl MusicScanner + 'static,
) -> Flow<Core, FatalError, Error> {
let (control_tx, control_rx) = channel::<ControlMsg>(); let (control_tx, control_rx) = channel::<ControlMsg>();
let (track_handle, track_rx) = { let (_track_handle, _track_rx) = {
let (track_tx, track_rx) = channel(); let (track_tx, track_rx) = channel();
let db = db.clone(); 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) (track_handle, track_rx)
}; };
let (playback_handle, playback_rx) = { let (_playback_handle, _playback_rx) = {
let (playback_tx, playback_rx) = channel(); let (_playback_tx, playback_rx) = channel();
let playback_handle = thread::spawn(move || {}); let playback_handle = thread::spawn(move || {});
(playback_handle, playback_rx) (playback_handle, playback_rx)
}; };
ok(Core { ok(Core {
db, db,
track_handle, _track_handle,
track_rx, _track_rx,
playback_handle, _playback_handle,
playback_rx, _playback_rx,
control_tx, control_tx,
}) })
} }
@ -82,7 +106,7 @@ impl Core {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::audio::{TrackId, TrackInfo}; use crate::{audio::TrackId, database::MemoryIndex, music_scanner::factories::MockScanner};
use std::collections::HashSet; use std::collections::HashSet;
fn with_example_index<F>(f: F) fn with_example_index<F>(f: F)
@ -90,43 +114,12 @@ mod test {
F: Fn(Core), F: Fn(Core),
{ {
let index = MemoryIndex::new(); let index = MemoryIndex::new();
index.add_track(TrackInfo { let scanner = MockScanner::new();
id: TrackId::from("/home/savanni/Track 1.mp3".to_owned()), match Core::new(Arc::new(index), scanner) {
track_number: None, Flow::Ok(core) => {
name: None, thread::sleep(Duration::from_millis(10));
album: None, f(core)
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),
Flow::Err(error) => panic!("{:?}", error), Flow::Err(error) => panic!("{:?}", error),
Flow::Fatal(error) => panic!("{:?}", error), Flow::Fatal(error) => panic!("{:?}", error),
} }

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
audio::TrackInfo,
core::{ControlMsg, TrackMsg}, core::{ControlMsg, TrackMsg},
database::MusicIndex, database::MusicIndex,
FatalError, FatalError,
@ -14,10 +15,6 @@ use std::{
}; };
use thiserror::Error; use thiserror::Error;
fn scan_frequency() -> Duration {
Duration::from_secs(60)
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ScannerError { pub enum ScannerError {
#[error("Cannot scan {0}")] #[error("Cannot scan {0}")]
@ -32,50 +29,26 @@ impl From<std::io::Error> for ScannerError {
} }
} }
pub trait MusicScanner { pub trait MusicScanner: Send {
fn scan(&self); fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = TrackInfo> + 'a>;
} }
/*
pub struct FileScanner { pub struct FileScanner {
db: Arc<dyn MusicIndex>, db: Arc<dyn MusicIndex>,
control_rx: Receiver<ControlMsg>,
tracker_tx: Sender<TrackMsg>, tracker_tx: Sender<TrackMsg>,
next_scan: Instant,
music_directories: Vec<PathBuf>, music_directories: Vec<PathBuf>,
} }
impl FileScanner { impl FileScanner {
fn new( fn new(db: Arc<dyn MusicIndex>, roots: Vec<PathBuf>, tracker_tx: Sender<TrackMsg>) -> Self {
db: Arc<dyn MusicIndex>,
roots: Vec<PathBuf>,
control_rx: Receiver<ControlMsg>,
tracker_tx: Sender<TrackMsg>,
) -> Self {
Self { Self {
db, db,
control_rx,
tracker_tx, tracker_tx,
next_scan: Instant::now(),
music_directories: roots, 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<PathBuf>) -> Flow<(), FatalError, ScannerError> { fn scan_dir(&self, mut paths: Vec<PathBuf>) -> Flow<(), FatalError, ScannerError> {
while let Some(dir) = paths.pop() { while let Some(dir) = paths.pop() {
println!("scanning {:?}", dir); println!("scanning {:?}", dir);
@ -112,3 +85,85 @@ impl FileScanner {
ok(()) ok(())
} }
} }
impl MusicScanner for FileScanner {
fn scan<'a>(&'a self) -> Box<&'a dyn Iterator<Item = TrackInfo>> {
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<TrackInfo>,
}
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<dyn Iterator<Item = TrackInfo> + 'a> {
Box::new(self.data.iter().map(|t| t.clone()))
}
}
}