ran cargo fmt

This commit is contained in:
MrDulfin 2025-01-01 04:51:24 -05:00
parent 195eed367b
commit 2ac3acdf5a
17 changed files with 1427 additions and 1061 deletions

View file

@ -29,7 +29,12 @@ impl Default for ConfigLibrary {
} }
impl ConfigLibrary { impl ConfigLibrary {
pub fn new(path: PathBuf, name: String, scan_folders: Option<Vec<PathBuf>>, uuid: Option<Uuid>) -> Self { pub fn new(
path: PathBuf,
name: String,
scan_folders: Option<Vec<PathBuf>>,
uuid: Option<Uuid>,
) -> Self {
ConfigLibrary { ConfigLibrary {
name, name,
path, path,
@ -197,7 +202,7 @@ pub mod tests {
PathBuf::from("test-config/library"), PathBuf::from("test-config/library"),
String::from("library"), String::from("library"),
None, None,
None None,
); );
let mut config = Config { let mut config = Config {
path: PathBuf::from("test-config/config_test.json"), path: PathBuf::from("test-config/config_test.json"),

View file

@ -1,23 +1,23 @@
#![allow(while_true)] #![allow(while_true)]
pub mod music_storage { pub mod music_storage {
pub mod library; pub mod library;
pub mod music_collection; pub mod music_collection;
pub mod playlist; pub mod playlist;
mod utils; mod utils;
#[allow(dead_code)] #[allow(dead_code)]
pub mod db_reader; pub mod db_reader;
} }
pub mod music_controller { pub mod music_controller {
pub mod connections; pub mod connections;
pub mod controller; pub mod controller;
pub mod controller_handle; pub mod controller_handle;
pub mod queue; pub mod library_command;
pub mod player_command; pub mod player_command;
pub mod player_monitor; pub mod player_monitor;
pub mod queue_command; pub mod queue;
pub mod library_command; pub mod queue_command;
} }
pub mod config; pub mod config;

View file

@ -1,4 +1,11 @@
use std::{sync::{atomic::{AtomicBool, Ordering}, Arc}, thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}}; use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::sleep,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use chrono::TimeDelta; use chrono::TimeDelta;
use crossbeam::{scope, select}; use crossbeam::{scope, select};
@ -8,7 +15,10 @@ use listenbrainz::ListenBrainz;
use parking_lot::RwLock; use parking_lot::RwLock;
use prismriver::State as PrismState; use prismriver::State as PrismState;
use crate::{config::Config, music_storage::library::{Song, Tag}}; use crate::{
config::Config,
music_storage::library::{Song, Tag},
};
use super::controller::Controller; use super::controller::Controller;
@ -16,7 +26,7 @@ use super::controller::Controller;
pub(super) enum ConnectionsNotification { pub(super) enum ConnectionsNotification {
Playback { Playback {
position: Option<TimeDelta>, position: Option<TimeDelta>,
duration: Option<TimeDelta> duration: Option<TimeDelta>,
}, },
StateChange(PrismState), StateChange(PrismState),
SongChange(Song), SongChange(Song),
@ -30,10 +40,9 @@ pub struct ConnectionsInput {
pub(super) struct ControllerConnections { pub(super) struct ControllerConnections {
pub notifications_tx: Receiver<ConnectionsNotification>, pub notifications_tx: Receiver<ConnectionsNotification>,
pub inner: ConnectionsInput pub inner: ConnectionsInput,
} }
static DC_ACTIVE: AtomicBool = AtomicBool::new(false); static DC_ACTIVE: AtomicBool = AtomicBool::new(false);
static LB_ACTIVE: AtomicBool = AtomicBool::new(false); static LB_ACTIVE: AtomicBool = AtomicBool::new(false);
@ -41,11 +50,11 @@ impl Controller {
pub(super) fn handle_connections( pub(super) fn handle_connections(
config: Arc<RwLock<Config>>, config: Arc<RwLock<Config>>,
ControllerConnections { ControllerConnections {
notifications_tx, notifications_tx,
inner: ConnectionsInput { inner: ConnectionsInput {
discord_rpc_client_id discord_rpc_client_id,
}, },
}: ControllerConnections }: ControllerConnections,
) { ) {
let (dc_state_rx, dc_state_tx) = bounded::<PrismState>(1); let (dc_state_rx, dc_state_tx) = bounded::<PrismState>(1);
let (dc_song_rx, dc_song_tx) = bounded::<Song>(1); let (dc_song_rx, dc_song_tx) = bounded::<Song>(1);
@ -53,49 +62,73 @@ impl Controller {
let (lb_eos_rx, lb_eos_tx) = bounded::<()>(1); let (lb_eos_rx, lb_eos_tx) = bounded::<()>(1);
scope(|s| { scope(|s| {
s.builder().name("Notifications Sorter".to_string()).spawn(|_| { s.builder()
use ConnectionsNotification::*; .name("Notifications Sorter".to_string())
while true { .spawn(|_| {
match notifications_tx.recv().unwrap() { use ConnectionsNotification::*;
Playback { .. } => {} while true {
StateChange(state) => { match notifications_tx.recv().unwrap() {
if DC_ACTIVE.load(Ordering::Relaxed) { dc_state_rx.send(state.clone()).unwrap(); } Playback { .. } => {}
} StateChange(state) => {
SongChange(song) => { if DC_ACTIVE.load(Ordering::Relaxed) {
if DC_ACTIVE.load(Ordering::Relaxed) { dc_song_rx.send(song.clone()).unwrap(); } dc_state_rx.send(state.clone()).unwrap();
if LB_ACTIVE.load(Ordering::Relaxed) { lb_song_rx.send(song).unwrap(); } }
} }
EOS => { SongChange(song) => {
if LB_ACTIVE.load(Ordering::Relaxed) { lb_eos_rx.send(()).unwrap(); } if DC_ACTIVE.load(Ordering::Relaxed) {
dc_song_rx.send(song.clone()).unwrap();
}
if LB_ACTIVE.load(Ordering::Relaxed) {
lb_song_rx.send(song).unwrap();
}
}
EOS => {
if LB_ACTIVE.load(Ordering::Relaxed) {
lb_eos_rx.send(()).unwrap();
}
}
} }
} }
} })
}).unwrap(); .unwrap();
if let Some(client_id) = discord_rpc_client_id { if let Some(client_id) = discord_rpc_client_id {
s.builder().name("Discord RPC Handler".to_string()).spawn(move |_| { s.builder()
Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx); .name("Discord RPC Handler".to_string())
}).unwrap(); .spawn(move |_| {
Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx);
})
.unwrap();
}; };
if let Some(token) = config.read().connections.listenbrainz_token.clone() { if let Some(token) = config.read().connections.listenbrainz_token.clone() {
s.builder().name("ListenBrainz Handler".to_string()).spawn(move |_| { s.builder()
Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx); .name("ListenBrainz Handler".to_string())
}).unwrap(); .spawn(move |_| {
Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx);
})
.unwrap();
} }
}).unwrap(); })
.unwrap();
} }
fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<PrismState>) { fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<PrismState>) {
// TODO: Handle seeking position change and pause // TODO: Handle seeking position change and pause
let mut client = discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None); let mut client =
discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None);
client.start(); client.start();
while !Client::is_ready() { sleep(Duration::from_millis(100)); } while !Client::is_ready() {
sleep(Duration::from_millis(100));
}
println!("discord connected"); println!("discord connected");
let mut state = "Started".to_string(); let mut state = "Started".to_string();
let mut song: Option<Song> = None; let mut song: Option<Song> = None;
let mut now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?").as_secs(); let mut now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards?")
.as_secs();
DC_ACTIVE.store(true, Ordering::Relaxed); DC_ACTIVE.store(true, Ordering::Relaxed);
while true { while true {
@ -121,38 +154,47 @@ impl Controller {
default(Duration::from_millis(99)) => () default(Duration::from_millis(99)) => ()
} }
client.set_activity(|activity| { client
let a = activity.state( .set_activity(|activity| {
song.as_ref().map_or(String::new(), |s| format!( let a = activity
"{}{}{}", .state(song.as_ref().map_or(String::new(), |s| {
s.get_tag(&Tag::Artist).map_or(String::new(), |album| album.clone()), format!(
if s.get_tag(&Tag::Album).is_some() && s.get_tag(&Tag::Artist).is_some() { " - " } else { "" }, "{}{}{}",
s.get_tag(&Tag::Album).map_or(String::new(), |album| album.clone()) s.get_tag(&Tag::Artist)
) .map_or(String::new(), |album| album.clone()),
) if s.get_tag(&Tag::Album).is_some()
)._type(discord_presence::models::ActivityType::Listening) && s.get_tag(&Tag::Artist).is_some()
.details( {
if let Some(song) = song { " - "
song.get_tag(&Tag::Title).map_or(String::from("Unknown Title"), |title| title.clone()) } else {
} else { ""
String::new() },
} s.get_tag(&Tag::Album)
); .map_or(String::new(), |album| album.clone())
if let Some(s) = song { )
if state.as_str() == "Playing" { }))
a.timestamps(|timestamps| { ._type(discord_presence::models::ActivityType::Listening)
timestamps.start(now) .details(if let Some(song) = song {
.end(now + s.duration.as_secs()) song.get_tag(&Tag::Title)
}) .map_or(String::from("Unknown Title"), |title| title.clone())
} else {
String::new()
});
if let Some(s) = song {
if state.as_str() == "Playing" {
a.timestamps(|timestamps| {
timestamps.start(now).end(now + s.duration.as_secs())
})
} else {
a
}
} else { } else {
a a
} }
} else { .assets(|a| a.large_text(state.clone()))
a .instance(true)
}.assets(|a| { })
a.large_text(state.clone()) .unwrap();
}).instance(true)
}).unwrap();
} }
DC_ACTIVE.store(false, Ordering::Relaxed); DC_ACTIVE.store(false, Ordering::Relaxed);
} }
@ -225,13 +267,17 @@ mod test_super {
let (song_rx, song_tx) = unbounded(); let (song_rx, song_tx) = unbounded();
let (eos_rx, eos_tx) = unbounded(); let (eos_rx, eos_tx) = unbounded();
let (config, lib ) = read_config_lib(); let (config, lib) = read_config_lib();
song_rx.send(lib.library[0].clone()).unwrap(); song_rx.send(lib.library[0].clone()).unwrap();
spawn(|| { spawn(|| {
Controller::listenbrainz_scrobble(config.connections.listenbrainz_token.unwrap().as_str(), song_tx, eos_tx); Controller::listenbrainz_scrobble(
config.connections.listenbrainz_token.unwrap().as_str(),
song_tx,
eos_tx,
);
}); });
sleep(Duration::from_secs(10)); sleep(Duration::from_secs(10));
eos_rx.send(()).unwrap(); eos_rx.send(()).unwrap();
sleep(Duration::from_secs(10)); sleep(Duration::from_secs(10));
} }
} }

View file

