cobbled together some test code for controller and ran cargo fmt

This commit is contained in:
MrDulfin 2024-09-23 20:52:23 -04:00
parent be9f28e38f
commit 15988ae808
12 changed files with 515 additions and 227 deletions

2
.gitignore vendored
View file

@ -3,6 +3,7 @@ target/
test-config/ test-config/
# Rust configuration # Rust configuration
Cargo.lock Cargo.lock
.cargo/
# Database files # Database files
*.db3* *.db3*
@ -15,4 +16,3 @@ music_database*
*.json *.json
*.zip *.zip
*.xml *.xml

View file

@ -49,3 +49,9 @@ listenbrainz = "0.7.0"
discord-rpc-client = "0.4.0" discord-rpc-client = "0.4.0"
nestify = "0.3.3" nestify = "0.3.3"
kushi = "0.1.3" kushi = "0.1.3"
moro = "0.4.0"
moro-local = "0.4.0"
futures = "0.3.30"
text_io = "0.1.12"
tokio = { version = "1.40.0", features = ["macros", "rt"] }
async-channel = "2.3.1"

View file

@ -193,10 +193,10 @@ pub mod tests {
use crate::music_storage::library::MusicLibrary; use crate::music_storage::library::MusicLibrary;
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::{Arc, RwLock},
}; };
pub fn new_config_lib() -> (Config, MusicLibrary) { pub fn new_config_lib() -> (Config, MusicLibrary) {
_ = std::fs::create_dir_all("test-config/music/");
let lib = ConfigLibrary::new( let lib = ConfigLibrary::new(
PathBuf::from("test-config/library"), PathBuf::from("test-config/library"),
String::from("library"), String::from("library"),
@ -216,7 +216,8 @@ pub mod tests {
) )
.unwrap(); .unwrap();
lib.scan_folder("test-config/music/").unwrap(); lib.scan_folder("test-config/music/").unwrap();
lib.save(config.libraries.get_default().unwrap().path.clone()).unwrap(); lib.save(config.libraries.get_default().unwrap().path.clone())
.unwrap();
(config, lib) (config, lib)
} }
@ -234,7 +235,8 @@ pub mod tests {
lib.scan_folder("test-config/music/").unwrap(); lib.scan_folder("test-config/music/").unwrap();
lib.save(config.libraries.get_default().unwrap().path.clone()).unwrap(); lib.save(config.libraries.get_default().unwrap().path.clone())
.unwrap();
(config, lib) (config, lib)
} }

View file

@ -1,20 +1,7 @@
pub enum Setting { pub enum Setting {
String { String { name: String, value: String },
name: String, Int { name: String, value: i32 },
value: String Bool { name: String, value: bool },
},
Int {
name: String,
value: i32
},
Bool {
name: String,
value: bool
},
}
pub struct Form {
} }
pub struct Form {}

View file

@ -9,8 +9,8 @@ pub mod music_storage {
} }
pub mod music_controller { pub mod music_controller {
pub mod controller;
pub mod connections; pub mod connections;
pub mod controller;
pub mod queue; pub mod queue;
} }

View file

@ -13,8 +13,6 @@
// use super::controller::DatabaseResponse; // use super::controller::DatabaseResponse;
// impl Controller { // impl Controller {
// pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> { // pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
// let config = &self.config.read().unwrap(); // let config = &self.config.read().unwrap();
@ -96,7 +94,6 @@
// c.q_enqueue(0, songs[1].location.to_owned()).unwrap(); // c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
// c.q_play(0).unwrap(); // c.q_play(0).unwrap();
// sleep(Duration::from_secs(100)); // sleep(Duration::from_secs(100));
// c.lbz_scrobble(client, songs[1].uuid).unwrap(); // c.lbz_scrobble(client, songs[1].uuid).unwrap();
// } // }

View file

@ -1,35 +1,24 @@
//! The [Controller] is the input and output for the entire //! The [Controller] is the input and output for the entire
//! player. It manages queues, playback, library access, and //! player. It manages queues, playback, library access, and
//! other functions //! other functions
#![allow(while_true)]
use crossbeam_channel;
use crossbeam_channel::{Receiver, Sender};
use kushi::QueueError;
use kushi::{Queue, QueueItemType}; use kushi::{Queue, QueueItemType};
use std::path::PathBuf; use kushi::{QueueError, QueueItem};
use std::sync::{Arc, Mutex, RwLock};
use std::thread::spawn;
use thiserror::Error;
use crossbeam_channel::unbounded;
use std::error::Error; use std::error::Error;
use std::marker::PhantomData;
use std::sync::{Arc, RwLock};
use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
use crate::config::ConfigError; use crate::config::ConfigError;
use crate::music_player::player::{Player, PlayerCommand, PlayerError}; use crate::music_player::player::{Player, PlayerError};
use crate::{ use crate::music_storage::library::Song;
config::Config, music_storage::library::MusicLibrary, use crate::{config::Config, music_storage::library::MusicLibrary};
};
use super::queue::{QueueAlbum, QueueSong}; use super::queue::{QueueAlbum, QueueSong};
pub struct Controller<'a, P>(&'a PhantomData<P>);
pub struct Controller<P: Player + Send + Sync> {
pub queue: Arc<RwLock<Queue<QueueSong, QueueAlbum>>>,
pub config: Arc<RwLock<Config>>,
pub library: MusicLibrary,
pub player: Arc<Mutex<P>>,
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ControllerError { pub enum ControllerError {
@ -52,146 +41,405 @@ pub enum PlayerLocation {
Custom, Custom,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub(super) struct MailMan<T: Send, U: Send> { pub struct MailMan<Tx: Send, Rx: Send> {
pub tx: Sender<T>, tx: async_channel::Sender<Tx>,
rx: Receiver<U>, rx: async_channel::Receiver<Rx>,
} }
impl<T: Send> MailMan<T, T> { impl<Tx: Send, Rx: Send> MailMan<Tx, Rx> {
pub fn new() -> Self { pub fn double() -> (MailMan<Tx, Rx>, MailMan<Rx, Tx>) {
let (tx, rx) = unbounded::<T>(); let (tx, rx) = async_channel::unbounded::<Tx>();
MailMan { tx, rx } let (tx1, rx1) = async_channel::unbounded::<Rx>();
}
}
impl<T: Send, U: Send> MailMan<T, U> {
pub fn double() -> (MailMan<T, U>, MailMan<U, T>) {
let (tx, rx) = unbounded::<T>();
let (tx1, rx1) = unbounded::<U>();
(MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx }) (MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx })
} }
pub fn send(&self, mail: T) -> Result<(), Box<dyn Error>> { pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError<Tx>> {
self.tx.send(mail).unwrap(); self.tx.send(mail).await
Ok(())
} }
pub fn recv(&self) -> Result<U, Box<dyn Error>> { pub async fn recv(&self) -> Result<Rx, async_channel::RecvError> {
let u = self.rx.recv()?; self.rx.recv().await
Ok(u)
} }
} }
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub enum PlayerCommand {
NextSong,
PrevSong,
Pause,
Play,
Enqueue(usize),
SetVolume(f64),
}
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub enum PlayerResponse {
Empty,
}
pub enum LibraryCommand {
Song(Uuid),
}
pub enum LibraryResponse {
Songs(Song),
}
enum InnerLibraryCommand {
Song(Uuid),
}
enum InnerLibraryResponse<'a> {
Song(&'a Song),
}
pub enum QueueCommand {
Append(QueueItem<QueueSong, QueueAlbum>),
Next,
Prev,
GetIndex(usize),
NowPlaying,
}
pub enum QueueResponse {
Ok,
Item(QueueItem<QueueSong, QueueAlbum>),
}
#[allow(unused_variables)] #[allow(unused_variables)]
impl<P: Player + Send + Sync + Sized + 'static> Controller<P> { impl<'c, P: Player + Send + Sync> Controller<'c, P> {
pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>> pub async fn start(
player_mail: (
MailMan<PlayerCommand, PlayerResponse>,
MailMan<PlayerResponse, PlayerCommand>,
),
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
mut library: MusicLibrary,
config: Arc<RwLock<Config>>,
) -> Result<(), Box<dyn Error>>
where where
std::path::PathBuf: std::convert::From<T>,
P: Player, P: Player,
{ {
let config_path = PathBuf::from(config_path); //TODO: make a separate event loop for sccessing library that clones borrowed values from inner library loop?
let mut queue: Queue<QueueSong, QueueAlbum> = Queue {
let config = Config::read_file(config_path)?;
let uuid = config.libraries.get_default()?.uuid;
let library = MusicLibrary::init(config.libraries.get_default()?.path.clone(), uuid)?;
let config_ = Arc::new(RwLock::from(config));
let queue: Queue<QueueSong, QueueAlbum> = Queue {
items: Vec::new(), items: Vec::new(),
played: Vec::new(), played: Vec::new(),
loop_: false, loop_: false,
shuffle: None shuffle: None,
}; };
let controller = Controller { for song in &library.library {
queue: Arc::new(RwLock::from(queue)), queue.add_item(
config: config_.clone(), QueueSong {
library, song: song.clone(),
player: Arc::new(Mutex::new(P::new()?)), location: PlayerLocation::Test,
};
let player = controller.player.clone();
let queue = controller.queue.clone();
let controller_thread = spawn(move || {
loop {
let signal = { player.lock().unwrap().message_channel().recv().unwrap() };
match signal {
PlayerCommand::AboutToFinish => {
println!("Switching songs!");
let mut queue = queue.write().unwrap();
let uri = queue
.next()
.unwrap()
.clone();
player
.lock()
.unwrap()
.enqueue_next(&{
match uri.item {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0.clone(),
_ => unimplemented!()
}
})
.unwrap();
}, },
PlayerCommand::EndOfStream => {dbg!()} true,
_ => {} );
}
} }
let inner_lib_mail = MailMan::double();
let queue = queue;
std::thread::scope(|scope| {
let queue_mail = MailMan::double();
let a = scope.spawn(|| {
futures::executor::block_on(async {
moro::async_scope!(|scope| {
println!("async scope created");
let player = Arc::new(RwLock::new(P::new().unwrap()));
let _player = player.clone();
scope
.spawn(async move {
Controller::<P>::player_command_loop(
_player,
player_mail.1,
queue_mail.0,
)
.await
.unwrap();
})
.await;
scope
.spawn(async move {
Controller::<P>::player_event_loop(player, player_mail.0)
.await
.unwrap();
})
.await;
scope
.spawn(async {
Controller::<P>::inner_library_loop(inner_lib_mail.1, &mut library)
.await
.unwrap()
})
.await;
scope
.spawn(async {
Controller::<P>::outer_library_loop(lib_mail, inner_lib_mail.0)
.await
.unwrap();
})
.await
})
.await;
})
}); });
let b = scope.spawn(|| {
futures::executor::block_on(async {
Controller::<P>::queue_loop(queue, queue_mail.1).await;
})
});
a.join().unwrap();
b.join().unwrap();
});
Ok(controller) Ok(())
} }
pub fn q_add(&mut self, item: &Uuid, source: PlayerLocation, by_human: bool) { async fn player_command_loop(
let item = self.library.query_uuid(item).unwrap().0.to_owned(); player: Arc<RwLock<P>>,
self.queue.write().unwrap().add_item(QueueSong { song: item, location: source }, by_human) player_mail: MailMan<PlayerResponse, PlayerCommand>,
queue_mail: MailMan<QueueCommand, QueueResponse>,
) -> Result<(), ()> {
{
player.write().unwrap().set_volume(0.05);
}
while true {
let _mail = player_mail.recv().await;
if let Ok(mail) = _mail {
match mail {
PlayerCommand::Play => {
player.write().unwrap().play().unwrap();
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
PlayerCommand::Pause => {
player.write().unwrap().pause().unwrap();
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
PlayerCommand::SetVolume(volume) => {
player.write().unwrap().set_volume(volume);
println!("volume set to {volume}");
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
PlayerCommand::NextSong => {
queue_mail.send(QueueCommand::Next).await.unwrap();
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
let uri = match &item.item {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
_ => unimplemented!(),
};
player.write().unwrap().enqueue_next(uri).unwrap();
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
}
PlayerCommand::PrevSong => {
queue_mail.send(QueueCommand::Prev).await.unwrap();
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
let uri = match &item.item {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
_ => unimplemented!(),
};
player.write().unwrap().enqueue_next(uri).unwrap();
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
}
PlayerCommand::Enqueue(index) => {
queue_mail
.send(QueueCommand::GetIndex(index))
.await
.unwrap();
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
match item.item {
QueueItemType::Single(song) => {
player
.write()
.unwrap()
.enqueue_next(song.song.primary_uri().unwrap().0)
.unwrap();
}
_ => unimplemented!(),
}
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
}
}
} else {
return Err(());
}
}
Ok(())
}
async fn outer_library_loop(
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>,
) -> Result<(), ()> {
while true {
match lib_mail.recv().await.unwrap() {
LibraryCommand::Song(uuid) => {
inner_lib_mail
.send(InnerLibraryCommand::Song(uuid))
.await
.unwrap();
let x = inner_lib_mail.recv().await.unwrap();
}
}
}
Ok(())
}
async fn inner_library_loop(
lib_mail: MailMan<InnerLibraryResponse<'c>, InnerLibraryCommand>,
library: &'c mut MusicLibrary,
) -> Result<(), ()> {
while true {
match lib_mail.recv().await.unwrap() {
InnerLibraryCommand::Song(uuid) => {
let song: &'c Song = library.query_uuid(&uuid).unwrap().0;
lib_mail
.send(InnerLibraryResponse::Song(song))
.await
.unwrap();
}
}
}
Ok(())
}
async fn player_event_loop(
player: Arc<RwLock<P>>,
player_mail: MailMan<PlayerCommand, PlayerResponse>,
) -> Result<(), ()> {
// just pretend this does something
Ok(())
}
async fn queue_loop(
mut queue: Queue<QueueSong, QueueAlbum>,
queue_mail: MailMan<QueueResponse, QueueCommand>,
) {
while true {
match queue_mail.recv().await.unwrap() {
QueueCommand::Append(item) => match item.item {
QueueItemType::Single(song) => queue.add_item(song, true),
_ => unimplemented!(),
},
QueueCommand::Next => {
let next = queue.next().unwrap();
queue_mail
.send(QueueResponse::Item(next.clone()))
.await
.unwrap();
}
QueueCommand::Prev => {
let next = queue.prev().unwrap();
queue_mail
.send(QueueResponse::Item(next.clone()))
.await
.unwrap();
}
QueueCommand::GetIndex(index) => {
let item = queue.items[index].clone();
queue_mail.send(QueueResponse::Item(item)).await.unwrap();
}
QueueCommand::NowPlaying => {
let item = queue.current().unwrap();
queue_mail
.send(QueueResponse::Item(item.clone()))
.await
.unwrap();
}
}
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod test_super { mod test_super {
use std::{thread::sleep, time::Duration}; use std::{
path::PathBuf,
sync::{Arc, RwLock},
thread::spawn,
};
use crate::{config::tests::read_config_lib, music_controller::controller::{PlayerLocation, QueueSong}, music_player::{gstreamer::GStreamer, player::Player}}; use crate::{
config::{tests::new_config_lib, Config},
music_controller::controller::{
LibraryCommand, LibraryResponse, MailMan, PlayerCommand, PlayerResponse,
},
music_player::gstreamer::GStreamer,
music_storage::library::MusicLibrary,
};
use super::Controller; use super::Controller;
#[test] #[tokio::test]
fn construct_controller() { async fn construct_controller() {
println!("starto!"); // use if you don't have a config setup and add music to the music folder
let config = read_config_lib(); new_config_lib();
let next = config.1.library[2].clone(); let lib_mail: (MailMan<LibraryCommand, LibraryResponse>, MailMan<_, _>) = MailMan::double();
{ let player_mail: (MailMan<PlayerCommand, PlayerResponse>, MailMan<_, _>) =
let controller = Controller::<GStreamer>::start("test-config/config_test.json").unwrap(); MailMan::double();
{
let mut queue = controller.queue.write().unwrap();
for x in config.1.library {
queue.add_item(QueueSong { song: x, location: PlayerLocation::Library }, true);
}
}
{
controller.player.lock().unwrap().enqueue_next(next.primary_uri().unwrap().0).unwrap();
}
{
controller.player.lock().unwrap().set_volume(0.1);
}
{
controller.player.lock().unwrap().play().unwrap();
}
println!("I'm a tire");
}
sleep(Duration::from_secs(10))
let _player_mail = player_mail.0.clone();
let b = spawn(move || {
futures::executor::block_on(async {
_player_mail
.send(PlayerCommand::SetVolume(0.01))
.await
.unwrap();
loop {
let buf: String = text_io::read!();
dbg!(&buf);
_player_mail
.send(match buf.to_lowercase().as_str() {
"next" => PlayerCommand::NextSong,
"prev" => PlayerCommand::PrevSong,
"pause" => PlayerCommand::Pause,
"play" => PlayerCommand::Play,
x if x.parse::<usize>().is_ok() => {
PlayerCommand::Enqueue(x.parse::<usize>().unwrap())
}
_ => continue,
})
.await
.unwrap();
println!("sent it");
println!("{:?}", _player_mail.recv().await.unwrap())
}
})
});
let a = spawn(move || {
futures::executor::block_on(async {
let config = Config::read_file(PathBuf::from(std::env!("CONFIG-PATH"))).unwrap();
let library = {
MusicLibrary::init(
config.libraries.get_default().unwrap().path.clone(),
config.libraries.get_default().unwrap().uuid,
)
.unwrap()
};
Controller::<GStreamer>::start(
player_mail,
lib_mail.1,
library,
Arc::new(RwLock::new(config)),
)
.await
.unwrap();
});
});
b.join().unwrap();
a.join().unwrap();
} }
} }

View file

@ -53,7 +53,7 @@ enum PlaybackInfo {
/// When this is sent, the thread will die! Use it when the [Player] is /// When this is sent, the thread will die! Use it when the [Player] is
/// done playing /// done playing
Finished Finished,
} }
/// An instance of a music player with a GStreamer backend /// An instance of a music player with a GStreamer backend
@ -89,7 +89,7 @@ impl GStreamer {
fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> { fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
if !source.exists().is_ok_and(|x| x) { if !source.exists().is_ok_and(|x| x) {
// If the source doesn't exist, gstreamer will crash! // If the source doesn't exist, gstreamer will crash!
return Err(PlayerError::NotFound) return Err(PlayerError::NotFound);
} }
// Make sure the playback tracker knows the stuff is stopped // Make sure the playback tracker knows the stuff is stopped
@ -110,10 +110,12 @@ impl GStreamer {
self.end = Some(Duration::from_std(*end).unwrap()); self.end = Some(Duration::from_std(*end).unwrap());
// Send the updated position to the tracker // Send the updated position to the tracker
self.playback_tx.send(PlaybackInfo::Playing{ self.playback_tx
.send(PlaybackInfo::Playing {
start: self.start.unwrap(), start: self.start.unwrap(),
end: self.end.unwrap() end: self.end.unwrap(),
}).unwrap(); })
.unwrap();
// Wait for it to be ready, and then move to the proper position // Wait for it to be ready, and then move to the proper position
self.play().unwrap(); self.play().unwrap();
@ -125,7 +127,9 @@ impl GStreamer {
std::thread::sleep(std::time::Duration::from_millis(1)); std::thread::sleep(std::time::Duration::from_millis(1));
} }
//panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)"); //panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
return Err(PlayerError::StateChange("Could not seek to beginning of CUE track".into())) return Err(PlayerError::StateChange(
"Could not seek to beginning of CUE track".into(),
));
} }
_ => { _ => {
self.playbin self.playbin
@ -145,10 +149,12 @@ impl GStreamer {
self.end = self.raw_duration(); self.end = self.raw_duration();
// Send the updated position to the tracker // Send the updated position to the tracker
self.playback_tx.send(PlaybackInfo::Playing{ self.playback_tx
.send(PlaybackInfo::Playing {
start: self.start.unwrap(), start: self.start.unwrap(),
end: self.end.unwrap() end: self.end.unwrap(),
}).unwrap(); })
.unwrap();
} }
} }
@ -223,7 +229,7 @@ impl Player for GStreamer {
fn new() -> Result<Self, PlayerError> { fn new() -> Result<Self, PlayerError> {
// Initialize GStreamer, maybe figure out how to nicely fail here // Initialize GStreamer, maybe figure out how to nicely fail here
if let Err(err) = gst::init() { if let Err(err) = gst::init() {
return Err(PlayerError::Init(err.to_string())) return Err(PlayerError::Init(err.to_string()));
}; };
let ctx = glib::MainContext::default(); let ctx = glib::MainContext::default();
let _guard = ctx.acquire(); let _guard = ctx.acquire();
@ -233,7 +239,7 @@ impl Player for GStreamer {
match gst::ElementFactory::make("playbin3").build() { match gst::ElementFactory::make("playbin3").build() {
Ok(playbin) => playbin, Ok(playbin) => playbin,
Err(error) => return Err(PlayerError::Init(error.to_string())), Err(error) => return Err(PlayerError::Init(error.to_string())),
} },
)); ));
let playbin = playbin_arc.clone(); let playbin = playbin_arc.clone();
@ -252,7 +258,10 @@ impl Player for GStreamer {
.build() .build()
.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));
@ -262,7 +271,9 @@ 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);
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);
@ -279,11 +290,11 @@ impl Player for GStreamer {
gst::MessageView::StreamStart(_) => println!("Stream start"), gst::MessageView::StreamStart(_) => println!("Stream start"),
gst::MessageView::Error(err) => { gst::MessageView::Error(err) => {
println!("Error recieved: {}", err); println!("Error recieved: {}", err);
return glib::ControlFlow::Break return glib::ControlFlow::Break;
} }
gst::MessageView::Buffering(buffering) => { gst::MessageView::Buffering(buffering) => {
if *bus_paused.read().unwrap() == true { if *bus_paused.read().unwrap() == true {
return glib::ControlFlow::Continue return glib::ControlFlow::Continue;
} }
// If the player is not paused, pause it // If the player is not paused, pause it
@ -349,7 +360,7 @@ impl Player for GStreamer {
fn play(&mut self) -> Result<(), PlayerError> { fn play(&mut self) -> Result<(), PlayerError> {
if self.state() == PlayerState::Playing { if self.state() == PlayerState::Playing {
return Ok(()) return Ok(());
} }
*self.paused.write().unwrap() = false; *self.paused.write().unwrap() = false;
self.set_state(gst::State::Playing)?; self.set_state(gst::State::Playing)?;
@ -358,7 +369,7 @@ impl Player for GStreamer {
fn pause(&mut self) -> Result<(), PlayerError> { fn pause(&mut self) -> Result<(), PlayerError> {
if self.state() == PlayerState::Paused || *self.paused.read().unwrap() { if self.state() == PlayerState::Paused || *self.paused.read().unwrap() {
return Ok(()) return Ok(());
} }
*self.paused.write().unwrap() = true; *self.paused.write().unwrap() = true;
self.set_state(gst::State::Paused)?; self.set_state(gst::State::Paused)?;
@ -495,16 +506,14 @@ fn playback_monitor(
// This has to be done AFTER the current time in the file // This has to be done AFTER the current time in the file
// is calculated, or everything else is wrong // is calculated, or everything else is wrong
pos_temp = Some(pos_temp.unwrap() - start) pos_temp = Some(pos_temp.unwrap() - start)
}, }
PlaybackInfo::Finished => { PlaybackInfo::Finished => {
println!("MONITOR: Shutting down"); println!("MONITOR: Shutting down");
*position.write().unwrap() = None; *position.write().unwrap() = None;
break break;
}, }
PlaybackInfo::Idle | PlaybackInfo::Switching => { PlaybackInfo::Idle | PlaybackInfo::Switching => sent_atf = false,
sent_atf = false _ => (),
},
_ => ()
} }
*position.write().unwrap() = pos_temp; *position.write().unwrap() = pos_temp;

View file

@ -41,7 +41,9 @@ 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. /// Get the currently playing [URI] from the player.
fn source(&self) -> &Option<URI>; fn source(&self) -> &Option<URI>;

View file

@ -2,13 +2,13 @@ use file_format::FileFormat;
use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt}; use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt};
use quick_xml::events::Event; use quick_xml::events::Event;
use quick_xml::reader::Reader; use quick_xml::reader::Reader;
use uuid::Uuid;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::fs::File; use std::fs::File;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration as StdDur; use std::time::Duration as StdDur;
use std::vec::Vec; use std::vec::Vec;
use uuid::Uuid;
use chrono::prelude::*; use chrono::prelude::*;
@ -118,7 +118,12 @@ impl ExternalLibrary for ITunesLibrary {
buf.clear(); buf.clear();
} }
let elasped = now.elapsed(); let elasped = now.elapsed();
println!("\n\niTunesReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}", count3, elasped.as_secs(), count4); println!(
"\n\niTunesReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}",
count3,
elasped.as_secs(),
count4
);
let mut lib = ITunesLibrary::new(); let mut lib = ITunesLibrary::new();
lib.tracks.append(converted_songs.as_mut()); lib.tracks.append(converted_songs.as_mut());
lib lib
@ -334,25 +339,43 @@ impl ITunesSong {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}}; use std::{
path::{Path, PathBuf},
sync::{Arc, RwLock},
};
use crate::{config::{Config, ConfigLibrary}, music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary}}; use crate::{
config::{Config, ConfigLibrary},
music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary},
};
use super::ITunesLibrary; use super::ITunesLibrary;
#[test] #[test]
fn itunes_lib_test() { fn itunes_lib_test() {
let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); let config_lib = ConfigLibrary::new(
PathBuf::from("test-config/library2"),
String::from("library2"),
None,
);
config.libraries.libraries.push(config_lib.clone()); config.libraries.libraries.push(config_lib.clone());
let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs(); let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs();
let mut library = MusicLibrary::init(config.libraries.get_default().unwrap().path.clone(), config_lib.uuid).unwrap(); let mut library = MusicLibrary::init(
config.libraries.get_default().unwrap().path.clone(),
config_lib.uuid,
)
.unwrap();
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.libraries.get_default().unwrap().path.clone()).unwrap(); library
.save(config.libraries.get_default().unwrap().path.clone())
.unwrap();
} }
} }

View file

@ -636,13 +636,9 @@ impl IntoIterator for Album {
for (disc, mut tracks) in self.discs { for (disc, mut tracks) in self.discs {
tracks.par_sort_by(|a, b| a.0.cmp(&b.0)); tracks.par_sort_by(|a, b| a.0.cmp(&b.0));
let mut tracks = tracks.into_iter() let mut tracks = tracks
.map(|(track, uuid)| .into_iter()
AlbumTrack { .map(|(track, uuid)| AlbumTrack { disc, track, uuid })
disc,
track,
uuid
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
vec.append(&mut tracks); vec.append(&mut tracks);
@ -654,7 +650,7 @@ impl IntoIterator for Album {
pub struct AlbumTrack { pub struct AlbumTrack {
disc: u16, disc: u16,
track: u16, track: u16,
uuid: Uuid uuid: Uuid,
} }
impl AlbumTrack { impl AlbumTrack {
@ -820,7 +816,10 @@ impl MusicLibrary {
} }
/// Finds all the audio files within a specified folder /// Finds all the audio files within a specified folder
pub fn scan_folder<P: ?Sized + AsRef<Path>>(&mut self, target_path: &P) -> 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)
@ -881,12 +880,18 @@ impl MusicLibrary {
self.library.par_iter().for_each(|t| { self.library.par_iter().for_each(|t| {
for location in &t.location { for location in &t.location {
if !location.exists().unwrap() { if !location.exists().unwrap() {
Arc::clone(&target_removals).lock().unwrap().push(location.clone()); Arc::clone(&target_removals)
.lock()
.unwrap()
.push(location.clone());
} }
} }
}); });
let target_removals = Arc::try_unwrap(target_removals).unwrap().into_inner().unwrap(); let target_removals = Arc::try_unwrap(target_removals)
.unwrap()
.into_inner()
.unwrap();
for location in target_removals { for location in target_removals {
self.remove_uri(&location).unwrap(); self.remove_uri(&location).unwrap();
} }
@ -1119,16 +1124,19 @@ impl MusicLibrary {
.unwrap_or(&String::new()) .unwrap_or(&String::new())
.parse::<u16>() .parse::<u16>()
.unwrap_or_default(), .unwrap_or_default(),
song.uuid song.uuid,
)), )),
None => { None => {
album.discs.insert(disc_num, vec![( album.discs.insert(
disc_num,
vec![(
song.get_tag(&Tag::Track) song.get_tag(&Tag::Track)
.unwrap_or(&String::new()) .unwrap_or(&String::new())
.parse::<u16>() .parse::<u16>()
.unwrap_or_default(), .unwrap_or_default(),
song.uuid song.uuid,
)]); )],
);
} }
}, },
// If the album is not in the list, make it new one and add it // If the album is not in the list, make it new one and add it
@ -1144,13 +1152,13 @@ impl MusicLibrary {
.unwrap_or(&String::new()) .unwrap_or(&String::new())
.parse::<u16>() .parse::<u16>()
.unwrap_or_default(), .unwrap_or_default(),
song.uuid song.uuid,
)])]), )],
)]),
cover: album_art.cloned(), cover: album_art.cloned(),
}; };
albums.insert(album_title, new_album); albums.insert(album_title, new_album);
} }
} }
paths.insert(song.uuid, song.primary_uri().unwrap()); paths.insert(song.uuid, song.primary_uri().unwrap());
} }
@ -1162,19 +1170,18 @@ impl MusicLibrary {
let num_a = a.0; let num_a = a.0;
let num_b = b.0; let num_b = b.0;
if (num_a, num_b) != (0,0) if (num_a, num_b) != (0, 0) {
{
// If parsing the track numbers succeeds, compare as numbers // If parsing the track numbers succeeds, compare as numbers
num_a.cmp(&num_b) num_a.cmp(&num_b)
} else { } else {
// If parsing doesn't succeed, compare the locations // If parsing doesn't succeed, compare the locations
let a = match paths.get_key_value(&a.1) { let a = match paths.get_key_value(&a.1) {
Some((_, (uri, _))) => uri, Some((_, (uri, _))) => uri,
None => return Ordering::Equal None => return Ordering::Equal,
}; };
let b = match paths.get_key_value(&b.1) { let b = match paths.get_key_value(&b.1) {
Some((_, (uri, _))) => uri, Some((_, (uri, _))) => uri,
None => return Ordering::Equal None => return Ordering::Equal,
}; };
a.as_uri().cmp(&b.as_uri()) a.as_uri().cmp(&b.as_uri())
@ -1217,13 +1224,20 @@ mod test {
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use crate::{config::{tests::new_config_lib, Config}, music_storage::library::MusicLibrary}; use crate::{
config::{tests::new_config_lib, Config},
music_storage::library::MusicLibrary,
};
#[test] #[test]
fn library_init() { fn library_init() {
let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap(); let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
let target_uuid = config.libraries.libraries[0].uuid; let target_uuid = config.libraries.libraries[0].uuid;
let a = MusicLibrary::init(config.libraries.get_default().unwrap().path.clone(), target_uuid).unwrap(); let a = MusicLibrary::init(
config.libraries.get_default().unwrap().path.clone(),
target_uuid,
)
.unwrap();
dbg!(a); dbg!(a);
} }
} }