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

4
.gitignore vendored
View file

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

View file

@ -49,3 +49,9 @@ listenbrainz = "0.7.0"
discord-rpc-client = "0.4.0"
nestify = "0.3.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 std::{
path::PathBuf,
sync::{Arc, RwLock},
};
pub fn new_config_lib() -> (Config, MusicLibrary) {
_ = std::fs::create_dir_all("test-config/music/");
let lib = ConfigLibrary::new(
PathBuf::from("test-config/library"),
String::from("library"),
@ -216,7 +216,8 @@ pub mod tests {
)
.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)
}
@ -234,7 +235,8 @@ pub mod tests {
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)
}

View file

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

View file

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

View file

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

View file

@ -1,35 +1,24 @@
//! The [Controller] is the input and output for the entire
//! player. It manages queues, playback, library access, and
//! other functions
#![allow(while_true)]
use crossbeam_channel;
use crossbeam_channel::{Receiver, Sender};
use kushi::QueueError;
use kushi::{Queue, QueueItemType};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::thread::spawn;
use thiserror::Error;
use crossbeam_channel::unbounded;
use kushi::{QueueError, QueueItem};
use std::error::Error;
use std::marker::PhantomData;
use std::sync::{Arc, RwLock};
use thiserror::Error;
use uuid::Uuid;
use crate::config::ConfigError;
use crate::music_player::player::{Player, PlayerCommand, PlayerError};
use crate::{
config::Config, music_storage::library::MusicLibrary,
};
use crate::music_player::player::{Player, PlayerError};
use crate::music_storage::library::Song;
use crate::{config::Config, music_storage::library::MusicLibrary};
use super::queue::{QueueAlbum, QueueSong};
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>>,
}
pub struct Controller<'a, P>(&'a PhantomData<P>);
#[derive(Error, Debug)]
pub enum ControllerError {
@ -52,146 +41,405 @@ pub enum PlayerLocation {
Custom,
}
#[derive(Debug)]
pub(super) struct MailMan<T: Send, U: Send> {
pub tx: Sender<T>,
rx: Receiver<U>,
#[derive(Debug, Clone)]
pub struct MailMan<Tx: Send, Rx: Send> {
tx: async_channel::Sender<Tx>,
rx: async_channel::Receiver<Rx>,
}
impl<T: Send> MailMan<T, T> {
pub fn new() -> Self {
let (tx, rx) = unbounded::<T>();
MailMan { tx, 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>();
impl<Tx: Send, Rx: Send> MailMan<Tx, Rx> {
pub fn double() -> (MailMan<Tx, Rx>, MailMan<Rx, Tx>) {
let (tx, rx) = async_channel::unbounded::<Tx>();
let (tx1, rx1) = async_channel::unbounded::<Rx>();
(MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx })
}
pub fn send(&self, mail: T) -> Result<(), Box<dyn Error>> {
self.tx.send(mail).unwrap();
Ok(())
pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError<Tx>> {
self.tx.send(mail).await
}
pub fn recv(&self) -> Result<U, Box<dyn Error>> {
let u = self.rx.recv()?;
Ok(u)
pub async fn recv(&self) -> Result<Rx, async_channel::RecvError> {
self.rx.recv().await
}
}
#[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)]
impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>>
impl<'c, P: Player + Send + Sync> Controller<'c, P> {
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
std::path::PathBuf: std::convert::From<T>,
P: Player,
{
let config_path = PathBuf::from(config_path);
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 {
//TODO: make a separate event loop for sccessing library that clones borrowed values from inner library loop?
let mut queue: Queue<QueueSong, QueueAlbum> = Queue {
items: Vec::new(),
played: Vec::new(),
loop_: false,
shuffle: None
shuffle: None,
};
let controller = Controller {
queue: Arc::new(RwLock::from(queue)),
config: config_.clone(),
library,
player: Arc::new(Mutex::new(P::new()?)),
};
for song in &library.library {
queue.add_item(
QueueSong {
song: song.clone(),
location: PlayerLocation::Test,
},
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 = 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!()
}
let _player = player.clone();
scope
.spawn(async move {
Controller::<P>::player_command_loop(
_player,
player_mail.1,
queue_mail.0,
)
.await
.unwrap();
})
.unwrap();
},
PlayerCommand::EndOfStream => {dbg!()}
_ => {}
}
}
.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) {
let item = self.library.query_uuid(item).unwrap().0.to_owned();
self.queue.write().unwrap().add_item(QueueSong { song: item, location: source }, by_human)
async fn player_command_loop(
player: Arc<RwLock<P>>,
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)]
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;
#[test]
fn construct_controller() {
println!("starto!");
let config = read_config_lib();
#[tokio::test]
async fn construct_controller() {
// use if you don't have a config setup and add music to the music folder
new_config_lib();
let next = config.1.library[2].clone();
{
let controller = Controller::<GStreamer>::start("test-config/config_test.json").unwrap();
{
let mut queue = controller.queue.write().unwrap();
for x in config.1.library {
queue.add_item(QueueSong { song: x, location: PlayerLocation::Library }, true);
let lib_mail: (MailMan<LibraryCommand, LibraryResponse>, MailMan<_, _>) = MailMan::double();
let player_mail: (MailMan<PlayerCommand, PlayerResponse>, MailMan<_, _>) =
MailMan::double();
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())
}
}
{
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 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

@ -46,30 +46,30 @@ impl TryInto<gst::State> for PlayerState {
enum PlaybackInfo {
Idle,
Switching,
Playing{
Playing {
start: Duration,
end: Duration,
end: Duration,
},
/// When this is sent, the thread will die! Use it when the [Player] is
/// done playing
Finished
Finished,
}
/// An instance of a music player with a GStreamer backend
#[derive(Debug)]
pub struct GStreamer {
source: Option<URI>,
source: Option<URI>,
message_rx: crossbeam::channel::Receiver<PlayerCommand>,
playback_tx: crossbeam::channel::Sender<PlaybackInfo>,
playbin: Arc<RwLock<Element>>,
volume: f64,
start: Option<Duration>,
end: Option<Duration>,
paused: Arc<RwLock<bool>>,
position: Arc<RwLock<Option<Duration>>>,
playbin: Arc<RwLock<Element>>,
volume: f64,
start: Option<Duration>,
end: Option<Duration>,
paused: Arc<RwLock<bool>>,
position: Arc<RwLock<Option<Duration>>>,
}
impl From<gst::StateChangeError> for PlayerError {
@ -89,7 +89,7 @@ impl GStreamer {
fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
if !source.exists().is_ok_and(|x| x) {
// 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
@ -110,10 +110,12 @@ impl GStreamer {
self.end = Some(Duration::from_std(*end).unwrap());
// Send the updated position to the tracker
self.playback_tx.send(PlaybackInfo::Playing{
start: self.start.unwrap(),
end: self.end.unwrap()
}).unwrap();
self.playback_tx
.send(PlaybackInfo::Playing {
start: self.start.unwrap(),
end: self.end.unwrap(),
})
.unwrap();
// Wait for it to be ready, and then move to the proper position
self.play().unwrap();
@ -125,7 +127,9 @@ impl GStreamer {
std::thread::sleep(std::time::Duration::from_millis(1));
}
//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
@ -145,10 +149,12 @@ impl GStreamer {
self.end = self.raw_duration();
// Send the updated position to the tracker
self.playback_tx.send(PlaybackInfo::Playing{
start: self.start.unwrap(),
end: self.end.unwrap()
}).unwrap();
self.playback_tx
.send(PlaybackInfo::Playing {
start: self.start.unwrap(),
end: self.end.unwrap(),
})
.unwrap();
}
}
@ -223,7 +229,7 @@ impl Player for GStreamer {
fn new() -> Result<Self, PlayerError> {
// Initialize GStreamer, maybe figure out how to nicely fail here
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 _guard = ctx.acquire();
@ -233,7 +239,7 @@ impl Player for GStreamer {
match gst::ElementFactory::make("playbin3").build() {
Ok(playbin) => playbin,
Err(error) => return Err(PlayerError::Init(error.to_string())),
}
},
));
let playbin = playbin_arc.clone();
@ -252,7 +258,10 @@ impl Player for GStreamer {
.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);
let position = Arc::new(RwLock::new(None));
@ -262,7 +271,9 @@ impl Player for GStreamer {
let (status_tx, status_rx) = unbounded::<PlaybackInfo>();
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
let playbin_bus_ctrl = Arc::clone(&playbin);
@ -279,11 +290,11 @@ impl Player for GStreamer {
gst::MessageView::StreamStart(_) => println!("Stream start"),
gst::MessageView::Error(err) => {
println!("Error recieved: {}", err);
return glib::ControlFlow::Break
return glib::ControlFlow::Break;
}
gst::MessageView::Buffering(buffering) => {
if *bus_paused.read().unwrap() == true {
return glib::ControlFlow::Continue
return glib::ControlFlow::Continue;
}
// If the player is not paused, pause it
@ -349,7 +360,7 @@ impl Player for GStreamer {
fn play(&mut self) -> Result<(), PlayerError> {
if self.state() == PlayerState::Playing {
return Ok(())
return Ok(());
}
*self.paused.write().unwrap() = false;
self.set_state(gst::State::Playing)?;
@ -358,7 +369,7 @@ impl Player for GStreamer {
fn pause(&mut self) -> Result<(), PlayerError> {
if self.state() == PlayerState::Paused || *self.paused.read().unwrap() {
return Ok(())
return Ok(());
}
*self.paused.write().unwrap() = true;
self.set_state(gst::State::Paused)?;
@ -472,7 +483,7 @@ fn playback_monitor(
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
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
let finish_point = end - Duration::milliseconds(2000);
if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
@ -495,16 +506,14 @@ fn playback_monitor(
// This has to be done AFTER the current time in the file
// is calculated, or everything else is wrong
pos_temp = Some(pos_temp.unwrap() - start)
},
}
PlaybackInfo::Finished => {
println!("MONITOR: Shutting down");
*position.write().unwrap() = None;
break
},
PlaybackInfo::Idle | PlaybackInfo::Switching => {
sent_atf = false
},
_ => ()
break;
}
PlaybackInfo::Idle | PlaybackInfo::Switching => sent_atf = false,
_ => (),
}
*position.write().unwrap() = pos_temp;

View file

@ -41,7 +41,9 @@ pub enum PlayerCommand {
pub trait 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>;

View file

@ -2,13 +2,13 @@ use file_format::FileFormat;
use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt};
use quick_xml::events::Event;
use quick_xml::reader::Reader;
use uuid::Uuid;
use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration as StdDur;
use std::vec::Vec;
use uuid::Uuid;
use chrono::prelude::*;
@ -118,7 +118,12 @@ impl ExternalLibrary for ITunesLibrary {
buf.clear();
}
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();
lib.tracks.append(converted_songs.as_mut());
lib
@ -175,10 +180,10 @@ impl ExternalLibrary for ITunesLibrary {
skips: 0,
favorited: track.favorited,
banned: if track.banned {
Some(BannedType::All)
}else {
None
},
Some(BannedType::All)
} else {
None
},
rating: track.rating,
format: match FileFormat::from_file(PathBuf::from(&loc)) {
Ok(e) => Some(e),
@ -334,25 +339,43 @@ impl ITunesSong {
#[cfg(test)]
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;
#[test]
fn itunes_lib_test() {
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());
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();
library.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
library
.save(config.libraries.get_default().unwrap().path.clone())
.unwrap();
}
}

View file

@ -10,4 +10,4 @@ pub mod itunes {
pub mod reader;
}
pub mod common;
pub mod extern_library;
pub mod extern_library;

View file

@ -636,16 +636,12 @@ impl IntoIterator for Album {
for (disc, mut tracks) in self.discs {
tracks.par_sort_by(|a, b| a.0.cmp(&b.0));
let mut tracks = tracks.into_iter()
.map(|(track, uuid)|
AlbumTrack {
disc,
track,
uuid
})
let mut tracks = tracks
.into_iter()
.map(|(track, uuid)| AlbumTrack { disc, track, uuid })
.collect::<Vec<_>>();
vec.append(&mut tracks);
vec.append(&mut tracks);
}
vec.into_iter()
}
@ -654,7 +650,7 @@ impl IntoIterator for Album {
pub struct AlbumTrack {
disc: u16,
track: u16,
uuid: Uuid
uuid: Uuid,
}
impl AlbumTrack {
@ -820,7 +816,10 @@ impl MusicLibrary {
}
/// 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 errors = 0;
for target_file in WalkDir::new(target_path)
@ -878,15 +877,21 @@ impl MusicLibrary {
pub fn remove_missing(&mut self) {
let target_removals = Arc::new(Mutex::new(Vec::new()));
self.library.par_iter().for_each(|t|{
self.library.par_iter().for_each(|t| {
for location in &t.location {
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 {
self.remove_uri(&location).unwrap();
}
@ -1119,16 +1124,19 @@ impl MusicLibrary {
.unwrap_or(&String::new())
.parse::<u16>()
.unwrap_or_default(),
song.uuid
song.uuid,
)),
None => {
album.discs.insert(disc_num, vec![(
song.get_tag(&Tag::Track)
.unwrap_or(&String::new())
.parse::<u16>()
.unwrap_or_default(),
song.uuid
)]);
album.discs.insert(
disc_num,
vec![(
song.get_tag(&Tag::Track)
.unwrap_or(&String::new())
.parse::<u16>()
.unwrap_or_default(),
song.uuid,
)],
);
}
},
// 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())
.parse::<u16>()
.unwrap_or_default(),
song.uuid
)])]),
song.uuid,
)],
)]),
cover: album_art.cloned(),
};
albums.insert(album_title, new_album);
}
}
paths.insert(song.uuid, song.primary_uri().unwrap());
}
@ -1162,19 +1170,18 @@ impl MusicLibrary {
let num_a = a.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
num_a.cmp(&num_b)
} else {
// If parsing doesn't succeed, compare the locations
let a = match paths.get_key_value(&a.1) {
Some((_, (uri, _))) => uri,
None => return Ordering::Equal
None => return Ordering::Equal,
};
let b = match paths.get_key_value(&b.1) {
Some((_, (uri, _))) => uri,
None => return Ordering::Equal
None => return Ordering::Equal,
};
a.as_uri().cmp(&b.as_uri())
@ -1217,13 +1224,20 @@ mod test {
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]
fn library_init() {
let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
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);
}
}