@ -1,357 +1,362 @@
//! 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
use chrono::TimeDelta; use chrono::TimeDelta;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use kushi::Queue; use kushi::Queue;
use kushi::{QueueError, QueueItem}; use kushi::{QueueError, QueueItem};
use parking_lot::RwLock; use parking_lot::RwLock;
use prismriver::{Error as PrismError, Prismriver}; use prismriver::{Error as PrismError, Prismriver};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::to_string_pretty; use serde_json::to_string_pretty;
use std::error::Error; use std::error::Error;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
use crate::config::ConfigError; use crate::config::ConfigError;
use crate::music_storage::library::Song; use crate::music_storage::library::Song;
use crate::music_storage::playlist::{ExternalPlaylist, Playlist}; use crate::music_storage::playlist::{ExternalPlaylist, Playlist};
use crate::{config::Config, music_storage::library::MusicLibrary}; use crate::{config::Config, music_storage::library::MusicLibrary};
use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections}; use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections};
use super::controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}; use super::controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput};
use super::queue::{QueueAlbum, QueueSong}; use super::queue::{QueueAlbum, QueueSong};
pub struct Controller(); pub struct Controller();
type QueueItem_ = QueueItem<QueueSong, QueueAlbum>; type QueueItem_ = QueueItem<QueueSong, QueueAlbum>;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ControllerError { pub enum ControllerError {
#[error("{0:?}")] #[error("{0:?}")]
QueueError(#[from] QueueError), QueueError(#[from] QueueError),
#[error("{0:?}")] #[error("{0:?}")]
PlayerError(#[from] prismriver::Error), PlayerError(#[from] prismriver::Error),
#[error("{0:?}")] #[error("{0:?}")]
ConfigError(#[from] ConfigError), ConfigError(#[from] ConfigError),
} }
// TODO: move this to a different location to be used elsewhere // TODO: move this to a different location to be used elsewhere
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
#[non_exhaustive] #[non_exhaustive]
pub enum PlayerLocation { pub enum PlayerLocation {
Test, Test,
Library, Library,
Playlist(Uuid), Playlist(Uuid),
File, File,
Custom, Custom,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MailMan<Tx: Send, Rx: Send> { pub struct MailMan<Tx: Send, Rx: Send> {
tx: async_channel::Sender<Tx>, tx: async_channel::Sender<Tx>,
rx: async_channel::Receiver<Rx>, rx: async_channel::Receiver<Rx>,
} }
impl<Tx: Send, Rx: Send> MailMan<Tx, Rx> { impl<Tx: Send, Rx: Send> MailMan<Tx, Rx> {
pub fn double() -> (MailMan<Tx, Rx>, MailMan<Rx, Tx>) { pub fn double() -> (MailMan<Tx, Rx>, MailMan<Rx, Tx>) {
let (tx, rx) = async_channel::unbounded::<Tx>(); let (tx, rx) = async_channel::unbounded::<Tx>();
let (tx1, rx1) = async_channel::unbounded::<Rx>(); let (tx1, rx1) = async_channel::unbounded::<Rx>();
(MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx }) (MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx })
} }
pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError<Tx>> { pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError<Tx>> {
self.tx.send(mail).await self.tx.send(mail).await
} }
pub async fn recv(&self) -> Result<Rx, async_channel::RecvError> { pub async fn recv(&self) -> Result<Rx, async_channel::RecvError> {
self.rx.recv().await self.rx.recv().await
} }
} }
#[derive(Debug, PartialEq, PartialOrd, Clone)] #[derive(Debug, PartialEq, PartialOrd, Clone)]
pub enum PlayerCommand { pub enum PlayerCommand {
NextSong, NextSong,
PrevSong, PrevSong,
Pause, Pause,
Play, Play,
Stop, Stop,
Seek(i64), Seek(i64),
Enqueue(usize), Enqueue(usize),
SetVolume(f32), SetVolume(f32),
PlayNow(Uuid, PlayerLocation), PlayNow(Uuid, PlayerLocation),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum PlayerResponse { pub enum PlayerResponse {
Empty(Result<(), PlayerError>), Empty(Result<(), PlayerError>),
NowPlaying(Result<Song, QueueError>) NowPlaying(Result<Song, QueueError>),
} }
#[derive(Error, Debug, PartialEq, Clone)] #[derive(Error, Debug, PartialEq, Clone)]
pub enum PlayerError { pub enum PlayerError {
#[error("{0}")] #[error("{0}")]
QueueError(#[from] QueueError), QueueError(#[from] QueueError),
#[error("{0}")] #[error("{0}")]
Prismriver(#[from] PrismError), Prismriver(#[from] PrismError),
} }
#[derive(Debug, PartialEq, PartialOrd, Clone)] #[derive(Debug, PartialEq, PartialOrd, Clone)]
pub enum LibraryCommand { pub enum LibraryCommand {
Song(Uuid), Song(Uuid),
AllSongs, AllSongs,
GetLibrary, GetLibrary,
ExternalPlaylist(Uuid), ExternalPlaylist(Uuid),
Playlist(Uuid), Playlist(Uuid),
ImportM3UPlayList(PathBuf), ImportM3UPlayList(PathBuf),
Save, Save,
Playlists, Playlists,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum LibraryResponse { pub enum LibraryResponse {
Ok, Ok,
Song(Song, usize), Song(Song, usize),
AllSongs(Vec<Song>), AllSongs(Vec<Song>),
Library(MusicLibrary), Library(MusicLibrary),
ExternalPlaylist(ExternalPlaylist), ExternalPlaylist(ExternalPlaylist),
Playlist(Playlist), Playlist(Playlist),
ImportM3UPlayList(Uuid, String), ImportM3UPlayList(Uuid, String),
Playlists(Vec<(Uuid, String)>), Playlists(Vec<(Uuid, String)>),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum QueueCommand { pub enum QueueCommand {
Append(QueueItem_, bool), Append(QueueItem_, bool),
Next, Next,
Prev, Prev,
GetIndex(usize), GetIndex(usize),
NowPlaying, NowPlaying,
Get, Get,
Clear, Clear,
Remove(usize), Remove(usize),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum QueueResponse { pub enum QueueResponse {
Empty(Result<(), QueueError>), Empty(Result<(), QueueError>),
Item(Result<QueueItem_, QueueError>), Item(Result<QueueItem_, QueueError>),
GetAll(Vec<QueueItem_>), GetAll(Vec<QueueItem_>),
} }
pub struct ControllerInput {
pub struct ControllerInput { player_mail: (
player_mail: ( async_channel::Sender<PlayerCommandInput>,
async_channel::Sender<PlayerCommandInput>, async_channel::Receiver<PlayerCommandInput>,
async_channel::Receiver<PlayerCommandInput> ),
), lib_mail: (
lib_mail: ( async_channel::Sender<LibraryCommandInput>,
async_channel::Sender<LibraryCommandInput>, async_channel::Receiver<LibraryCommandInput>,
async_channel::Receiver<LibraryCommandInput> ),
), queue_mail: (
queue_mail: ( async_channel::Sender<QueueCommandInput>,
async_channel::Sender<QueueCommandInput>, async_channel::Receiver<QueueCommandInput>,
async_channel::Receiver<QueueCommandInput> ),
), library: MusicLibrary,
library: MusicLibrary, config: Arc<RwLock<Config>>,
config: Arc<RwLock<Config>>, playback_info: Arc<AtomicCell<PlaybackInfo>>,
playback_info: Arc<AtomicCell<PlaybackInfo>>, notify_next_song: Sender<Song>,
notify_next_song: Sender<Song>, connections: Option<ConnectionsInput>,
connections: Option<ConnectionsInput> }
}
pub struct ControllerHandle {
pub struct ControllerHandle { pub(super) lib_mail_rx: async_channel::Sender<LibraryCommandInput>,
pub(super) lib_mail_rx: async_channel::Sender<LibraryCommandInput>, pub(super) player_mail_rx: async_channel::Sender<PlayerCommandInput>,
pub(super) player_mail_rx: async_channel::Sender<PlayerCommandInput>, pub(super) queue_mail_rx: async_channel::Sender<QueueCommandInput>,
pub(super) queue_mail_rx: async_channel::Sender<QueueCommandInput>, }
}
impl ControllerHandle {
impl ControllerHandle { pub fn new(
pub fn new(library: MusicLibrary, config: Arc<RwLock<Config>>, connections: Option<ConnectionsInput>) -> (Self, ControllerInput, Arc<AtomicCell<PlaybackInfo>>, Receiver<Song>) { library: MusicLibrary,
let (lib_mail_rx, lib_mail_tx) = async_channel::unbounded(); config: Arc<RwLock<Config>>,
let (player_mail_rx, player_mail_tx) = async_channel::unbounded(); connections: Option<ConnectionsInput>,
let (queue_mail_rx, queue_mail_tx)= async_channel::unbounded(); ) -> (
let playback_info = Arc::new(AtomicCell::new(PlaybackInfo::default())); Self,
let notify_next_song = crossbeam::channel::unbounded::<Song>(); ControllerInput,
( Arc<AtomicCell<PlaybackInfo>>,
ControllerHandle { Receiver<Song>,
lib_mail_rx: lib_mail_rx.clone(), ) {
player_mail_rx: player_mail_rx.clone(), let (lib_mail_rx, lib_mail_tx) = async_channel::unbounded();
queue_mail_rx: queue_mail_rx.clone() let (player_mail_rx, player_mail_tx) = async_channel::unbounded();
}, let (queue_mail_rx, queue_mail_tx) = async_channel::unbounded();
ControllerInput { let playback_info = Arc::new(AtomicCell::new(PlaybackInfo::default()));
player_mail: (player_mail_rx, player_mail_tx), let notify_next_song = crossbeam::channel::unbounded::<Song>();
lib_mail: (lib_mail_rx, lib_mail_tx), (
queue_mail: (queue_mail_rx, queue_mail_tx), ControllerHandle {
library, lib_mail_rx: lib_mail_rx.clone(),
config, player_mail_rx: player_mail_rx.clone(),
playback_info: Arc::clone(&playback_info), queue_mail_rx: queue_mail_rx.clone(),
notify_next_song: notify_next_song.0, },
connections, ControllerInput {
}, player_mail: (player_mail_rx, player_mail_tx),
playback_info, lib_mail: (lib_mail_rx, lib_mail_tx),
notify_next_song.1 queue_mail: (queue_mail_rx, queue_mail_tx),
) library,
} config,
} playback_info: Arc::clone(&playback_info),
notify_next_song: notify_next_song.0,
#[derive(Debug, Default, Serialize, Deserialize, Clone)] connections,
pub struct ControllerState { },
pub(super) path: PathBuf, playback_info,
pub(super) volume: f32, notify_next_song.1,
pub(super) now_playing: Uuid, )
} }
}
impl ControllerState {
pub(super) fn new(path: PathBuf) -> Self { #[derive(Debug, Default, Serialize, Deserialize, Clone)]
ControllerState { pub struct ControllerState {
path, pub(super) path: PathBuf,
volume: 0.35, pub(super) volume: f32,
..Default::default() pub(super) now_playing: Uuid,
} }
}
impl ControllerState {
pub(super) fn write_file(&self) -> Result<(), std::io::Error> { pub(super) fn new(path: PathBuf) -> Self {
OpenOptions::new() ControllerState {
.truncate(true) path,
.create(true) volume: 0.35,
.write(true) ..Default::default()
.open(&self.path) }
.unwrap() }
.write_all(&to_string_pretty(self)?.into_bytes())?;
Ok(()) pub(super) fn write_file(&self) -> Result<(), std::io::Error> {
} OpenOptions::new()
.truncate(true)
pub(super) fn read_file(path: impl AsRef<Path>) -> Result<Self, std::io::Error> { .create(true)
let state = serde_json::from_str(&std::fs::read_to_string(path)?)?; .write(true)
Ok(state) .open(&self.path)
} .unwrap()
} .write_all(&to_string_pretty(self)?.into_bytes())?;
Ok(())
#[allow(unused_variables)] }
impl Controller {
pub async fn start( pub(super) fn read_file(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
ControllerInput { let state = serde_json::from_str(&std::fs::read_to_string(path)?)?;
player_mail, Ok(state)
lib_mail, }
queue_mail, }
mut library,
config, #[allow(unused_variables)]
playback_info, impl Controller {
notify_next_song, pub async fn start(
connections, ControllerInput {
}: ControllerInput player_mail,
) -> Result<(), Box<dyn Error>> { lib_mail,
let queue: Queue<QueueSong, QueueAlbum> = Queue { queue_mail,
items: Vec::new(), mut library,
played: Vec::new(), config,
loop_: false, playback_info,
shuffle: None, notify_next_song,
}; connections,
}: ControllerInput,
let state = { ) -> Result<(), Box<dyn Error>> {
let path = &config.read().state_path; let queue: Queue<QueueSong, QueueAlbum> = Queue {
if let Ok(state) = ControllerState::read_file(path) { items: Vec::new(),
state played: Vec::new(),
} else { loop_: false,
ControllerState::new(path.clone()) shuffle: None,
} };
};
let state = {
std::thread::scope(|scope| { let path = &config.read().state_path;
let player = Prismriver::new(); if let Ok(state) = ControllerState::read_file(path) {
let player_state = player.state.clone(); state
let player_timing = player.get_timing_recv(); } else {
let finished_tx = player.get_finished_recv(); ControllerState::new(path.clone())
let (notifications_rx, notifications_tx) = crossbeam_channel::unbounded::<ConnectionsNotification>(); }
};
let a = scope.spawn({
let queue_mail = queue_mail.clone(); std::thread::scope(|scope| {
let _notifications_rx = notifications_rx.clone(); let player = Prismriver::new();
let _config = config.clone(); let player_state = player.state.clone();
move || { let player_timing = player.get_timing_recv();
futures::executor::block_on(async { let finished_tx = player.get_finished_recv();
moro::async_scope!(|scope| { let (notifications_rx, notifications_tx) =
println!("async scope created"); crossbeam_channel::unbounded::<ConnectionsNotification>();
let _lib_mail = lib_mail.0.clone(); let a = scope.spawn({
let _queue_mail = queue_mail.0.clone(); let queue_mail = queue_mail.clone();
scope let _notifications_rx = notifications_rx.clone();
.spawn(async move { let _config = config.clone();
Controller::player_command_loop( move || {
player, futures::executor::block_on(async {
player_mail.1, moro::async_scope!(|scope| {
_queue_mail, println!("async scope created");
_lib_mail,
_notifications_rx, let _lib_mail = lib_mail.0.clone();
state, let _queue_mail = queue_mail.0.clone();
) scope.spawn(async move {
.await Controller::player_command_loop(
.unwrap(); player,
}); player_mail.1,
scope _queue_mail,
.spawn(async { _lib_mail,
Controller::library_loop( _notifications_rx,
lib_mail.1, state,
&mut library, )
_config, .await
) .unwrap();
.await });
.unwrap(); scope.spawn(async {
}); Controller::library_loop(lib_mail.1, &mut library, _config)
}) .await
.await; .unwrap();
}) });
} })
}); .await;
})
let b = scope.spawn(|| { }
futures::executor::block_on(async { });
Controller::queue_loop(queue, queue_mail.1).await;
}) let b = scope.spawn(|| {
}); futures::executor::block_on(async {
Controller::queue_loop(queue, queue_mail.1).await;
let c = scope.spawn(|| { })
Controller::player_monitor_loop( });
player_state,
player_timing, let c = scope.spawn(|| {
finished_tx, Controller::player_monitor_loop(
player_mail.0, player_state,
notify_next_song, player_timing,
notifications_rx, finished_tx,
playback_info, player_mail.0,
).unwrap(); notify_next_song,
}); notifications_rx,
playback_info,
if let Some(inner) = connections { )
dbg!(&inner); .unwrap();
let d = scope.spawn(|| { });
Controller::handle_connections(
config, if let Some(inner) = connections {
ControllerConnections { dbg!(&inner);
notifications_tx, let d = scope.spawn(|| {
inner, Controller::handle_connections(
}); config,
}); ControllerConnections {
} notifications_tx,
a.join().unwrap(); inner,
b.join().unwrap(); },
c.join().unwrap(); );
}); });
}
Ok(()) a.join().unwrap();
} b.join().unwrap();
} c.join().unwrap();
});
#[derive(Debug, Default, Serialize, Clone)]
pub struct PlaybackInfo { Ok(())
pub position: Option<TimeDelta>, }
pub duration: Option<TimeDelta>, }
}
#[derive(Debug, Default, Serialize, Clone)]
pub struct PlaybackInfo {
pub position: Option<TimeDelta>,
pub duration: Option<TimeDelta>,
}

View file

@ -6,7 +6,13 @@ use uuid::Uuid;
use crate::music_storage::{library::Song, playlist::ExternalPlaylist}; use crate::music_storage::{library::Song, playlist::ExternalPlaylist};
use super::{controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerCommand, PlayerError, PlayerLocation, PlayerResponse, QueueCommand, QueueResponse}, queue::{QueueAlbum, QueueSong}}; use super::{
controller::{
ControllerHandle, LibraryCommand, LibraryResponse, PlayerCommand, PlayerError,
PlayerLocation, PlayerResponse, QueueCommand, QueueResponse,
},
queue::{QueueAlbum, QueueSong},
};
impl ControllerHandle { impl ControllerHandle {
// The Library Section // The Library Section
@ -59,12 +65,17 @@ impl ControllerHandle {
pub async fn playlist_import_m3u(&self, path: PathBuf) -> Result<(Uuid, String), ()> { pub async fn playlist_import_m3u(&self, path: PathBuf) -> Result<(Uuid, String), ()> {
let (command, tx) = LibraryCommandInput::command(LibraryCommand::ImportM3UPlayList(path)); let (command, tx) = LibraryCommandInput::command(LibraryCommand::ImportM3UPlayList(path));
self.lib_mail_rx.send(command).await.unwrap(); self.lib_mail_rx.send(command).await.unwrap();
let LibraryResponse::ImportM3UPlayList(uuid, name) = tx.recv().await.unwrap() else { unreachable!("It has been reached") }; let LibraryResponse::ImportM3UPlayList(uuid, name) = tx.recv().await.unwrap() else {
unreachable!("It has been reached")
};
Ok((uuid, name)) Ok((uuid, name))
} }
// The Queue Section // The Queue Section
pub async fn queue_append(&self, item: QueueItem<QueueSong, QueueAlbum>) -> Result<(), QueueError> { pub async fn queue_append(
&self,
item: QueueItem<QueueSong, QueueAlbum>,
) -> Result<(), QueueError> {
let (command, tx) = QueueCommandInput::command(QueueCommand::Append(item, true)); let (command, tx) = QueueCommandInput::command(QueueCommand::Append(item, true));
self.queue_mail_rx.send(command).await.unwrap(); self.queue_mail_rx.send(command).await.unwrap();
let QueueResponse::Empty(res) = tx.recv().await.unwrap() else { let QueueResponse::Empty(res) = tx.recv().await.unwrap() else {
@ -73,7 +84,10 @@ impl ControllerHandle {
res res
} }
pub async fn queue_remove(&self, index: usize) -> Result<QueueItem<QueueSong, QueueAlbum>, QueueError> { pub async fn queue_remove(
&self,
index: usize,
) -> Result<QueueItem<QueueSong, QueueAlbum>, QueueError> {
let (command, tx) = QueueCommandInput::command(QueueCommand::Remove(index)); let (command, tx) = QueueCommandInput::command(QueueCommand::Remove(index));
self.queue_mail_rx.send(command).await.unwrap(); self.queue_mail_rx.send(command).await.unwrap();
let QueueResponse::Item(res) = tx.recv().await.unwrap() else { let QueueResponse::Item(res) = tx.recv().await.unwrap() else {
@ -166,7 +180,7 @@ impl ControllerHandle {
pub(super) struct LibraryCommandInput { pub(super) struct LibraryCommandInput {
pub res_rx: Sender<LibraryResponse>, pub res_rx: Sender<LibraryResponse>,
pub command: LibraryCommand pub command: LibraryCommand,
} }
impl LibraryCommandInput { impl LibraryCommandInput {
@ -175,16 +189,16 @@ impl LibraryCommandInput {
( (
Self { Self {
res_rx: rx, res_rx: rx,
command command,
}, },
tx tx,
) )
} }
} }
pub(super) struct QueueCommandInput { pub(super) struct QueueCommandInput {
pub res_rx: Sender<QueueResponse>, pub res_rx: Sender<QueueResponse>,
pub command: QueueCommand pub command: QueueCommand,
} }
impl QueueCommandInput { impl QueueCommandInput {
@ -193,16 +207,16 @@ impl QueueCommandInput {
( (
Self { Self {
res_rx: rx, res_rx: rx,
command command,
}, },
tx tx,
) )
} }
} }
pub(super) struct PlayerCommandInput { pub(super) struct PlayerCommandInput {
pub res_rx: Sender<PlayerResponse>, pub res_rx: Sender<PlayerResponse>,
pub command: PlayerCommand pub command: PlayerCommand,
} }
impl PlayerCommandInput { impl PlayerCommandInput {
@ -211,9 +225,9 @@ impl PlayerCommandInput {
( (
Self { Self {
res_rx: rx, res_rx: rx,
command command,
}, },
tx tx,
) )
} }
} }

View file

@ -3,9 +3,18 @@ use std::sync::Arc;
use parking_lot::RwLock; use parking_lot::RwLock;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
use crate::{config::Config, music_storage::{library::MusicLibrary, playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}}}; use crate::{
config::Config,
music_storage::{
library::MusicLibrary,
playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem},
},
};
use super::{controller::{Controller, LibraryCommand, LibraryResponse}, controller_handle::LibraryCommandInput}; use super::{
controller::{Controller, LibraryCommand, LibraryResponse},
controller_handle::LibraryCommandInput,
};
impl Controller { impl Controller {
pub(super) async fn library_loop( pub(super) async fn library_loop(
@ -18,36 +27,73 @@ impl Controller {
match command { match command {
LibraryCommand::Song(uuid) => { LibraryCommand::Song(uuid) => {
let (song, i) = library.query_uuid(&uuid).unwrap(); let (song, i) = library.query_uuid(&uuid).unwrap();
res_rx.send(LibraryResponse::Song(song.clone(), i)).await.unwrap(); res_rx
.send(LibraryResponse::Song(song.clone(), i))
.await
.unwrap();
} }
LibraryCommand::AllSongs => { LibraryCommand::AllSongs => {
res_rx.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap(); res_rx
}, .send(LibraryResponse::AllSongs(library.library.clone()))
.await
.unwrap();
}
LibraryCommand::ExternalPlaylist(uuid) => { LibraryCommand::ExternalPlaylist(uuid) => {
let playlist = library.query_playlist_uuid(&uuid).unwrap(); let playlist = library.query_playlist_uuid(&uuid).unwrap();
res_rx.send(LibraryResponse::ExternalPlaylist(ExternalPlaylist::from_playlist(playlist, library))).await.unwrap(); res_rx
.send(LibraryResponse::ExternalPlaylist(
ExternalPlaylist::from_playlist(playlist, library),
))
.await
.unwrap();
} }
LibraryCommand::ImportM3UPlayList(path) => { LibraryCommand::ImportM3UPlayList(path) => {
let playlist = Playlist::from_m3u(path, library).unwrap(); let playlist = Playlist::from_m3u(path, library).unwrap();
let uuid = playlist.uuid; let uuid = playlist.uuid;
let name = playlist.title.clone(); let name = playlist.title.clone();
library.playlists.items.push(PlaylistFolderItem::List(playlist)); library
.playlists
.items
.push(PlaylistFolderItem::List(playlist));
res_rx.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap(); res_rx
.send(LibraryResponse::ImportM3UPlayList(uuid, name))
.await
.unwrap();
} }
LibraryCommand::Save => { LibraryCommand::Save => {
library.save(config.read().libraries.get_library(&library.uuid).unwrap().path.clone()).unwrap(); library
.save(
config
.read()
.libraries
.get_library(&library.uuid)
.unwrap()
.path
.clone(),
)
.unwrap();
res_rx.send(LibraryResponse::Ok).await.unwrap(); res_rx.send(LibraryResponse::Ok).await.unwrap();
} }
LibraryCommand::Playlists => { LibraryCommand::Playlists => {
let mut lists = vec![]; let mut lists = vec![];
library.playlists.lists_recursive().into_par_iter().map(|list| (list.uuid, list.title.clone())).collect_into_vec(&mut lists); library
.playlists
.lists_recursive()
.into_par_iter()
.map(|list| (list.uuid, list.title.clone()))
.collect_into_vec(&mut lists);
res_rx.send(LibraryResponse::Playlists(lists)).await.unwrap(); res_rx
.send(LibraryResponse::Playlists(lists))
.await
.unwrap();
}
_ => {
todo!()
} }
_ => { todo!() }
} }
} }
Ok(()) Ok(())
} }
} }

View file

@ -3,9 +3,19 @@ use crossbeam_channel::Sender;
use kushi::{QueueItem, QueueItemType}; use kushi::{QueueItem, QueueItemType};
use prismriver::{Prismriver, Volume}; use prismriver::{Prismriver, Volume};
use crate::music_controller::{controller::{LibraryCommand, LibraryResponse}, queue::QueueSong}; use crate::music_controller::{
controller::{LibraryCommand, LibraryResponse},
queue::QueueSong,
};
use super::{connections::ConnectionsNotification, controller::{Controller, ControllerState, PlayerCommand, PlayerLocation, PlayerResponse, QueueCommand, QueueResponse}, controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}}; use super::{
connections::ConnectionsNotification,
controller::{
Controller, ControllerState, PlayerCommand, PlayerLocation, PlayerResponse, QueueCommand,
QueueResponse,
},
controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput},
};
impl Controller { impl Controller {
pub(super) async fn player_command_loop( pub(super) async fn player_command_loop(
@ -19,7 +29,7 @@ impl Controller {
player.set_volume(Volume::new(state.volume)); player.set_volume(Volume::new(state.volume));
'outer: while true { 'outer: while true {
let _mail = player_mail.recv().await; let _mail = player_mail.recv().await;
if let Ok(PlayerCommandInput {res_rx, command}) = _mail { if let Ok(PlayerCommandInput { res_rx, command }) = _mail {
match command { match command {
PlayerCommand::Play => { PlayerCommand::Play => {
player.play(); player.play();
@ -38,7 +48,10 @@ impl Controller {
PlayerCommand::Seek(time) => { PlayerCommand::Seek(time) => {
let res = player.seek_to(TimeDelta::milliseconds(time)); let res = player.seek_to(TimeDelta::milliseconds(time));
res_rx.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap(); res_rx
.send(PlayerResponse::Empty(res.map_err(|e| e.into())))
.await
.unwrap();
} }
PlayerCommand::SetVolume(volume) => { PlayerCommand::SetVolume(volume) => {
@ -57,63 +70,79 @@ impl Controller {
match tx.recv().await.unwrap() { match tx.recv().await.unwrap() {
QueueResponse::Item(Ok(item)) => { QueueResponse::Item(Ok(item)) => {
let uri = match &item.item { let uri = match &item.item {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, QueueItemType::Single(song) => {
song.song.primary_uri().unwrap().0
}
_ => unimplemented!(), _ => unimplemented!(),
}; };
let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap(); let prism_uri =
prismriver::utils::path_to_uri(&uri.as_path().unwrap())
.unwrap();
println!("Playing song at path: {:?}", prism_uri); println!("Playing song at path: {:?}", prism_uri);
// handle error here for unknown formats // handle error here for unknown formats
player.load_new(&prism_uri).unwrap(); player.load_new(&prism_uri).unwrap();
player.play(); player.play();
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; let QueueItemType::Single(np_song) = item.item else {
panic!("This is temporary, handle queueItemTypes at some point")
};
let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs); let (command, tx) =
LibraryCommandInput::command(LibraryCommand::AllSongs);
// Append next song in library // Append next song in library
lib_mail.send(command).await.unwrap(); lib_mail.send(command).await.unwrap();
let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else { let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap()
else {
continue; continue;
}; };
let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(np_song.song.uuid)); let (command, tx) = LibraryCommandInput::command(
LibraryCommand::Song(np_song.song.uuid),
);
lib_mail.send(command).await.unwrap(); lib_mail.send(command).await.unwrap();
let LibraryResponse::Song(_, i) = tx.recv().await.unwrap() else { let LibraryResponse::Song(_, i) = tx.recv().await.unwrap() else {
unreachable!() unreachable!()
}; };
if let Some(song) = songs.get(i + 49) { if let Some(song) = songs.get(i + 49) {
let (command, tx) = QueueCommandInput::command( let (command, tx) =
QueueCommand::Append( QueueCommandInput::command(QueueCommand::Append(
QueueItem::from_item_type( QueueItem::from_item_type(QueueItemType::Single(
QueueItemType::Single( QueueSong {
QueueSong { song: song.clone(),
song: song.clone(), location: np_song.location,
location: np_song.location },
} )),
) false,
), ));
false queue_mail.send(command).await.unwrap();
) let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap()
); else {
queue_mail.send(command).await
.unwrap();
let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap() else {
unreachable!() unreachable!()
}; };
} else { } else {
println!("Library Empty"); println!("Library Empty");
} }
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap(); res_rx
.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone())))
.await
.unwrap();
state.now_playing = np_song.song.uuid; state.now_playing = np_song.song.uuid;
_ = state.write_file(); _ = state.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap(); notify_connections_
} QueueResponse::Item(Err(e)) => { .send(ConnectionsNotification::SongChange(np_song.song))
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); .unwrap();
} }
_ => continue QueueResponse::Item(Err(e)) => {
res_rx
.send(PlayerResponse::NowPlaying(Err(e.into())))
.await
.unwrap();
}
_ => continue,
} }
} }
@ -123,60 +152,87 @@ impl Controller {
match tx.recv().await.unwrap() { match tx.recv().await.unwrap() {
QueueResponse::Item(Ok(item)) => { QueueResponse::Item(Ok(item)) => {
let uri = match &item.item { let uri = match &item.item {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, QueueItemType::Single(song) => {
song.song.primary_uri().unwrap().0
}
_ => unimplemented!(), _ => unimplemented!(),
}; };
let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap(); let prism_uri =
prismriver::utils::path_to_uri(&uri.as_path().unwrap())
.unwrap();
player.load_new(&prism_uri).unwrap(); player.load_new(&prism_uri).unwrap();
player.play(); player.play();
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; let QueueItemType::Single(np_song) = item.item else {
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap(); panic!("This is temporary, handle queueItemTypes at some point")
};
res_rx
.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone())))
.await
.unwrap();
state.now_playing = np_song.song.uuid; state.now_playing = np_song.song.uuid;
_ = state.write_file(); _ = state.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap(); notify_connections_
.send(ConnectionsNotification::SongChange(np_song.song))
.unwrap();
} }
QueueResponse::Item(Err(e)) => { QueueResponse::Item(Err(e)) => {
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); res_rx
.send(PlayerResponse::NowPlaying(Err(e.into())))
.await
.unwrap();
} }
_ => continue _ => continue,
} }
} }
PlayerCommand::Enqueue(index) => { PlayerCommand::Enqueue(index) => {
let (command, tx) = QueueCommandInput::command(QueueCommand::GetIndex(index)); let (command, tx) =
queue_mail QueueCommandInput::command(QueueCommand::GetIndex(index));
.send(command) queue_mail.send(command).await.unwrap();
.await
.unwrap();
match tx.recv().await.unwrap() { match tx.recv().await.unwrap() {
QueueResponse::Item(Ok(item)) => { QueueResponse::Item(Ok(item)) => {
match item.item { match item.item {
QueueItemType::Single(np_song) => { QueueItemType::Single(np_song) => {
let prism_uri = prismriver::utils::path_to_uri(&np_song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap(); let prism_uri = prismriver::utils::path_to_uri(
&np_song
.song
.primary_uri()
.unwrap()
.0
.as_path()
.unwrap(),
)
.unwrap();
player.load_new(&prism_uri).unwrap(); player.load_new(&prism_uri).unwrap();
player.play(); player.play();
state.now_playing = np_song.song.uuid; state.now_playing = np_song.song.uuid;
_ = state.write_file(); _ = state.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap(); notify_connections_
.send(ConnectionsNotification::SongChange(np_song.song))
.unwrap();
} }
_ => unimplemented!(), _ => unimplemented!(),
} }
res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
} }
QueueResponse::Item(Err(e)) => { QueueResponse::Item(Err(e)) => {
res_rx.send(PlayerResponse::Empty(Err(e.into()))).await.unwrap(); res_rx
.send(PlayerResponse::Empty(Err(e.into())))
.await
.unwrap();
} }
_ => continue _ => continue,
} }
} }
PlayerCommand::PlayNow(uuid, location) => { PlayerCommand::PlayNow(uuid, location) => {
// TODO: This assumes the uuid doesn't point to an album. we've been over this. // TODO: This assumes the uuid doesn't point to an album. we've been over this.
let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(uuid)); let (command, tx) =
LibraryCommandInput::command(LibraryCommand::Song(uuid));
lib_mail.send(command).await.unwrap(); lib_mail.send(command).await.unwrap();
let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else { let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else {
unreachable!() unreachable!()
@ -187,27 +243,40 @@ impl Controller {
match tx.recv().await.unwrap() { match tx.recv().await.unwrap() {
QueueResponse::Empty(Ok(())) => (), QueueResponse::Empty(Ok(())) => (),
QueueResponse::Empty(Err(e)) => { QueueResponse::Empty(Err(e)) => {
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); res_rx
.send(PlayerResponse::NowPlaying(Err(e.into())))
.await
.unwrap();
continue; continue;
} }
_ => unreachable!() _ => unreachable!(),
} }
let (command, tx) = QueueCommandInput::command( let (command, tx) = QueueCommandInput::command(QueueCommand::Append(
QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: np_song.clone(), location })), true) QueueItem::from_item_type(QueueItemType::Single(QueueSong {
); song: np_song.clone(),
location,
})),
true,
));
queue_mail.send(command).await.unwrap(); queue_mail.send(command).await.unwrap();
match tx.recv().await.unwrap() { match tx.recv().await.unwrap() {
QueueResponse::Empty(Ok(())) => (), QueueResponse::Empty(Ok(())) => (),
QueueResponse::Empty(Err(e)) => { QueueResponse::Empty(Err(e)) => {
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); res_rx
.send(PlayerResponse::NowPlaying(Err(e.into())))
.await
.unwrap();
continue; continue;
} }
_ => unreachable!() _ => unreachable!(),
} }
// TODO: Handle non Local URIs here, and whenever `load_new()` or `load_gapless()` is called // TODO: Handle non Local URIs here, and whenever `load_new()` or `load_gapless()` is called
let prism_uri = prismriver::utils::path_to_uri(&np_song.primary_uri().unwrap().0.as_path().unwrap()).unwrap(); let prism_uri = prismriver::utils::path_to_uri(
&np_song.primary_uri().unwrap().0.as_path().unwrap(),
)
.unwrap();
player.load_new(&prism_uri).unwrap(); player.load_new(&prism_uri).unwrap();
player.play(); player.play();
@ -217,39 +286,54 @@ impl Controller {
let (songs, index) = match location { let (songs, index) = match location {
PlayerLocation::Library => { PlayerLocation::Library => {
let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs); let (command, tx) =
LibraryCommandInput::command(LibraryCommand::AllSongs);
lib_mail.send(command).await.unwrap(); lib_mail.send(command).await.unwrap();
let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else { let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap()
else {
unreachable!() unreachable!()
}; };
(songs, index) (songs, index)
} }
PlayerLocation::Playlist(uuid) => { PlayerLocation::Playlist(uuid) => {
let (command, tx) = LibraryCommandInput::command(LibraryCommand::ExternalPlaylist(uuid)); let (command, tx) = LibraryCommandInput::command(
LibraryCommand::ExternalPlaylist(uuid),
);
lib_mail.send(command).await.unwrap(); lib_mail.send(command).await.unwrap();
let LibraryResponse::ExternalPlaylist(list) = tx.recv().await.unwrap() else { let LibraryResponse::ExternalPlaylist(list) =
tx.recv().await.unwrap()
else {
unreachable!() unreachable!()
}; };
let index = list.get_index(np_song.uuid).unwrap(); let index = list.get_index(np_song.uuid).unwrap();
(list.tracks, index) (list.tracks, index)
} }
_ => todo!("Got Location other than Library or Playlist") _ => todo!("Got Location other than Library or Playlist"),
}; };
for i in index + 1..(index + 50) {
for i in index+1..(index+50) {
if let Some(song) = songs.get(i) { if let Some(song) = songs.get(i) {
let (command, tx) = QueueCommandInput::command( let (command, tx) =
QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), false) QueueCommandInput::command(QueueCommand::Append(
); QueueItem::from_item_type(QueueItemType::Single(
QueueSong {
song: song.clone(),
location,
},
)),
false,
));
queue_mail.send(command).await.unwrap(); queue_mail.send(command).await.unwrap();
match tx.recv().await.unwrap() { match tx.recv().await.unwrap() {
QueueResponse::Empty(Ok(())) => (), QueueResponse::Empty(Ok(())) => (),
QueueResponse::Empty(Err(e)) => { QueueResponse::Empty(Err(e)) => {
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap(); res_rx
.send(PlayerResponse::NowPlaying(Err(e.into())))
.await
.unwrap();
continue 'outer; continue 'outer;
} }
_ => unreachable!() _ => unreachable!(),
} }
} else { } else {
println!("End of Library / Playlist"); println!("End of Library / Playlist");
@ -257,11 +341,16 @@ impl Controller {
} }
} }
// ^ This be my solution for now ^ // ^ This be my solution for now ^
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.clone()))).await.unwrap(); res_rx
.send(PlayerResponse::NowPlaying(Ok(np_song.clone())))
.await
.unwrap();
state.now_playing = np_song.uuid; state.now_playing = np_song.uuid;
_ = state.write_file(); _ = state.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song)).unwrap(); notify_connections_
.send(ConnectionsNotification::SongChange(np_song))
.unwrap();
} }
} }
} else { } else {
@ -270,4 +359,4 @@ impl Controller {
} }
Ok(()) Ok(())
} }
} }

View file

@ -5,9 +5,16 @@ use crossbeam::atomic::AtomicCell;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use prismriver::State as PrismState; use prismriver::State as PrismState;
use crate::{music_controller::controller::{PlayerCommand, PlayerResponse}, music_storage::library::Song}; use crate::{
music_controller::controller::{PlayerCommand, PlayerResponse},
music_storage::library::Song,
};
use super::{connections::ConnectionsNotification, controller::{Controller, PlaybackInfo}, controller_handle::PlayerCommandInput}; use super::{
connections::ConnectionsNotification,
controller::{Controller, PlaybackInfo},
controller_handle::PlayerCommandInput,
};
impl Controller { impl Controller {
pub(super) fn player_monitor_loop( pub(super) fn player_monitor_loop(
@ -17,7 +24,7 @@ impl Controller {
player_mail: async_channel::Sender<PlayerCommandInput>, player_mail: async_channel::Sender<PlayerCommandInput>,
notify_next_song: Sender<Song>, notify_next_song: Sender<Song>,
notify_connections_: Sender<ConnectionsNotification>, notify_connections_: Sender<ConnectionsNotification>,
playback_info: Arc<AtomicCell<PlaybackInfo>> playback_info: Arc<AtomicCell<PlaybackInfo>>,
) -> Result<(), ()> { ) -> Result<(), ()> {
std::thread::scope(|s| { std::thread::scope(|s| {
// Thread for timing and metadata // Thread for timing and metadata
@ -27,7 +34,12 @@ impl Controller {
println!("playback monitor started"); println!("playback monitor started");
while true { while true {
let (position, duration) = playback_time_tx.recv().unwrap(); let (position, duration) = playback_time_tx.recv().unwrap();
notify_connections.send(ConnectionsNotification::Playback { position: position.clone(), duration: duration.clone() }).unwrap(); notify_connections
.send(ConnectionsNotification::Playback {
position: position.clone(),
duration: duration.clone(),
})
.unwrap();
playback_info.store(PlaybackInfo { position, duration }); playback_info.store(PlaybackInfo { position, duration });
} }
} }
@ -35,35 +47,43 @@ impl Controller {
// Thread for End of Track // Thread for End of Track
let notify_connections = notify_connections_.clone(); let notify_connections = notify_connections_.clone();
s.spawn(move || { futures::executor::block_on(async { s.spawn(move || {
println!("EOS monitor started"); futures::executor::block_on(async {
while true { println!("EOS monitor started");
let _ = finished_recv.recv(); while true {
println!("End of song"); let _ = finished_recv.recv();
println!("End of song");
let (command, tx) = PlayerCommandInput::command(PlayerCommand::NextSong); let (command, tx) = PlayerCommandInput::command(PlayerCommand::NextSong);
player_mail.send(command).await.unwrap(); player_mail.send(command).await.unwrap();
let PlayerResponse::NowPlaying(res) = tx.recv().await.unwrap() else { let PlayerResponse::NowPlaying(res) = tx.recv().await.unwrap() else {
unreachable!() unreachable!()
}; };
if let Ok(song) = res { if let Ok(song) = res {
notify_next_song.send(song.clone()).unwrap(); notify_next_song.send(song.clone()).unwrap();
notify_connections.send(ConnectionsNotification::SongChange(song)).unwrap(); notify_connections
notify_connections.send(ConnectionsNotification::EOS).unwrap(); .send(ConnectionsNotification::SongChange(song))
.unwrap();
notify_connections
.send(ConnectionsNotification::EOS)
.unwrap();
}
} }
} std::thread::sleep(Duration::from_millis(100));
std::thread::sleep(Duration::from_millis(100)); });
});}); });
let notify_connections = notify_connections_.clone(); let notify_connections = notify_connections_.clone();
s.spawn(move || { s.spawn(move || {
let mut state = PrismState::Stopped; let mut state = PrismState::Stopped;
while true { while true {
let _state = playback_state.read().unwrap().to_owned(); let _state = playback_state.read().unwrap().to_owned();
if _state != state { if _state != state {
state = _state; state = _state;
println!("State Changed to {state:?}"); println!("State Changed to {state:?}");
notify_connections.send(ConnectionsNotification::StateChange(state.clone())).unwrap(); notify_connections
.send(ConnectionsNotification::StateChange(state.clone()))
.unwrap();
} }
std::thread::sleep(Duration::from_millis(100)); std::thread::sleep(Duration::from_millis(100));
} }
@ -72,4 +92,4 @@ impl Controller {
println!("Monitor Loops Ended"); println!("Monitor Loops Ended");
Ok(()) Ok(())
} }
} }

View file

@ -1,6 +1,10 @@
use kushi::{Queue, QueueError, QueueItemType}; use kushi::{Queue, QueueError, QueueItemType};
use super::{controller::{Controller, QueueCommand, QueueResponse}, controller_handle::QueueCommandInput, queue::{QueueAlbum, QueueSong}}; use super::{
controller::{Controller, QueueCommand, QueueResponse},
controller_handle::QueueCommandInput,
queue::{QueueAlbum, QueueSong},
};
impl Controller { impl Controller {
pub(super) async fn queue_loop( pub(super) async fn queue_loop(
@ -15,47 +19,57 @@ impl Controller {
QueueItemType::Single(song) => queue.add_item(song, by_human), QueueItemType::Single(song) => queue.add_item(song, by_human),
_ => unimplemented!(), _ => unimplemented!(),
} }
res_rx res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap();
.send(QueueResponse::Empty(Ok(()))) }
.await
.unwrap();
},
QueueCommand::Next => { QueueCommand::Next => {
let next = queue.next().map_or( Err(QueueError::NoNext), |s| Ok(s.clone())); let next = queue
.next()
.map_or(Err(QueueError::NoNext), |s| Ok(s.clone()));
res_rx res_rx
.send(QueueResponse::Item(next.clone())) .send(QueueResponse::Item(next.clone()))
.await .await
.unwrap(); .unwrap();
} }
QueueCommand::Prev => { QueueCommand::Prev => {
let prev = queue.prev().map_or( Err(QueueError::EmptyPlayed), |s| Ok(s.clone())); let prev = queue
.prev()
.map_or(Err(QueueError::EmptyPlayed), |s| Ok(s.clone()));
res_rx res_rx
.send(QueueResponse::Item(prev.clone())) .send(QueueResponse::Item(prev.clone()))
.await .await
.unwrap(); .unwrap();
} }
QueueCommand::GetIndex(index) => { QueueCommand::GetIndex(index) => {
let item = queue.items.get(index).map_or( Err(QueueError::OutOfBounds { index, len: queue.items.len() }), |s| Ok(s.clone())); let item = queue.items.get(index).map_or(
Err(QueueError::OutOfBounds {
index,
len: queue.items.len(),
}),
|s| Ok(s.clone()),
);
res_rx.send(QueueResponse::Item(item)).await.unwrap(); res_rx.send(QueueResponse::Item(item)).await.unwrap();
} }
QueueCommand::NowPlaying => { QueueCommand::NowPlaying => {
let item = queue.current().map(|t| t.clone()); let item = queue.current().map(|t| t.clone());
res_rx res_rx.send(QueueResponse::Item(item)).await.unwrap();
.send(QueueResponse::Item(item))
.await
.unwrap();
} }
QueueCommand::Get => { QueueCommand::Get => {
res_rx.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap(); res_rx
.send(QueueResponse::GetAll(queue.items.clone()))
.await
.unwrap();
} }
QueueCommand::Clear => { QueueCommand::Clear => {
queue.clear(); queue.clear();
res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap(); res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap();
} }
QueueCommand::Remove(index) => { QueueCommand::Remove(index) => {
res_rx.send(QueueResponse::Item(queue.remove_item(index))).await.unwrap(); res_rx
.send(QueueResponse::Item(queue.remove_item(index)))
.await
.unwrap();
} }
} }
} }
} }
} }

View file

@ -70,9 +70,7 @@ impl ExternalLibrary for ITunesLibrary {
if song_tags.contains_key("Location") { if song_tags.contains_key("Location") {
count3 += 1; count3 += 1;
//check for skipped IDs //check for skipped IDs
if &count3.to_string() if &count3.to_string() != song_tags.get_key_value("Track ID").unwrap().1 {
!= song_tags.get_key_value("Track ID").unwrap().1
{
count3 += 1; count3 += 1;
count4 += 1; count4 += 1;
} }
@ -232,7 +230,8 @@ fn get_art(file: &Path) -> Result<Vec<AlbumArt>, lofty::error::LoftyError> {
let mut album_art: Vec<AlbumArt> = Vec::new(); let mut album_art: Vec<AlbumArt> = Vec::new();
let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2); let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2);
let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed); let normal_options =
lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
let tagged_file: lofty::file::TaggedFile; let tagged_file: lofty::file::TaggedFile;
let tag = match Probe::open(file)?.options(normal_options).read() { let tag = match Probe::open(file)?.options(normal_options).read() {
@ -284,7 +283,9 @@ impl ITunesSong {
Default::default() Default::default()
} }
fn from_hashmap(map: &mut HashMap<String, String>) -> Result<ITunesSong, lofty::error::LoftyError> { fn from_hashmap(
map: &mut HashMap<String, String>,
) -> Result<ITunesSong, lofty::error::LoftyError> {
let mut song = ITunesSong::new(); let mut song = ITunesSong::new();
//get the path with the first bit chopped off //get the path with the first bit chopped off
let path_: String = map.get_key_value("Location").unwrap().1.clone(); let path_: String = map.get_key_value("Location").unwrap().1.clone();
@ -351,7 +352,7 @@ mod tests {
PathBuf::from("test-config/library2"), PathBuf::from("test-config/library2"),
String::from("library2"), String::from("library2"),
None, None,
None None,
); );
config.libraries.libraries.push(config_lib.clone()); config.libraries.libraries.push(config_lib.clone());

View file

@ -66,7 +66,6 @@ pub enum Tag {
Field(String), Field(String),
} }
impl Display for Tag { impl Display for Tag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let path_str: String = match self { let path_str: String = match self {
@ -228,7 +227,8 @@ impl Song {
/// Creates a [`Song`] from a music file /// Creates a [`Song`] from a music file
pub fn from_file<P: ?Sized + AsRef<Path>>(target_file: &P) -> Result<Self, Box<dyn Error>> { pub fn from_file<P: ?Sized + AsRef<Path>>(target_file: &P) -> Result<Self, Box<dyn Error>> {
let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed); let normal_options =
lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2); let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2);
let tagged_file: lofty::file::TaggedFile; let tagged_file: lofty::file::TaggedFile;
@ -480,8 +480,12 @@ impl Song {
match art { match art {
AlbumArt::Embedded(j) => { AlbumArt::Embedded(j) => {
let file = lofty::read_from_path(self.primary_uri()?.0.path())?; let file = lofty::read_from_path(self.primary_uri()?.0.path())?;
Ok(Some(file.tag(file.primary_tag_type()).unwrap().pictures()[*j].data().to_vec())) Ok(Some(
}, file.tag(file.primary_tag_type()).unwrap().pictures()[*j]
.data()
.to_vec(),
))
}
AlbumArt::External(ref path) => { AlbumArt::External(ref path) => {
let mut buf = vec![]; let mut buf = vec![];
std::fs::File::open(path.path())?.read_to_end(&mut buf)?; std::fs::File::open(path.path())?.read_to_end(&mut buf)?;
@ -491,7 +495,6 @@ impl Song {
} else { } else {
Ok(None) Ok(None)
} }
} }
} }
@ -1236,15 +1239,12 @@ impl MusicLibrary {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::{path::PathBuf, time::Instant};
use crate::music_storage::library::Tag; use crate::music_storage::library::Tag;
use std::{path::PathBuf, time::Instant};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{config::Config, music_storage::library::MusicLibrary};
config::Config,
music_storage::library::MusicLibrary,
};
#[test] #[test]
fn library_init() { fn library_init() {
@ -1263,20 +1263,27 @@ mod test {
let lib = MusicLibrary::init( let lib = MusicLibrary::init(
PathBuf::from("/media/g2/Storage4/Media-Files/Music/Albums/library.dlib"), PathBuf::from("/media/g2/Storage4/Media-Files/Music/Albums/library.dlib"),
Uuid::new_v4(), Uuid::new_v4(),
).unwrap(); )
.unwrap();
let timer = Instant::now(); let timer = Instant::now();
let result = lib.query_tracks( let result = lib
&String::from(""), .query_tracks(
&vec![], &String::from(""),
&vec![ &vec![],
Tag::Field("location".to_string()), &vec![
Tag::Album, Tag::Field("location".to_string()),
Tag::Disk, Tag::Album,
Tag::Track, Tag::Disk,
], Tag::Track,
).unwrap(); ],
println!("{} songs in {}ms", result.len(), timer.elapsed().as_millis()); )
.unwrap();
println!(
"{} songs in {}ms",
result.len(),
timer.elapsed().as_millis()
);
/* /*
for song in result { for song in result {

View file

@ -45,8 +45,10 @@ impl PlaylistFolder {
for item in &self.items { for item in &self.items {
match item { match item {
PlaylistFolderItem::Folder(folder) => return folder.query_uuid(uuid), PlaylistFolderItem::Folder(folder) => return folder.query_uuid(uuid),
PlaylistFolderItem::List(ref playlist) => if &playlist.uuid == uuid { PlaylistFolderItem::List(ref playlist) => {
return Some(playlist); if &playlist.uuid == uuid {
return Some(playlist);
}
} }
} }
} }
@ -225,17 +227,18 @@ impl Playlist {
continue; continue;
}; };
let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) { let uuid =
song.uuid if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) {
} else { song.uuid
let song_: Song = match Song::from_file(&song_path) { } else {
Ok(s) => s, let song_: Song = match Song::from_file(&song_path) {
Err(e) => panic!("{e}\npath: {}", song_path.display()) Ok(s) => s,
Err(e) => panic!("{e}\npath: {}", song_path.display()),
};
let uuid = song_.uuid.to_owned();
_ = lib.add_song(song_); // TODO: Add proper error handling with Library
uuid
}; };
let uuid = song_.uuid.to_owned();
_ = lib.add_song(song_); // TODO: Add proper error handling with Library
uuid
};
uuids.push(uuid); uuids.push(uuid);
} }
let mut playlist = Playlist::new(); let mut playlist = Playlist::new();
@ -348,7 +351,6 @@ impl Default for Playlist {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ExternalPlaylist { pub struct ExternalPlaylist {
pub uuid: Uuid, pub uuid: Uuid,
@ -361,11 +363,11 @@ pub struct ExternalPlaylist {
impl ExternalPlaylist { impl ExternalPlaylist {
pub(crate) fn from_playlist(playlist: &Playlist, library: &MusicLibrary) -> Self { pub(crate) fn from_playlist(playlist: &Playlist, library: &MusicLibrary) -> Self {
let tracks: Vec<Song> = playlist.tracks.iter().filter_map(|uuid| { let tracks: Vec<Song> = playlist
library.query_uuid(uuid).map(|res| { .tracks
res.0.clone() .iter()
}) .filter_map(|uuid| library.query_uuid(uuid).map(|res| res.0.clone()))
}).collect_vec(); .collect_vec();
Self { Self {
uuid: playlist.uuid, uuid: playlist.uuid,
@ -373,7 +375,7 @@ impl ExternalPlaylist {
tracks, tracks,
sort_order: playlist.sort_order.clone(), sort_order: playlist.sort_order.clone(),
play_count: playlist.play_count, play_count: playlist.play_count,
play_time: playlist.play_time play_time: playlist.play_time,
} }
} }
@ -399,7 +401,6 @@ impl ExternalPlaylist {
} }
} }
#[cfg(test)] #[cfg(test)]
mod test_super { mod test_super {
use super::*; use super::*;
@ -412,18 +413,19 @@ mod test_super {
let tracks = lib.library.iter().map(|track| track.uuid).collect(); let tracks = lib.library.iter().map(|track| track.uuid).collect();
playlist.set_tracks(tracks); playlist.set_tracks(tracks);
playlist.to_m3u( playlist
Arc::new(RwLock::from(lib)), .to_m3u(
".\\test-config\\playlists\\playlist.m3u", Arc::new(RwLock::from(lib)),
).unwrap(); ".\\test-config\\playlists\\playlist.m3u",
)
.unwrap();
} }
#[test] #[test]
fn m3u_to_list() { fn m3u_to_list() {
let (_, mut lib) = read_config_lib(); let (_, mut lib) = read_config_lib();
let playlist = let playlist = Playlist::from_m3u(".\\test-config\\playlists\\playlist", &mut lib).unwrap();
Playlist::from_m3u(".\\test-config\\playlists\\playlist", &mut lib).unwrap();
_ = playlist.to_file(".\\test-config\\playlists\\playlist"); _ = playlist.to_file(".\\test-config\\playlists\\playlist");
dbg!(&playlist, playlist.tracks.len()); dbg!(&playlist, playlist.tracks.len());

View file

@ -11,7 +11,7 @@ pub enum QueueState {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub struct QueueItem< pub struct QueueItem<
T: Debug + Clone + PartialEq, // T: The Singular Item Type T: Debug + Clone + PartialEq, // T: The Singular Item Type
U: Debug + PartialEq + Clone + IntoIterator, // U: an Iterator U: Debug + PartialEq + Clone + IntoIterator, // U: an Iterator
> { > {
pub item: QueueItemType<T, U>, pub item: QueueItemType<T, U>,
@ -22,17 +22,18 @@ pub struct QueueItem<
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub enum QueueItemType< pub enum QueueItemType<
T: Debug + Clone + PartialEq, // T: The Singular Item Type T: Debug + Clone + PartialEq, // T: The Singular Item Type
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
> { > {
Single(T), Single(T),
Multi(U) Multi(U),
} }
impl< impl<
T: Debug + Clone + PartialEq, // T: The Singular Item Type T: Debug + Clone + PartialEq, // T: The Singular Item Type
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
> QueueItemType<T, U> { > QueueItemType<T, U>
{
pub fn from_single(item: T) -> Self { pub fn from_single(item: T) -> Self {
QueueItemType::Single(item) QueueItemType::Single(item)
} }
@ -42,12 +43,7 @@ impl<
} }
} }
impl<T: Debug + Clone + PartialEq, U: Debug + PartialEq + Clone + IntoIterator> QueueItem<T, U> {
impl<
T: Debug + Clone + PartialEq,
U: Debug + PartialEq + Clone + IntoIterator,
>
QueueItem<T, U> {
pub fn from_item_type(item: QueueItemType<T, U>) -> Self { pub fn from_item_type(item: QueueItemType<T, U>) -> Self {
QueueItem { QueueItem {
item, item,
@ -59,7 +55,7 @@ QueueItem<T, U> {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Queue< pub struct Queue<
T: Debug + Clone + PartialEq, // T: The Singular Item Type T: Debug + Clone + PartialEq, // T: The Singular Item Type
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
> { > {
pub items: Vec<QueueItem<T, U>>, pub items: Vec<QueueItem<T, U>>,
@ -69,10 +65,7 @@ pub struct Queue<
} }
// TODO: HAndle the First QueueState[looping] and shuffle // TODO: HAndle the First QueueState[looping] and shuffle
impl< impl<T: Debug + Clone + PartialEq, U: Debug + PartialEq + Clone + IntoIterator> Queue<T, U> {
T: Debug + Clone + PartialEq,
U: Debug + PartialEq + Clone + IntoIterator,
> Queue<T, U> {
fn has_addhere(&self) -> bool { fn has_addhere(&self) -> bool {
for item in &self.items { for item in &self.items {
if item.state == QueueState::AddHere { if item.state == QueueState::AddHere {
@ -98,7 +91,7 @@ impl<
items: Vec::new(), items: Vec::new(),
played: Vec::new(), played: Vec::new(),
loop_, loop_,
shuffle shuffle,
} }
} }
@ -130,7 +123,10 @@ impl<
let empty = self.items.is_empty(); let empty = self.items.is_empty();
if !empty { if !empty {
self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState; self.items
.get_mut(i)
.expect("There should be an item at index {i}")
.state = QueueState::NoState;
} }
if by_human { if by_human {
@ -143,13 +139,11 @@ impl<
}, },
); );
} else { } else {
self.items.push( self.items.push(QueueItem {
QueueItem { item,
item, state: QueueState::NoState,
state: QueueState::NoState, by_human,
by_human, });
}
);
} }
} }
@ -196,7 +190,10 @@ impl<
let empty = self.items.is_empty(); let empty = self.items.is_empty();
if !empty { if !empty {
self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState; self.items
.get_mut(i)
.expect("There should be an item at index {i}")
.state = QueueState::NoState;
} }
let len = items.len(); let len = items.len();
@ -211,13 +208,11 @@ impl<
}, },
); );
} else { } else {
self.items.push( self.items.push(QueueItem {
QueueItem { item,
item, state: QueueState::NoState,
state: QueueState::NoState, by_human, // false
by_human, // false });
},
);
} }
} }
self.items[i + len - if empty { 1 } else { 0 }].state = QueueState::AddHere; self.items[i + len - if empty { 1 } else { 0 }].state = QueueState::AddHere;
@ -418,7 +413,8 @@ impl<
if let QueueItemType::Multi(_) = self.items[0].item { if let QueueItemType::Multi(_) = self.items[0].item {
unimplemented!(); // TODO: Handle Multi items here? unimplemented!(); // TODO: Handle Multi items here?
} if let QueueItemType::Multi(_) = item.item { }
if let QueueItemType::Multi(_) = item.item {
unimplemented!(); // TODO: Handle Multi items here? unimplemented!(); // TODO: Handle Multi items here?
} }
@ -447,7 +443,6 @@ impl<
} }
} }
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)] #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)]

View file

@ -1,59 +1,82 @@
use std::{fs::OpenOptions, io::Write}; use std::{fs::OpenOptions, io::Write};
use dmp_core::music_controller::{controller::{ControllerHandle, PlayerLocation}, queue::QueueSong}; use dmp_core::music_controller::{
use kushi::QueueItem; controller::{ControllerHandle, PlayerLocation},
use tauri::{AppHandle, Emitter, State, Wry}; queue::QueueSong,
use tempfile::TempDir; };
use uuid::Uuid; use kushi::QueueItem;
use tauri::{AppHandle, Emitter, State, Wry};
use crate::wrappers::_Song; use tempfile::TempDir;
use uuid::Uuid;
use crate::wrappers::_Song;
#[tauri::command]
pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> { #[tauri::command]
dbg!(&location); pub async fn add_song_to_queue(
let (song, _) = ctrl_handle.lib_get_song(uuid).await; app: AppHandle<Wry>,
match ctrl_handle.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location }))).await { ctrl_handle: State<'_, ControllerHandle>,
Ok(()) => (), uuid: Uuid,
Err(e) => return Err(e.to_string()) location: PlayerLocation,
} ) -> Result<(), String> {
app.emit("queue_updated", ()).unwrap(); dbg!(&location);
Ok(()) let (song, _) = ctrl_handle.lib_get_song(uuid).await;
} match ctrl_handle
.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(
#[tauri::command] QueueSong { song, location },
pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> { )))
let song = match ctrl_handle.play_now(uuid, location).await { .await
Ok(song) => song, {
Err(e) => return Err(e.to_string()) Ok(()) => (),
}; Err(e) => return Err(e.to_string()),
app.emit("queue_updated", ()).unwrap(); }
app.emit("now_playing_change", _Song::from(&song)).unwrap(); app.emit("queue_updated", ()).unwrap();
app.emit("playing", ()).unwrap(); Ok(())
Ok(()) }
}
#[tauri::command]
#[tauri::command] pub async fn play_now(
pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_dir: State<'_, TempDir>, uuid: Uuid) -> Result<(), String> { app: AppHandle<Wry>,
match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) { ctrl_handle: State<'_, ControllerHandle>,
Ok(art) => { uuid: Uuid,
let mut art = art.unwrap(); location: PlayerLocation,
let path = temp_dir.path().join(format!("CoverArt_{uuid}.{}", file_format::FileFormat::from_bytes(&art).extension())); ) -> Result<(), String> {
if !path.exists() { let song = match ctrl_handle.play_now(uuid, location).await {
// TODO: This can be optimised later Ok(song) => song,
let mut file = OpenOptions::new() Err(e) => return Err(e.to_string()),
.create(true) };
.truncate(true) app.emit("queue_updated", ()).unwrap();
.write(true) app.emit("now_playing_change", _Song::from(&song)).unwrap();
.read(true) app.emit("playing", ()).unwrap();
.open(path.clone()) Ok(())
.unwrap(); }
file.write_all(&mut art).unwrap();
} #[tauri::command]
opener::open(path).unwrap(); pub async fn display_album_art(
} ctrl_handle: State<'_, ControllerHandle>,
Err(e) => return Err(e.to_string()) temp_dir: State<'_, TempDir>,
}; uuid: Uuid,
Ok(()) ) -> Result<(), String> {
} match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) {
Ok(art) => {
let mut art = art.unwrap();
let path = temp_dir.path().join(format!(
"CoverArt_{uuid}.{}",
file_format::FileFormat::from_bytes(&art).extension()
));
if !path.exists() {
// TODO: This can be optimised later
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.read(true)
.open(path.clone())
.unwrap();
file.write_all(&mut art).unwrap();
}
opener::open(path).unwrap();
}
Err(e) => return Err(e.to_string()),
};
Ok(())
}

View file

@ -1,277 +1,315 @@
#![allow(while_true)] #![allow(while_true)]
use std::{fs, path::PathBuf, sync::Arc, thread::{scope, spawn}, time::Duration}; use std::{
fs,
use crossbeam::channel::{bounded, unbounded, Receiver, Sender}; path::PathBuf,
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::{connections::ConnectionsInput, controller::{Controller, ControllerHandle, PlaybackInfo}}, music_storage::library::{MusicLibrary, Song}}; sync::Arc,
use futures::channel::oneshot; thread::{scope, spawn},
use parking_lot::RwLock; time::Duration,
use tauri::{http::Response, Emitter, Manager, State, Wry}; };
use uuid::Uuid;
use wrappers::{_Song, stop}; use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
use dmp_core::{
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists, remove_from_queue, seek}; config::{Config, ConfigLibrary},
use commands::{add_song_to_queue, play_now, display_album_art}; music_controller::{
connections::ConnectionsInput,
controller::{Controller, ControllerHandle, PlaybackInfo},
pub mod wrappers; },
pub mod commands; music_storage::library::{MusicLibrary, Song},
};
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png"); use futures::channel::oneshot;
use parking_lot::RwLock;
#[cfg_attr(mobile, tauri::mobile_entry_point)] use tauri::{http::Response, Emitter, Manager, State, Wry};
pub fn run() { use uuid::Uuid;
let (rx, tx) = unbounded::<Config>(); use wrappers::{_Song, stop};
let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>();
let (handle_rx, handle_tx) = unbounded::<ControllerHandle>(); use crate::wrappers::{
let (playback_info_rx, playback_info_tx) = bounded(1); get_library, get_playlist, get_playlists, get_queue, get_song, import_playlist, next, pause,
let (next_rx, next_tx) = bounded(1); play, prev, remove_from_queue, seek, set_volume,
};
let _controller_thread = spawn(move || { use commands::{add_song_to_queue, display_album_art, play_now};
let mut config = { tx.recv().unwrap() } ;
let scan_path = { lib_tx.recv().unwrap() }; pub mod commands;
let _temp_config = ConfigLibrary::default(); pub mod wrappers;
let _lib = config.libraries.get_default().unwrap_or(&_temp_config);
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png");
let save_path = if _lib.path == PathBuf::default() {
let p = scan_path.as_ref().unwrap().clone().canonicalize().unwrap(); #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
if cfg!(windows) { let (rx, tx) = unbounded::<Config>();
p.join("library_windows.dlib") let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>();
} else if cfg!(unix) { let (handle_rx, handle_tx) = unbounded::<ControllerHandle>();
p.join("library_unix.dlib") let (playback_info_rx, playback_info_tx) = bounded(1);
} else { let (next_rx, next_tx) = bounded(1);
p.join("library.dlib")
} let _controller_thread = spawn(move || {
} else { let mut config = { tx.recv().unwrap() };
_lib.path.clone() let scan_path = { lib_tx.recv().unwrap() };
}; let _temp_config = ConfigLibrary::default();
println!("save_path: {}\nscan_path:{scan_path:?}", save_path.display()); let _lib = config.libraries.get_default().unwrap_or(&_temp_config);
let mut library = MusicLibrary::init( let save_path = if _lib.path == PathBuf::default() {
save_path.clone(), let p = scan_path.as_ref().unwrap().clone().canonicalize().unwrap();
_lib.uuid
).unwrap(); if cfg!(windows) {
p.join("library_windows.dlib")
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone()); } else if cfg!(unix) {
p.join("library_unix.dlib")
if config.libraries.get_default().is_err() { } else {
library.scan_folder(&scan_path).unwrap(); p.join("library.dlib")
config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()]), Some(library.uuid))); }
} } else {
if library.library.is_empty() { _lib.path.clone()
println!("library is empty"); };
} else { println!(
config.write_file().unwrap(); "save_path: {}\nscan_path:{scan_path:?}",
} save_path.display()
println!("scan_path: {}", scan_path.display()); );
library.save(save_path).unwrap(); let mut library = MusicLibrary::init(save_path.clone(), _lib.uuid).unwrap();
let ( let scan_path = scan_path.unwrap_or_else(|| {
handle, config
input, .libraries
playback_info, .get_default()
next_song_notification, .unwrap()
) = ControllerHandle::new( .scan_folders
library, .as_ref()
std::sync::Arc::new(RwLock::new(config)), .unwrap()[0]
Some(ConnectionsInput { .clone()
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID").map(|id| id.parse::<u64>().unwrap()), });
}),
); if config.libraries.get_default().is_err() {
library.scan_folder(&scan_path).unwrap();
handle_rx.send(handle).unwrap(); config.push_library(ConfigLibrary::new(
playback_info_rx.send(playback_info).unwrap(); save_path.clone(),
next_rx.send(next_song_notification).unwrap(); String::from("Library"),
Some(vec![scan_path.clone()]),
let _controller = futures::executor::block_on(Controller::start(input)).unwrap(); Some(library.uuid),
));
}
if library.library.is_empty() {
}); println!("library is empty");
let app = tauri::Builder::default() } else {
.plugin(tauri_plugin_shell::init()) config.write_file().unwrap();
.invoke_handler(tauri::generate_handler![ }
get_config, println!("scan_path: {}", scan_path.display());
create_new_library,
get_library, library.save(save_path).unwrap();
play,
pause, let (handle, input, playback_info, next_song_notification) = ControllerHandle::new(
stop, library,
set_volume, std::sync::Arc::new(RwLock::new(config)),
next, Some(ConnectionsInput {
prev, discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID")
get_song, .map(|id| id.parse::<u64>().unwrap()),
lib_already_created, }),
get_queue, );
add_song_to_queue,
play_now, handle_rx.send(handle).unwrap();
import_playlist, playback_info_rx.send(playback_info).unwrap();
get_playlist, next_rx.send(next_song_notification).unwrap();
get_playlists,
remove_from_queue, let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
display_album_art, });
seek, let app = tauri::Builder::default()
]).manage(ConfigRx(rx)) .plugin(tauri_plugin_shell::init())
.manage(LibRx(lib_rx)) .invoke_handler(tauri::generate_handler![
.manage(HandleTx(handle_tx)) get_config,
.manage(tempfile::TempDir::new().unwrap()) create_new_library,
.setup(|app| { get_library,
let _app = app.handle().clone(); play,
let app = _app.clone(); pause,
stop,
std::thread::Builder::new() set_volume,
.name("PlaybackInfo handler".to_string()) next,
.spawn(move || { prev,
let mut _info: Arc<RwLock<PlaybackInfo>> = Arc::new(RwLock::new(PlaybackInfo::default())); get_song,
let mut _now_playing: Arc<RwLock<Option<Song>>> = Arc::new(RwLock::new(None)); lib_already_created,
get_queue,
scope(|s| { add_song_to_queue,
let info = _info.clone(); play_now,
s.spawn(|| { import_playlist,
let info = info; get_playlist,
let playback_info = playback_info_tx.recv().unwrap(); get_playlists,
while true { remove_from_queue,
let i = playback_info.take(); display_album_art,
app.emit("playback_info", i.clone()).unwrap(); seek,
*info.write() = i; ])
std::thread::sleep(Duration::from_millis(100)); .manage(ConfigRx(rx))
} .manage(LibRx(lib_rx))
}); .manage(HandleTx(handle_tx))
.manage(tempfile::TempDir::new().unwrap())
let now_playing = _now_playing.clone(); .setup(|app| {
s.spawn(|| { let _app = app.handle().clone();
let now_playing = now_playing; let app = _app.clone();
let next_song_notification = next_tx.recv().unwrap();
while true { std::thread::Builder::new()
let song = next_song_notification.recv().unwrap(); .name("PlaybackInfo handler".to_string())
app.emit("now_playing_change", _Song::from(&song)).unwrap(); .spawn(move || {
app.emit("queue_updated", ()).unwrap(); let mut _info: Arc<RwLock<PlaybackInfo>> =
app.emit("playing", ()).unwrap(); Arc::new(RwLock::new(PlaybackInfo::default()));
_ = now_playing.write().insert(song); let mut _now_playing: Arc<RwLock<Option<Song>>> = Arc::new(RwLock::new(None));
}
}); scope(|s| {
}); let info = _info.clone();
}).unwrap(); s.spawn(|| {
let info = info;
Ok(()) let playback_info = playback_info_tx.recv().unwrap();
}) while true {
.register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| { let i = playback_info.take();
let query = req app.emit("playback_info", i.clone()).unwrap();
.clone() *info.write() = i;
.uri() std::thread::sleep(Duration::from_millis(100));
.clone() }
.into_parts() });
.path_and_query
.unwrap() let now_playing = _now_playing.clone();
.query() s.spawn(|| {
.unwrap() let now_playing = now_playing;
.to_string(); let next_song_notification = next_tx.recv().unwrap();
while true {
let bytes = if query.as_str() == "default" { let song = next_song_notification.recv().unwrap();
Some(DEFAULT_IMAGE.to_vec()) app.emit("now_playing_change", _Song::from(&song)).unwrap();
} else {futures::executor::block_on(async move { app.emit("queue_updated", ()).unwrap();
let controller = ctx.app_handle().state::<ControllerHandle>(); app.emit("playing", ()).unwrap();
let song = controller.lib_get_song(Uuid::parse_str(query.as_str()).unwrap()).await.0; _ = now_playing.write().insert(song);
Some(song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())) }
})}; });
});
res.respond( })
Response::builder() .unwrap();
.header("Origin", "*")
.header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len()) Ok(())
.status(200) })
.body(bytes.unwrap_or_default()) .register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| {
.unwrap() let query = req
); .clone()
}) .uri()
.build(tauri::generate_context!()) .clone()
.expect("error while building tauri application"); .into_parts()
.path_and_query
app .unwrap()
.run(|_app_handle, event| match event { .query()
tauri::RunEvent::ExitRequested { .. } => { .unwrap()
// api.prevent_exit(); .to_string();
//panic!("does this kill the player?")
} let bytes = if query.as_str() == "default" {
_ => {} Some(DEFAULT_IMAGE.to_vec())
}); } else {
} futures::executor::block_on(async move {
let controller = ctx.app_handle().state::<ControllerHandle>();
struct ConfigRx(Sender<Config>); let song = controller
.lib_get_song(Uuid::parse_str(query.as_str()).unwrap())
struct LibRx(Sender<Option<PathBuf>>); .await
struct HandleTx(Receiver<ControllerHandle>); .0;
Some(
song.album_art(0)
#[tauri::command] .unwrap_or_else(|_| None)
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> { .unwrap_or(DEFAULT_IMAGE.to_vec()),
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") { )
let path = dir.config_dir(); })
fs::create_dir_all(path).or_else(|err| { };
if err.kind() == std::io::ErrorKind::AlreadyExists {
Ok(()) res.respond(
} else { Response::builder()
Err(err) .header("Origin", "*")
} .header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len())
}).unwrap(); .status(200)
.body(bytes.unwrap_or_default())
let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) { .unwrap(),
if c.state_path == PathBuf::default() { );
c.state_path = PathBuf::from(path).join("state"); })
} .build(tauri::generate_context!())
c .expect("error while building tauri application");
} else {
let c = Config { app.run(|_app_handle, event| match event {
path: PathBuf::from(path).join("config"), tauri::RunEvent::ExitRequested { .. } => {
state_path: PathBuf::from(path).join("state"), // api.prevent_exit();
..Default::default() //panic!("does this kill the player?")
}; }
c.write_file().unwrap(); _ => {}
c });
}; }
state.inner().0.send(config.clone()).unwrap(); struct ConfigRx(Sender<Config>);
Ok(config) struct LibRx(Sender<Option<PathBuf>>);
} else { struct HandleTx(Receiver<ControllerHandle>);
panic!("No config dir for DMP")
} #[tauri::command]
} async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") {
#[tauri::command] let path = dir.config_dir();
async fn create_new_library( fs::create_dir_all(path)
app: tauri::AppHandle<Wry>, .or_else(|err| {
lib_rx: State<'_, LibRx>, if err.kind() == std::io::ErrorKind::AlreadyExists {
handle_tx: State<'_, HandleTx>, Ok(())
) -> Result<(), String> { } else {
let dir = rfd::AsyncFileDialog::new() Err(err)
.set_title("Pick a library path") }
.pick_folder() })
.await .unwrap();
.unwrap();
let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) {
let path = dir.path().canonicalize().unwrap(); if c.state_path == PathBuf::default() {
println!("{}", path.display()); c.state_path = PathBuf::from(path).join("state");
}
if !path.exists() { c
panic!("Path {} does not exist!", path.display()) } else {
} else if !path.is_dir() { let c = Config {
panic!("Path {} is not a directory!", path.display()) path: PathBuf::from(path).join("config"),
} state_path: PathBuf::from(path).join("state"),
..Default::default()
lib_rx.inner().0.send(Some(path)).unwrap(); };
app.manage(handle_tx.inner().0.recv().unwrap()); c.write_file().unwrap();
app.emit("library_loaded", ()).unwrap(); c
Ok(()) };
}
state.inner().0.send(config.clone()).unwrap();
#[tauri::command]
async fn lib_already_created(app: tauri::AppHandle<Wry>, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> { Ok(config)
println!("lib already created"); } else {
lib_rx.inner().0.send(None).unwrap(); panic!("No config dir for DMP")
app.manage(handle_tx.inner().0.recv().unwrap()); }
app.emit("library_loaded", ()).unwrap(); }
Ok(())
} #[tauri::command]
async fn create_new_library(
app: tauri::AppHandle<Wry>,
lib_rx: State<'_, LibRx>,
handle_tx: State<'_, HandleTx>,
) -> Result<(), String> {
let dir = rfd::AsyncFileDialog::new()
.set_title("Pick a library path")
.pick_folder()
.await
.unwrap();
let path = dir.path().canonicalize().unwrap();
println!("{}", path.display());
if !path.exists() {
panic!("Path {} does not exist!", path.display())
} else if !path.is_dir() {
panic!("Path {} is not a directory!", path.display())
}
lib_rx.inner().0.send(Some(path)).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap());
app.emit("library_loaded", ()).unwrap();
Ok(())
}
#[tauri::command]
async fn lib_already_created(
app: tauri::AppHandle<Wry>,
lib_rx: State<'_, LibRx>,
handle_tx: State<'_, HandleTx>,
) -> Result<(), String> {
println!("lib already created");
lib_rx.inner().0.send(None).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap());
app.emit("library_loaded", ()).unwrap();
Ok(())
}

View file

@ -1,7 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() { fn main() {
colog::init(); colog::init();

View file

@ -1,8 +1,11 @@
use std::{collections::BTreeMap, path::PathBuf}; use std::{collections::BTreeMap, path::PathBuf};
use chrono::{DateTime, Utc, serde::ts_milliseconds_option}; use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
use crossbeam::channel::Sender; use crossbeam::channel::Sender;
use dmp_core::{music_controller::controller::{ControllerHandle, PlayerLocation}, music_storage::library::{Song, Tag, URI}}; use dmp_core::{
music_controller::controller::{ControllerHandle, PlayerLocation},
music_storage::library::{Song, Tag, URI},
};
use itertools::Itertools; use itertools::Itertools;
use kushi::QueueItemType; use kushi::QueueItemType;
use serde::Serialize; use serde::Serialize;
@ -12,40 +15,52 @@ use uuid::Uuid;
pub struct ArtworkRx(pub Sender<Vec<u8>>); pub struct ArtworkRx(pub Sender<Vec<u8>>);
#[tauri::command] #[tauri::command]
pub async fn play(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn play(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
match ctrl_handle.play().await { match ctrl_handle.play().await {
Ok(()) => { Ok(()) => {
app.emit("playing", ()).unwrap(); app.emit("playing", ()).unwrap();
Ok(()) Ok(())
}, }
Err(e) => Err(e.to_string()) Err(e) => Err(e.to_string()),
} }
} }
#[tauri::command] #[tauri::command]
pub async fn pause(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn pause(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
match ctrl_handle.pause().await { match ctrl_handle.pause().await {
Ok(()) => { Ok(()) => {
app.emit("paused", ()).unwrap(); app.emit("paused", ()).unwrap();
Ok(()) Ok(())
} }
Err(e) => Err(e.to_string()) Err(e) => Err(e.to_string()),
} }
} }
#[tauri::command] #[tauri::command]
pub async fn stop(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn stop(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
match ctrl_handle.stop().await { match ctrl_handle.stop().await {
Ok(()) => { Ok(()) => {
app.emit("stop", ()).unwrap(); app.emit("stop", ()).unwrap();
Ok(()) Ok(())
} }
Err(e) => Err(e.to_string()) Err(e) => Err(e.to_string()),
} }
} }
#[tauri::command] #[tauri::command]
pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String) -> Result<(), String> { pub async fn set_volume(
ctrl_handle: State<'_, ControllerHandle>,
volume: String,
) -> Result<(), String> {
let volume = volume.parse::<f32>().unwrap() / 100.0; let volume = volume.parse::<f32>().unwrap() / 100.0;
ctrl_handle.set_volume(volume).await; ctrl_handle.set_volume(volume).await;
Ok(()) Ok(())
@ -57,10 +72,13 @@ pub async fn get_volume(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
} }
#[tauri::command] #[tauri::command]
pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn next(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
let song = match ctrl_handle.next().await { let song = match ctrl_handle.next().await {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e.to_string()) Err(e) => return Err(e.to_string()),
}; };
app.emit("now_playing_change", _Song::from(&song)).unwrap(); app.emit("now_playing_change", _Song::from(&song)).unwrap();
app.emit("queue_updated", ()).unwrap(); app.emit("queue_updated", ()).unwrap();
@ -69,10 +87,13 @@ pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
} }
#[tauri::command] #[tauri::command]
pub async fn prev(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn prev(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
let song = match ctrl_handle.prev().await { let song = match ctrl_handle.prev().await {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e.to_string()) Err(e) => return Err(e.to_string()),
}; };
println!("prev"); println!("prev");
app.emit("now_playing_change", _Song::from(&song)).unwrap(); app.emit("now_playing_change", _Song::from(&song)).unwrap();
@ -82,33 +103,38 @@ pub async fn prev(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
#[tauri::command] #[tauri::command]
pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn get_queue(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<(_Song, PlayerLocation)>, String> { pub async fn get_queue(
Ok( ctrl_handle: State<'_, ControllerHandle>,
ctrl_handle ) -> Result<Vec<(_Song, PlayerLocation)>, String> {
.queue_get_all() Ok(ctrl_handle
.await .queue_get_all()
.into_iter() .await
.map(|item| { .into_iter()
let QueueItemType::Single(song) = item.item else { unreachable!("There should be no albums in the queue right now") }; .map(|item| {
(_Song::from(&song.song), song.location) let QueueItemType::Single(song) = item.item else {
} unreachable!("There should be no albums in the queue right now")
).collect_vec() };
) (_Song::from(&song.song), song.location)
})
.collect_vec())
} }
#[tauri::command] #[tauri::command]
pub async fn remove_from_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, index: usize) -> Result<(), String> { pub async fn remove_from_queue(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
index: usize,
) -> Result<(), String> {
match ctrl_handle.queue_remove(index).await { match ctrl_handle.queue_remove(index).await {
Ok(_) => { Ok(_) => {
app.emit("queue_updated", ()).unwrap(); app.emit("queue_updated", ()).unwrap();
Ok(()) Ok(())
} }
Err(e) => Err(e.to_string()) Err(e) => Err(e.to_string()),
} }
} }
@ -140,7 +166,11 @@ impl From<&Song> for _Song {
last_played: value.last_played, last_played: value.last_played,
date_added: value.date_added, date_added: value.date_added,
date_modified: value.date_modified, date_modified: value.date_modified,
tags: value.tags.iter().map(|(k, v)| (k.to_string(), v.clone())).collect() tags: value
.tags
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
} }
} }
} }
@ -157,50 +187,82 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec
} }
#[tauri::command] #[tauri::command]
pub async fn get_playlist(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<Vec<_Song>, String> { pub async fn get_playlist(
ctrl_handle: State<'_, ControllerHandle>,
uuid: Uuid,
) -> Result<Vec<_Song>, String> {
let playlist = match ctrl_handle.playlist_get(uuid).await { let playlist = match ctrl_handle.playlist_get(uuid).await {
Ok(list) => list, Ok(list) => list,
Err(_) => todo!() Err(_) => todo!(),
}; };
let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::<Vec<_>>(); let songs = playlist
println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len()); .tracks
.iter()
.map(|song| _Song::from(song))
.collect::<Vec<_>>();
println!(
"Got Playlist {}, len {}",
playlist.title,
playlist.tracks.len()
);
Ok(songs) Ok(songs)
} }
#[tauri::command] #[tauri::command]
pub async fn get_playlists(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn get_playlists(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
let lists = ctrl_handle.playlist_get_all().await; let lists = ctrl_handle.playlist_get_all().await;
app.emit("playlists_gotten", lists.into_iter().map(|(uuid, name)| PlaylistPayload { uuid, name }).collect_vec()).unwrap(); app.emit(
"playlists_gotten",
lists
.into_iter()
.map(|(uuid, name)| PlaylistPayload { uuid, name })
.collect_vec(),
)
.unwrap();
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result<PlaylistPayload, String> { pub async fn import_playlist(
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<PlaylistPayload, String> {
let file = rfd::AsyncFileDialog::new() let file = rfd::AsyncFileDialog::new()
.add_filter("m3u8 Playlist", &["m3u8", "m3u"]) .add_filter("m3u8 Playlist", &["m3u8", "m3u"])
.set_title("Import a Playlist") .set_title("Import a Playlist")
.pick_file() .pick_file()
.await .await
.unwrap(); .unwrap();
let (uuid, name) = ctrl_handle.playlist_import_m3u(PathBuf::from(file.path())).await.unwrap(); let (uuid, name) = ctrl_handle
.playlist_import_m3u(PathBuf::from(file.path()))
.await
.unwrap();
ctrl_handle.lib_save().await; ctrl_handle.lib_save().await;
println!("Imported Playlist {name}"); println!("Imported Playlist {name}");
Ok(PlaylistPayload {uuid, name}) Ok(PlaylistPayload { uuid, name })
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
pub struct PlaylistPayload { pub struct PlaylistPayload {
uuid: Uuid, uuid: Uuid,
name: String name: String,
} }
#[tauri::command] #[tauri::command]
pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<_Song, String> { pub async fn get_song(
ctrl_handle: State<'_, ControllerHandle>,
uuid: Uuid,
) -> Result<_Song, String> {
let song = ctrl_handle.lib_get_song(uuid).await.0; let song = ctrl_handle.lib_get_song(uuid).await.0;
println!("got song {}", &song.tags.get(&Tag::Title).unwrap_or(&String::new())); println!(
"got song {}",
&song.tags.get(&Tag::Title).unwrap_or(&String::new())
);
Ok(_Song::from(&song)) Ok(_Song::from(&song))
} }