mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 17:42:55 -05:00
cobbled together some test code for controller and ran cargo fmt
This commit is contained in:
parent
be9f28e38f
commit
15988ae808
12 changed files with 515 additions and 227 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,14 +46,14 @@ impl TryInto<gst::State> for PlayerState {
|
||||||
enum PlaybackInfo {
|
enum PlaybackInfo {
|
||||||
Idle,
|
Idle,
|
||||||
Switching,
|
Switching,
|
||||||
Playing{
|
Playing {
|
||||||
start: Duration,
|
start: Duration,
|
||||||
end: Duration,
|
end: Duration,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// 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)?;
|
||||||
|
@ -472,7 +483,7 @@ fn playback_monitor(
|
||||||
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
|
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
|
||||||
|
|
||||||
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(2000);
|
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() {
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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
|
||||||
|
@ -176,7 +181,7 @@ impl ExternalLibrary for ITunesLibrary {
|
||||||
favorited: track.favorited,
|
favorited: track.favorited,
|
||||||
banned: if track.banned {
|
banned: if track.banned {
|
||||||
Some(BannedType::All)
|
Some(BannedType::All)
|
||||||
}else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
rating: track.rating,
|
rating: track.rating,
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -878,15 +877,21 @@ impl MusicLibrary {
|
||||||
|
|
||||||
pub fn remove_missing(&mut self) {
|
pub fn remove_missing(&mut self) {
|
||||||
let target_removals = Arc::new(Mutex::new(Vec::new()));
|
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 {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue