mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-20 07:52:53 -05:00
Compare commits
3 commits
32c0cf3105
...
54704260ba
Author | SHA1 | Date | |
---|---|---|---|
|
54704260ba | ||
407a1b16bd | |||
42ff5b20a8 |
6 changed files with 103 additions and 73 deletions
|
@ -6,6 +6,7 @@ use crossbeam_channel;
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::thread::spawn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crossbeam_channel::unbounded;
|
use crossbeam_channel::unbounded;
|
||||||
|
@ -13,18 +14,18 @@ use std::error::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::config::ConfigError;
|
use crate::config::config::ConfigError;
|
||||||
use crate::music_player::player::{Player, PlayerError};
|
use crate::music_player::player::{Player, PlayerCommand, PlayerError};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
|
config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::queue::QueueError;
|
use super::queue::QueueError;
|
||||||
|
|
||||||
pub struct Controller<P: Player> {
|
pub struct Controller<P: Player + Send + Sync> {
|
||||||
pub queue: Queue,
|
pub queue: Queue,
|
||||||
pub config: Arc<RwLock<Config>>,
|
pub config: Arc<RwLock<Config>>,
|
||||||
pub library: MusicLibrary,
|
pub library: MusicLibrary,
|
||||||
pub player: Box<P>,
|
pub player: Arc<RwLock<Box<P>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -69,7 +70,7 @@ impl<T: Send, U: Send> MailMan<T, U> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
impl<P: Player> Controller<P> {
|
impl<P: Player + Send + Sync> Controller<P> {
|
||||||
pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
|
pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
|
||||||
where
|
where
|
||||||
std::path::PathBuf: std::convert::From<T>,
|
std::path::PathBuf: std::convert::From<T>,
|
||||||
|
@ -83,12 +84,28 @@ impl<P: Player> Controller<P> {
|
||||||
let config_ = Arc::new(RwLock::from(config));
|
let config_ = Arc::new(RwLock::from(config));
|
||||||
let library = MusicLibrary::init(config_.clone(), uuid)?;
|
let library = MusicLibrary::init(config_.clone(), uuid)?;
|
||||||
|
|
||||||
Ok(Controller {
|
let controller = Controller {
|
||||||
queue: Queue::default(),
|
queue: Queue::default(),
|
||||||
config: config_.clone(),
|
config: config_.clone(),
|
||||||
library,
|
library,
|
||||||
player: Box::new(P::new()?),
|
player: Arc::new(RwLock::new(Box::new(P::new()?))),
|
||||||
})
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let player = controller.player.clone();
|
||||||
|
let controler_thread = spawn(move || {
|
||||||
|
match player.read().unwrap().message_channel().recv().unwrap() {
|
||||||
|
PlayerCommand::AboutToFinish => {},
|
||||||
|
PlayerCommand::EndOfStream => {
|
||||||
|
|
||||||
|
player.write().unwrap().enqueue_next(todo!());
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Ok(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) {
|
pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub struct Queue {
|
||||||
pub items: Vec<QueueItem>,
|
pub items: Vec<QueueItem>,
|
||||||
pub played: Vec<QueueItem>,
|
pub played: Vec<QueueItem>,
|
||||||
pub loop_: bool,
|
pub loop_: bool,
|
||||||
pub shuffle: bool,
|
pub shuffle: Option<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: HAndle the First QueueState[looping] and shuffle
|
// TODO: HAndle the First QueueState[looping] and shuffle
|
||||||
|
|
|
@ -94,6 +94,7 @@ impl GStreamer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the playback tracker knows the stuff is stopped
|
// Make sure the playback tracker knows the stuff is stopped
|
||||||
|
println!("Beginning switch");
|
||||||
self.playback_tx.send(PlaybackInfo::Switching).unwrap();
|
self.playback_tx.send(PlaybackInfo::Switching).unwrap();
|
||||||
|
|
||||||
let uri = self.playbin.read().unwrap().property_value("current-uri");
|
let uri = self.playbin.read().unwrap().property_value("current-uri");
|
||||||
|
@ -133,12 +134,11 @@ impl GStreamer {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_property("uri", source.as_uri());
|
.set_property("uri", source.as_uri());
|
||||||
|
|
||||||
self.play().unwrap();
|
if self.state() != PlayerState::Playing {
|
||||||
|
self.play().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
while uri.get::<&str>().unwrap_or("")
|
while self.raw_duration().is_none() {
|
||||||
== self.property("current-uri").get::<&str>().unwrap_or("")
|
|
||||||
|| self.position().is_none()
|
|
||||||
{
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +213,11 @@ impl GStreamer {
|
||||||
fn property(&self, property: &str) -> glib::Value {
|
fn property(&self, property: &str) -> glib::Value {
|
||||||
self.playbin().unwrap().property_value(property)
|
self.playbin().unwrap().property_value(property)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ready(&mut self) -> Result<(), PlayerError> {
|
||||||
|
self.set_state(gst::State::Ready)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player for GStreamer {
|
impl Player for GStreamer {
|
||||||
|
@ -249,7 +254,7 @@ impl Player for GStreamer {
|
||||||
.ok_or(PlayerError::Build)?;
|
.ok_or(PlayerError::Build)?;
|
||||||
|
|
||||||
playbin.write().unwrap().set_property_from_value("flags", &flags);
|
playbin.write().unwrap().set_property_from_value("flags", &flags);
|
||||||
playbin.write().unwrap().set_property("instant-uri", true);
|
//playbin.write().unwrap().set_property("instant-uri", true);
|
||||||
|
|
||||||
let position = Arc::new(RwLock::new(None));
|
let position = Arc::new(RwLock::new(None));
|
||||||
|
|
||||||
|
@ -258,8 +263,7 @@ impl Player for GStreamer {
|
||||||
let (status_tx, status_rx) = unbounded::<PlaybackInfo>();
|
let (status_tx, status_rx) = unbounded::<PlaybackInfo>();
|
||||||
let position_update = Arc::clone(&position);
|
let position_update = Arc::clone(&position);
|
||||||
|
|
||||||
let _playback_monitor =
|
std::thread::spawn(|| playback_monitor(playbin_arc, status_rx, playback_tx, position_update));
|
||||||
std::thread::spawn(|| playback_monitor(playbin_arc, status_rx, playback_tx, position_update));
|
|
||||||
|
|
||||||
// Set up the thread to monitor bus messages
|
// Set up the thread to monitor bus messages
|
||||||
let playbin_bus_ctrl = Arc::clone(&playbin);
|
let playbin_bus_ctrl = Arc::clone(&playbin);
|
||||||
|
@ -330,62 +334,46 @@ impl Player for GStreamer {
|
||||||
&self.source
|
&self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new track to be played. This method should be called at the
|
|
||||||
/// beginning to start playback of something, and once the [PlayerCommand]
|
|
||||||
/// indicates the track is about to finish to enqueue gaplessly.
|
|
||||||
fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
|
fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
|
||||||
self.set_source(next_track)
|
self.set_source(next_track)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the playback volume, accepts a float from 0 to 1
|
|
||||||
fn set_volume(&mut self, volume: f64) {
|
fn set_volume(&mut self, volume: f64) {
|
||||||
self.volume = volume.clamp(0.0, 1.0);
|
self.volume = volume.clamp(0.0, 1.0);
|
||||||
self.set_gstreamer_volume(self.volume);
|
self.set_gstreamer_volume(self.volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current volume level, a float from 0 to 1
|
fn volume(&self) -> f64 {
|
||||||
fn volume(&mut self) -> f64 {
|
|
||||||
self.volume
|
self.volume
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ready(&mut self) -> Result<(), PlayerError> {
|
|
||||||
self.set_state(gst::State::Ready)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If the player is paused or stopped, starts playback
|
|
||||||
fn play(&mut self) -> Result<(), PlayerError> {
|
fn play(&mut self) -> Result<(), PlayerError> {
|
||||||
|
if self.state() == PlayerState::Playing {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
*self.paused.write().unwrap() = false;
|
*self.paused.write().unwrap() = false;
|
||||||
self.set_state(gst::State::Playing)?;
|
self.set_state(gst::State::Playing)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pause, if playing
|
|
||||||
fn pause(&mut self) -> Result<(), PlayerError> {
|
fn pause(&mut self) -> Result<(), PlayerError> {
|
||||||
|
if self.state() == PlayerState::Paused || *self.paused.read().unwrap() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
*self.paused.write().unwrap() = true;
|
*self.paused.write().unwrap() = true;
|
||||||
self.set_state(gst::State::Paused)?;
|
self.set_state(gst::State::Paused)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resume from being paused
|
fn is_paused(&self) -> bool {
|
||||||
fn resume(&mut self) -> Result<(), PlayerError> {
|
|
||||||
*self.paused.write().unwrap() = false;
|
|
||||||
self.set_state(gst::State::Playing)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if playback is paused
|
|
||||||
fn is_paused(&mut self) -> bool {
|
|
||||||
self.playbin().unwrap().current_state() == gst::State::Paused
|
self.playbin().unwrap().current_state() == gst::State::Paused
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current playback position of the player
|
fn position(&self) -> Option<Duration> {
|
||||||
fn position(&mut self) -> Option<Duration> {
|
|
||||||
*self.position.read().unwrap()
|
*self.position.read().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the duration of the currently playing track
|
fn duration(&self) -> Option<Duration> {
|
||||||
fn duration(&mut self) -> Option<Duration> {
|
|
||||||
if self.end.is_some() && self.start.is_some() {
|
if self.end.is_some() && self.start.is_some() {
|
||||||
Some(self.end.unwrap() - self.start.unwrap())
|
Some(self.end.unwrap() - self.start.unwrap())
|
||||||
} else {
|
} else {
|
||||||
|
@ -393,7 +381,6 @@ impl Player for GStreamer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seek relative to the current position
|
|
||||||
fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError> {
|
fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError> {
|
||||||
let time_pos = match *self.position.read().unwrap() {
|
let time_pos = match *self.position.read().unwrap() {
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
|
@ -405,7 +392,6 @@ impl Player for GStreamer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seek absolutely
|
|
||||||
fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError> {
|
fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError> {
|
||||||
let start = if self.start.is_none() {
|
let start = if self.start.is_none() {
|
||||||
return Err(PlayerError::Seek("No START time".into()));
|
return Err(PlayerError::Seek("No START time".into()));
|
||||||
|
@ -433,7 +419,6 @@ impl Player for GStreamer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the playback entirely
|
|
||||||
fn stop(&mut self) -> Result<(), PlayerError> {
|
fn stop(&mut self) -> Result<(), PlayerError> {
|
||||||
self.pause()?;
|
self.pause()?;
|
||||||
self.ready()?;
|
self.ready()?;
|
||||||
|
@ -448,7 +433,6 @@ impl Player for GStreamer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the player message channel
|
|
||||||
fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand> {
|
fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand> {
|
||||||
&self.message_rx
|
&self.message_rx
|
||||||
}
|
}
|
||||||
|
@ -477,7 +461,7 @@ fn playback_monitor(
|
||||||
let mut sent_atf = false;
|
let mut sent_atf = false;
|
||||||
loop {
|
loop {
|
||||||
// Check for new messages to decide how to proceed
|
// Check for new messages to decide how to proceed
|
||||||
if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(10)) {
|
if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(50)) {
|
||||||
stats = result
|
stats = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,8 +474,9 @@ fn playback_monitor(
|
||||||
match stats {
|
match stats {
|
||||||
PlaybackInfo::Playing{start, end} if pos_temp.is_some() => {
|
PlaybackInfo::Playing{start, end} if pos_temp.is_some() => {
|
||||||
// Check if the current playback position is close to the end
|
// Check if the current playback position is close to the end
|
||||||
let finish_point = end - Duration::milliseconds(250);
|
let finish_point = end - Duration::milliseconds(2000);
|
||||||
if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
|
if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
|
||||||
|
println!("MONITOR: End of stream");
|
||||||
let _ = playback_tx.try_send(PlayerCommand::EndOfStream);
|
let _ = playback_tx.try_send(PlayerCommand::EndOfStream);
|
||||||
playbin
|
playbin
|
||||||
.write()
|
.write()
|
||||||
|
@ -500,7 +485,9 @@ fn playback_monitor(
|
||||||
.expect("Unable to set the pipeline state");
|
.expect("Unable to set the pipeline state");
|
||||||
sent_atf = false
|
sent_atf = false
|
||||||
} else if pos_temp.unwrap().num_microseconds() >= finish_point.num_microseconds()
|
} else if pos_temp.unwrap().num_microseconds() >= finish_point.num_microseconds()
|
||||||
&& !sent_atf {
|
&& !sent_atf
|
||||||
|
{
|
||||||
|
println!("MONITOR: About to finish");
|
||||||
let _ = playback_tx.try_send(PlayerCommand::AboutToFinish);
|
let _ = playback_tx.try_send(PlayerCommand::AboutToFinish);
|
||||||
sent_atf = true;
|
sent_atf = true;
|
||||||
}
|
}
|
||||||
|
@ -510,6 +497,7 @@ fn playback_monitor(
|
||||||
pos_temp = Some(pos_temp.unwrap() - start)
|
pos_temp = Some(pos_temp.unwrap() - start)
|
||||||
},
|
},
|
||||||
PlaybackInfo::Finished => {
|
PlaybackInfo::Finished => {
|
||||||
|
println!("MONITOR: Shutting down");
|
||||||
*position.write().unwrap() = None;
|
*position.write().unwrap() = None;
|
||||||
break
|
break
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,36 +40,58 @@ pub enum PlayerCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Player {
|
pub trait Player {
|
||||||
/// Create a new player
|
/// Create a new player.
|
||||||
fn new() -> Result<Self, PlayerError> where Self: Sized;
|
fn new() -> Result<Self, PlayerError> where Self: Sized;
|
||||||
|
|
||||||
|
/// Get the currently playing [URI] from the player.
|
||||||
fn source(&self) -> &Option<URI>;
|
fn source(&self) -> &Option<URI>;
|
||||||
|
|
||||||
|
/// Insert a new [`URI`] to be played. This method should be called at the
|
||||||
|
/// beginning to start playback of something, and once the [`PlayerCommand`]
|
||||||
|
/// indicates the track is about to finish to enqueue gaplessly.
|
||||||
|
///
|
||||||
|
/// For backends which do not support gapless playback, `AboutToFinish`
|
||||||
|
/// will not be called, and the next [`URI`] should be enqueued once `Eos`
|
||||||
|
/// occurs.
|
||||||
fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
|
fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
|
||||||
|
|
||||||
|
/// Set the playback volume, accepts a float from `0` to `1`.
|
||||||
|
///
|
||||||
|
/// Values outside the range of `0` to `1` will be capped.
|
||||||
fn set_volume(&mut self, volume: f64);
|
fn set_volume(&mut self, volume: f64);
|
||||||
|
|
||||||
fn volume(&mut self) -> f64;
|
/// Returns the current volume level, a float from `0` to `1`.
|
||||||
|
fn volume(&self) -> f64;
|
||||||
fn ready(&mut self) -> Result<(), PlayerError>;
|
|
||||||
|
|
||||||
|
/// If the player is paused or stopped, starts playback.
|
||||||
fn play(&mut self) -> Result<(), PlayerError>;
|
fn play(&mut self) -> Result<(), PlayerError>;
|
||||||
|
|
||||||
fn resume(&mut self) -> Result<(), PlayerError>;
|
/// If the player is playing, pause playback.
|
||||||
|
|
||||||
fn pause(&mut self) -> Result<(), PlayerError>;
|
fn pause(&mut self) -> Result<(), PlayerError>;
|
||||||
|
|
||||||
|
/// Stop the playback entirely, removing the current [`URI`] from the player.
|
||||||
fn stop(&mut self) -> Result<(), PlayerError>;
|
fn stop(&mut self) -> Result<(), PlayerError>;
|
||||||
|
|
||||||
fn is_paused(&mut self) -> bool;
|
/// Convenience function to check if playback is paused.
|
||||||
|
fn is_paused(&self) -> bool;
|
||||||
|
|
||||||
fn position(&mut self) -> Option<Duration>;
|
/// Get the current playback position of the player.
|
||||||
|
fn position(&self) -> Option<Duration>;
|
||||||
|
|
||||||
fn duration(&mut self) -> Option<Duration>;
|
/// Get the duration of the currently playing track.
|
||||||
|
fn duration(&self) -> Option<Duration>;
|
||||||
|
|
||||||
|
/// Seek relative to the current position.
|
||||||
|
///
|
||||||
|
/// The position is capped at the duration of the song, and zero.
|
||||||
fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
|
fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
|
||||||
|
|
||||||
|
/// Seek absolutely within the song.
|
||||||
|
///
|
||||||
|
/// The position is capped at the duration of the song, and zero.
|
||||||
fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
|
fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
|
||||||
|
|
||||||
|
/// Return a reference to the player message channel, which can be cloned
|
||||||
|
/// in order to monitor messages from the player.
|
||||||
fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand>;
|
fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,6 +353,6 @@ mod tests {
|
||||||
songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
|
songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
|
||||||
|
|
||||||
config.write_file().unwrap();
|
config.write_file().unwrap();
|
||||||
library.save(config).unwrap();
|
library.save(Arc::new(RwLock::from(config))).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,8 +283,8 @@ impl Song {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find images around the music file that can be used
|
// Find images around the music file that can be used
|
||||||
let mut found_images = find_images(target_file.as_ref()).unwrap();
|
let found_images = find_images(target_file.as_ref()).unwrap();
|
||||||
album_art.append(&mut found_images);
|
album_art.extend_from_slice(&found_images);
|
||||||
|
|
||||||
// Get the format as a string
|
// Get the format as a string
|
||||||
let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
|
let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
|
||||||
|
@ -624,8 +624,6 @@ impl Album<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct MusicLibrary {
|
pub struct MusicLibrary {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -636,6 +634,8 @@ pub struct MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MusicLibrary {
|
impl MusicLibrary {
|
||||||
|
const BLOCKED_EXTENSIONS: &'static [&'static str] = &["vob", "log", "txt", "sf2"];
|
||||||
|
|
||||||
/// Create a new library from a name and [Uuid]
|
/// Create a new library from a name and [Uuid]
|
||||||
fn new(name: String, uuid: Uuid) -> Self {
|
fn new(name: String, uuid: Uuid) -> Self {
|
||||||
MusicLibrary {
|
MusicLibrary {
|
||||||
|
@ -654,7 +654,7 @@ impl MusicLibrary {
|
||||||
/// the [MusicLibrary] Vec
|
/// the [MusicLibrary] Vec
|
||||||
pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
|
pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
|
||||||
let global_config = &*config.read().unwrap();
|
let global_config = &*config.read().unwrap();
|
||||||
let path = global_config.libraries.get_library(&uuid)?.path;
|
let path = global_config.libraries.get_library(&uuid)?.path.clone();
|
||||||
|
|
||||||
let library: MusicLibrary = match path.exists() {
|
let library: MusicLibrary = match path.exists() {
|
||||||
true => read_file(path)?,
|
true => read_file(path)?,
|
||||||
|
@ -671,7 +671,8 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[cfg(debug_assertions)] // We probably wouldn't want to use this for real, but maybe it would have some utility?
|
//#[cfg(debug_assertions)] // We probably wouldn't want to use this for real, but maybe it would have some utility?
|
||||||
pub fn from_path(path: PathBuf) -> Result<Self, Box<dyn Error>> {
|
pub fn from_path<P: ?Sized + AsRef<Path>>(path: &P) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let path: PathBuf = path.as_ref().to_path_buf();
|
||||||
let library: MusicLibrary = match path.exists() {
|
let library: MusicLibrary = match path.exists() {
|
||||||
true => read_file(path)?,
|
true => read_file(path)?,
|
||||||
false => {
|
false => {
|
||||||
|
@ -684,8 +685,8 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes the database out to the file specified in the config
|
/// Serializes the database out to the file specified in the config
|
||||||
pub fn save(&self, config: Config) -> Result<(), Box<dyn Error>> {
|
pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> {
|
||||||
let path = config.libraries.get_library(&self.uuid)?.path;
|
let path = config.read().unwrap().libraries.get_library(&self.uuid)?.path.clone();
|
||||||
match path.try_exists() {
|
match path.try_exists() {
|
||||||
Ok(_) => write_file(self, path)?,
|
Ok(_) => write_file(self, path)?,
|
||||||
Err(error) => return Err(error.into()),
|
Err(error) => return Err(error.into()),
|
||||||
|
@ -715,7 +716,7 @@ impl MusicLibrary {
|
||||||
for location in &track.location {
|
for location in &track.location {
|
||||||
//TODO: check that this works
|
//TODO: check that this works
|
||||||
if path == location {
|
if path == location {
|
||||||
return std::ops::ControlFlow::Break((track, i));
|
return Break((track, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Continue(())
|
Continue(())
|
||||||
|
@ -765,7 +766,7 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds all the audio files within a specified folder
|
/// Finds all the audio files within a specified folder
|
||||||
pub fn scan_folder(&mut self, target_path: &str) -> Result<i32, Box<dyn std::error::Error>> {
|
pub fn scan_folder<P: ?Sized + AsRef<Path>>(&mut self, target_path: &P) -> Result<i32, Box<dyn std::error::Error>> {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
let mut errors = 0;
|
let mut errors = 0;
|
||||||
for target_file in WalkDir::new(target_path)
|
for target_file in WalkDir::new(target_path)
|
||||||
|
@ -803,27 +804,29 @@ impl MusicLibrary {
|
||||||
// If it's a normal file, add it to the database
|
// If it's a normal file, add it to the database
|
||||||
// if it's a cuesheet, do a bunch of fancy stuff
|
// if it's a cuesheet, do a bunch of fancy stuff
|
||||||
if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
|
if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
|
||||||
&& !BLOCKED_EXTENSIONS.contains(&extension.as_str())
|
&& !Self::BLOCKED_EXTENSIONS.contains(&extension.as_str())
|
||||||
{
|
{
|
||||||
match self.add_file(target_file.path()) {
|
match self.add_file(target_file.path()) {
|
||||||
Ok(_) => total += 1,
|
Ok(_) => total += 1,
|
||||||
Err(_error) => {
|
Err(_error) => {
|
||||||
errors += 1;
|
errors += 1;
|
||||||
//println!("{}, {:?}: {}", format, target_file.file_name(), _error)
|
println!("{:?}: {}", target_file.file_name(), _error)
|
||||||
} // TODO: Handle more of these errors
|
} // TODO: Handle more of these errors
|
||||||
};
|
};
|
||||||
} else if extension == "cue" {
|
} else if extension == "cue" {
|
||||||
total += match self.add_cuesheet(target_file.path()) {
|
total += match self.add_cuesheet(target_file.path()) {
|
||||||
Ok(added) => added,
|
Ok(added) => added,
|
||||||
Err(error) => {
|
Err(_error) => {
|
||||||
errors += 1;
|
errors += 1;
|
||||||
//println!("{}", error);
|
println!("{:?}: {}", target_file.file_name(), _error);
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("Total scanning errors: {}", errors);
|
||||||
|
|
||||||
Ok(total)
|
Ok(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue