Render a file list from the filesystem #24
|
@ -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");
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue