2023-02-11 17:59:15 +00:00
|
|
|
use crate::{
|
2023-02-26 03:17:00 +00:00
|
|
|
audio::TrackInfo, database::MusicIndex, music_scanner::MusicScanner, Error, FatalError,
|
2023-02-11 17:59:15 +00:00
|
|
|
};
|
2023-02-26 03:17:00 +00:00
|
|
|
use flow::{ok, Flow};
|
2023-02-11 17:59:15 +00:00
|
|
|
use std::{
|
|
|
|
sync::{
|
|
|
|
mpsc::{channel, Receiver, RecvTimeoutError, Sender},
|
|
|
|
Arc,
|
|
|
|
},
|
|
|
|
thread,
|
|
|
|
thread::JoinHandle,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
|
|
|
|
2023-02-26 03:17:00 +00:00
|
|
|
fn scan_frequency() -> Duration {
|
|
|
|
Duration::from_secs(60)
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum ControlMsg {
|
|
|
|
Exit,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum TrackMsg {
|
2023-02-26 03:17:00 +00:00
|
|
|
UpdateInProgress,
|
|
|
|
UpdateComplete,
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum PlaybackMsg {
|
|
|
|
PositionUpdate,
|
|
|
|
Playing,
|
|
|
|
Pausing,
|
|
|
|
Stopping,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Core {
|
|
|
|
db: Arc<dyn MusicIndex>,
|
2023-02-26 03:17:00 +00:00
|
|
|
_track_handle: JoinHandle<()>,
|
|
|
|
_track_rx: Receiver<TrackMsg>,
|
|
|
|
_playback_handle: JoinHandle<()>,
|
|
|
|
_playback_rx: Receiver<PlaybackMsg>,
|
2023-02-11 17:59:15 +00:00
|
|
|
control_tx: Sender<ControlMsg>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Core {
|
2023-02-26 03:17:00 +00:00
|
|
|
pub fn new(
|
|
|
|
db: Arc<dyn MusicIndex>,
|
|
|
|
scanner: impl MusicScanner + 'static,
|
|
|
|
) -> Flow<Core, FatalError, Error> {
|
2023-02-11 17:59:15 +00:00
|
|
|
let (control_tx, control_rx) = channel::<ControlMsg>();
|
2023-02-26 03:17:00 +00:00
|
|
|
let db = db;
|
2023-02-11 17:59:15 +00:00
|
|
|
|
2023-02-26 03:17:00 +00:00
|
|
|
let (_track_handle, _track_rx) = {
|
2023-02-11 17:59:15 +00:00
|
|
|
let (track_tx, track_rx) = channel();
|
|
|
|
let db = db.clone();
|
|
|
|
let track_handle = thread::spawn(move || {
|
2023-02-26 03:17:00 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
2023-02-11 17:59:15 +00:00
|
|
|
});
|
|
|
|
(track_handle, track_rx)
|
|
|
|
};
|
|
|
|
|
2023-02-26 03:17:00 +00:00
|
|
|
let (_playback_handle, _playback_rx) = {
|
|
|
|
let (_playback_tx, playback_rx) = channel();
|
2023-02-11 17:59:15 +00:00
|
|
|
let playback_handle = thread::spawn(move || {});
|
|
|
|
(playback_handle, playback_rx)
|
|
|
|
};
|
|
|
|
|
|
|
|
ok(Core {
|
|
|
|
db,
|
2023-02-26 03:17:00 +00:00
|
|
|
_track_handle,
|
|
|
|
_track_rx,
|
|
|
|
_playback_handle,
|
|
|
|
_playback_rx,
|
2023-02-11 17:59:15 +00:00
|
|
|
control_tx,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-26 03:17:00 +00:00
|
|
|
pub fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, Error> {
|
|
|
|
self.db.list_tracks().map_err(Error::DatabaseError)
|
|
|
|
}
|
|
|
|
|
2023-02-11 17:59:15 +00:00
|
|
|
pub fn exit(&self) {
|
|
|
|
let _ = self.control_tx.send(ControlMsg::Exit);
|
|
|
|
/*
|
|
|
|
self.track_handle.join();
|
|
|
|
self.playback_handle.join();
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2023-02-26 03:17:00 +00:00
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
use crate::{audio::TrackId, database::MemoryIndex, music_scanner::factories::MockScanner};
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
fn with_example_index<F>(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::<HashSet<TrackId>>();
|
|
|
|
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),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|