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 {
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 {
name,
path,
@ -197,7 +202,7 @@ pub mod tests {
PathBuf::from("test-config/library"),
String::from("library"),
None,
None
None,
);
let mut config = Config {
path: PathBuf::from("test-config/config_test.json"),

View file

@ -13,11 +13,11 @@ pub mod music_controller {
pub mod connections;
pub mod controller;
pub mod controller_handle;
pub mod queue;
pub mod library_command;
pub mod player_command;
pub mod player_monitor;
pub mod queue;
pub mod queue_command;
pub mod library_command;
}
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 crossbeam::{scope, select};
@ -8,7 +15,10 @@ use listenbrainz::ListenBrainz;
use parking_lot::RwLock;
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;
@ -16,7 +26,7 @@ use super::controller::Controller;
pub(super) enum ConnectionsNotification {
Playback {
position: Option<TimeDelta>,
duration: Option<TimeDelta>
duration: Option<TimeDelta>,
},
StateChange(PrismState),
SongChange(Song),
@ -30,10 +40,9 @@ pub struct ConnectionsInput {
pub(super) struct ControllerConnections {
pub notifications_tx: Receiver<ConnectionsNotification>,
pub inner: ConnectionsInput
pub inner: ConnectionsInput,
}
static DC_ACTIVE: AtomicBool = AtomicBool::new(false);
static LB_ACTIVE: AtomicBool = AtomicBool::new(false);
@ -43,9 +52,9 @@ impl Controller {
ControllerConnections {
notifications_tx,
inner: ConnectionsInput {
discord_rpc_client_id
discord_rpc_client_id,
},
}: ControllerConnections
}: ControllerConnections,
) {
let (dc_state_rx, dc_state_tx) = bounded::<PrismState>(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);
scope(|s| {
s.builder().name("Notifications Sorter".to_string()).spawn(|_| {
s.builder()
.name("Notifications Sorter".to_string())
.spawn(|_| {
use ConnectionsNotification::*;
while true {
match notifications_tx.recv().unwrap() {
Playback { .. } => {}
StateChange(state) => {
if DC_ACTIVE.load(Ordering::Relaxed) { dc_state_rx.send(state.clone()).unwrap(); }
if DC_ACTIVE.load(Ordering::Relaxed) {
dc_state_rx.send(state.clone()).unwrap();
}
}
SongChange(song) => {
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(); }
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(); }
if LB_ACTIVE.load(Ordering::Relaxed) {
lb_eos_rx.send(()).unwrap();
}
}
}
}).unwrap();
}
})
.unwrap();
if let Some(client_id) = discord_rpc_client_id {
s.builder().name("Discord RPC Handler".to_string()).spawn(move |_| {
s.builder()
.name("Discord RPC Handler".to_string())
.spawn(move |_| {
Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx);
}).unwrap();
})
.unwrap();
};
if let Some(token) = config.read().connections.listenbrainz_token.clone() {
s.builder().name("ListenBrainz Handler".to_string()).spawn(move |_| {
s.builder()
.name("ListenBrainz Handler".to_string())
.spawn(move |_| {
Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx);
}).unwrap();
})
.unwrap();
}
}).unwrap();
})
.unwrap();
}
fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<PrismState>) {
// 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();
while !Client::is_ready() { sleep(Duration::from_millis(100)); }
while !Client::is_ready() {
sleep(Duration::from_millis(100));
}
println!("discord connected");
let mut state = "Started".to_string();
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);
while true {
@ -121,38 +154,47 @@ impl Controller {
default(Duration::from_millis(99)) => ()
}
client.set_activity(|activity| {
let a = activity.state(
song.as_ref().map_or(String::new(), |s| format!(
client
.set_activity(|activity| {
let a = activity
.state(song.as_ref().map_or(String::new(), |s| {
format!(
"{}{}{}",
s.get_tag(&Tag::Artist).map_or(String::new(), |album| album.clone()),
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()
&& s.get_tag(&Tag::Artist).is_some()
{
" - "
} else {
""
},
s.get_tag(&Tag::Album)
.map_or(String::new(), |album| album.clone())
)
)
)._type(discord_presence::models::ActivityType::Listening)
.details(
if let Some(song) = song {
song.get_tag(&Tag::Title).map_or(String::from("Unknown Title"), |title| title.clone())
}))
._type(discord_presence::models::ActivityType::Listening)
.details(if let Some(song) = song {
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())
timestamps.start(now).end(now + s.duration.as_secs())
})
} else {
a
}
} else {
a
}.assets(|a| {
a.large_text(state.clone())
}).instance(true)
}).unwrap();
}
.assets(|a| a.large_text(state.clone()))
.instance(true)
})
.unwrap();
}
DC_ACTIVE.store(false, Ordering::Relaxed);
}
@ -228,7 +270,11 @@ mod test_super {
let (config, lib) = read_config_lib();
song_rx.send(lib.library[0].clone()).unwrap();
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));
eos_rx.send(()).unwrap();

View file

@ -92,7 +92,7 @@ pub enum PlayerCommand {
#[derive(Debug, PartialEq, Clone)]
pub enum PlayerResponse {
Empty(Result<(), PlayerError>),
NowPlaying(Result<Song, QueueError>)
NowPlaying(Result<Song, QueueError>),
}
#[derive(Error, Debug, PartialEq, Clone)]
@ -146,25 +146,24 @@ pub enum QueueResponse {
GetAll(Vec<QueueItem_>),
}
pub struct ControllerInput {
player_mail: (
async_channel::Sender<PlayerCommandInput>,
async_channel::Receiver<PlayerCommandInput>
async_channel::Receiver<PlayerCommandInput>,
),
lib_mail: (
async_channel::Sender<LibraryCommandInput>,
async_channel::Receiver<LibraryCommandInput>
async_channel::Receiver<LibraryCommandInput>,
),
queue_mail: (
async_channel::Sender<QueueCommandInput>,
async_channel::Receiver<QueueCommandInput>
async_channel::Receiver<QueueCommandInput>,
),
library: MusicLibrary,
config: Arc<RwLock<Config>>,
playback_info: Arc<AtomicCell<PlaybackInfo>>,
notify_next_song: Sender<Song>,
connections: Option<ConnectionsInput>
connections: Option<ConnectionsInput>,
}
pub struct ControllerHandle {
@ -174,7 +173,16 @@ pub struct ControllerHandle {
}
impl ControllerHandle {
pub fn new(library: MusicLibrary, config: Arc<RwLock<Config>>, connections: Option<ConnectionsInput>) -> (Self, ControllerInput, Arc<AtomicCell<PlaybackInfo>>, Receiver<Song>) {
pub fn new(
library: MusicLibrary,
config: Arc<RwLock<Config>>,
connections: Option<ConnectionsInput>,
) -> (
Self,
ControllerInput,
Arc<AtomicCell<PlaybackInfo>>,
Receiver<Song>,
) {
let (lib_mail_rx, lib_mail_tx) = async_channel::unbounded();
let (player_mail_rx, player_mail_tx) = async_channel::unbounded();
let (queue_mail_rx, queue_mail_tx) = async_channel::unbounded();
@ -184,7 +192,7 @@ impl ControllerHandle {
ControllerHandle {
lib_mail_rx: lib_mail_rx.clone(),
player_mail_rx: player_mail_rx.clone(),
queue_mail_rx: queue_mail_rx.clone()
queue_mail_rx: queue_mail_rx.clone(),
},
ControllerInput {
player_mail: (player_mail_rx, player_mail_tx),
@ -197,7 +205,7 @@ impl ControllerHandle {
connections,
},
playback_info,
notify_next_song.1
notify_next_song.1,
)
}
}
@ -247,7 +255,7 @@ impl Controller {
playback_info,
notify_next_song,
connections,
}: ControllerInput
}: ControllerInput,
) -> Result<(), Box<dyn Error>> {
let queue: Queue<QueueSong, QueueAlbum> = Queue {
items: Vec::new(),
@ -270,7 +278,8 @@ impl Controller {
let player_state = player.state.clone();
let player_timing = player.get_timing_recv();
let finished_tx = player.get_finished_recv();
let (notifications_rx, notifications_tx) = crossbeam_channel::unbounded::<ConnectionsNotification>();
let (notifications_rx, notifications_tx) =
crossbeam_channel::unbounded::<ConnectionsNotification>();
let a = scope.spawn({
let queue_mail = queue_mail.clone();
@ -283,8 +292,7 @@ impl Controller {
let _lib_mail = lib_mail.0.clone();
let _queue_mail = queue_mail.0.clone();
scope
.spawn(async move {
scope.spawn(async move {
Controller::player_command_loop(
player,
player_mail.1,
@ -296,13 +304,8 @@ impl Controller {
.await
.unwrap();
});
scope
.spawn(async {
Controller::library_loop(
lib_mail.1,
&mut library,
_config,
)
scope.spawn(async {
Controller::library_loop(lib_mail.1, &mut library, _config)
.await
.unwrap();
});
@ -327,7 +330,8 @@ impl Controller {
notify_next_song,
notifications_rx,
playback_info,
).unwrap();
)
.unwrap();
});
if let Some(inner) = connections {
@ -338,7 +342,8 @@ impl Controller {
ControllerConnections {
notifications_tx,
inner,
});
},
);
});
}
a.join().unwrap();

View file

@ -6,7 +6,13 @@ use uuid::Uuid;
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 {
// The Library Section
@ -59,12 +65,17 @@ impl ControllerHandle {
pub async fn playlist_import_m3u(&self, path: PathBuf) -> Result<(Uuid, String), ()> {
let (command, tx) = LibraryCommandInput::command(LibraryCommand::ImportM3UPlayList(path));
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))
}
// 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));
self.queue_mail_rx.send(command).await.unwrap();
let QueueResponse::Empty(res) = tx.recv().await.unwrap() else {
@ -73,7 +84,10 @@ impl ControllerHandle {
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));
self.queue_mail_rx.send(command).await.unwrap();
let QueueResponse::Item(res) = tx.recv().await.unwrap() else {
@ -166,7 +180,7 @@ impl ControllerHandle {
pub(super) struct LibraryCommandInput {
pub res_rx: Sender<LibraryResponse>,
pub command: LibraryCommand
pub command: LibraryCommand,
}
impl LibraryCommandInput {
@ -175,16 +189,16 @@ impl LibraryCommandInput {
(
Self {
res_rx: rx,
command
command,
},
tx
tx,
)
}
}
pub(super) struct QueueCommandInput {
pub res_rx: Sender<QueueResponse>,
pub command: QueueCommand
pub command: QueueCommand,
}
impl QueueCommandInput {
@ -193,16 +207,16 @@ impl QueueCommandInput {
(
Self {
res_rx: rx,
command
command,
},
tx
tx,
)
}
}
pub(super) struct PlayerCommandInput {
pub res_rx: Sender<PlayerResponse>,
pub command: PlayerCommand
pub command: PlayerCommand,
}
impl PlayerCommandInput {
@ -211,9 +225,9 @@ impl PlayerCommandInput {
(
Self {
res_rx: rx,
command
command,
},
tx
tx,
)
}
}

View file

@ -3,9 +3,18 @@ use std::sync::Arc;
use parking_lot::RwLock;
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 {
pub(super) async fn library_loop(
@ -18,34 +27,71 @@ impl Controller {
match command {
LibraryCommand::Song(uuid) => {
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 => {
res_rx.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap();
},
res_rx
.send(LibraryResponse::AllSongs(library.library.clone()))
.await
.unwrap();
}
LibraryCommand::ExternalPlaylist(uuid) => {
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) => {
let playlist = Playlist::from_m3u(path, library).unwrap();
let uuid = playlist.uuid;
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 => {
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();
}
LibraryCommand::Playlists => {
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(())

View file

@ -3,9 +3,19 @@ use crossbeam_channel::Sender;
use kushi::{QueueItem, QueueItemType};
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 {
pub(super) async fn player_command_loop(
@ -38,7 +48,10 @@ impl Controller {
PlayerCommand::Seek(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) => {
@ -57,63 +70,79 @@ impl Controller {
match tx.recv().await.unwrap() {
QueueResponse::Item(Ok(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!(),
};
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);
// handle error here for unknown formats
player.load_new(&prism_uri).unwrap();
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
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;
};
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();
let LibraryResponse::Song(_, i) = tx.recv().await.unwrap() else {
unreachable!()
};
if let Some(song) = songs.get(i + 49) {
let (command, tx) = QueueCommandInput::command(
QueueCommand::Append(
QueueItem::from_item_type(
QueueItemType::Single(
let (command, tx) =
QueueCommandInput::command(QueueCommand::Append(
QueueItem::from_item_type(QueueItemType::Single(
QueueSong {
song: song.clone(),
location: np_song.location
}
)
),
false
)
);
queue_mail.send(command).await
.unwrap();
let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap() else {
location: np_song.location,
},
)),
false,
));
queue_mail.send(command).await.unwrap();
let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap()
else {
unreachable!()
};
} else {
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.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
} QueueResponse::Item(Err(e)) => {
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
notify_connections_
.send(ConnectionsNotification::SongChange(np_song.song))
.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() {
QueueResponse::Item(Ok(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!(),
};
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.play();
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap();
let QueueItemType::Single(np_song) = item.item else {
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.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
notify_connections_
.send(ConnectionsNotification::SongChange(np_song.song))
.unwrap();
}
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) => {
let (command, tx) = QueueCommandInput::command(QueueCommand::GetIndex(index));
queue_mail
.send(command)
.await
.unwrap();
let (command, tx) =
QueueCommandInput::command(QueueCommand::GetIndex(index));
queue_mail.send(command).await.unwrap();
match tx.recv().await.unwrap() {
QueueResponse::Item(Ok(item)) => {
match item.item {
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.play();
state.now_playing = np_song.song.uuid;
_ = state.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
notify_connections_
.send(ConnectionsNotification::SongChange(np_song.song))
.unwrap();
}
_ => unimplemented!(),
}
res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
}
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) => {
// 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();
let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else {
unreachable!()
@ -187,27 +243,40 @@ impl Controller {
match tx.recv().await.unwrap() {
QueueResponse::Empty(Ok(())) => (),
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;
}
_ => unreachable!()
_ => unreachable!(),
}
let (command, tx) = QueueCommandInput::command(
QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: np_song.clone(), location })), true)
);
let (command, tx) = QueueCommandInput::command(QueueCommand::Append(
QueueItem::from_item_type(QueueItemType::Single(QueueSong {
song: np_song.clone(),
location,
})),
true,
));
queue_mail.send(command).await.unwrap();
match tx.recv().await.unwrap() {
QueueResponse::Empty(Ok(())) => (),
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;
}
_ => unreachable!()
_ => unreachable!(),
}
// 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.play();
@ -217,39 +286,54 @@ impl Controller {
let (songs, index) = match location {
PlayerLocation::Library => {
let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs);
let (command, tx) =
LibraryCommandInput::command(LibraryCommand::AllSongs);
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!()
};
(songs, index)
}
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();
let LibraryResponse::ExternalPlaylist(list) = tx.recv().await.unwrap() else {
let LibraryResponse::ExternalPlaylist(list) =
tx.recv().await.unwrap()
else {
unreachable!()
};
let index = list.get_index(np_song.uuid).unwrap();
(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) {
if let Some(song) = songs.get(i) {
let (command, tx) = QueueCommandInput::command(
QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), false)
);
let (command, tx) =
QueueCommandInput::command(QueueCommand::Append(
QueueItem::from_item_type(QueueItemType::Single(
QueueSong {
song: song.clone(),
location,
},
)),
false,
));
queue_mail.send(command).await.unwrap();
match tx.recv().await.unwrap() {
QueueResponse::Empty(Ok(())) => (),
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;
}
_ => unreachable!()
_ => unreachable!(),
}
} else {
println!("End of Library / Playlist");
@ -257,11 +341,16 @@ impl Controller {
}
}
// ^ 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.write_file();
notify_connections_.send(ConnectionsNotification::SongChange(np_song)).unwrap();
notify_connections_
.send(ConnectionsNotification::SongChange(np_song))
.unwrap();
}
}
} else {

View file

@ -5,9 +5,16 @@ use crossbeam::atomic::AtomicCell;
use crossbeam_channel::{Receiver, Sender};
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 {
pub(super) fn player_monitor_loop(
@ -17,7 +24,7 @@ impl Controller {
player_mail: async_channel::Sender<PlayerCommandInput>,
notify_next_song: Sender<Song>,
notify_connections_: Sender<ConnectionsNotification>,
playback_info: Arc<AtomicCell<PlaybackInfo>>
playback_info: Arc<AtomicCell<PlaybackInfo>>,
) -> Result<(), ()> {
std::thread::scope(|s| {
// Thread for timing and metadata
@ -27,7 +34,12 @@ impl Controller {
println!("playback monitor started");
while true {
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 });
}
}
@ -35,7 +47,8 @@ impl Controller {
// Thread for End of Track
let notify_connections = notify_connections_.clone();
s.spawn(move || { futures::executor::block_on(async {
s.spawn(move || {
futures::executor::block_on(async {
println!("EOS monitor started");
while true {
let _ = finished_recv.recv();
@ -48,12 +61,17 @@ impl Controller {
};
if let Ok(song) = res {
notify_next_song.send(song.clone()).unwrap();
notify_connections.send(ConnectionsNotification::SongChange(song)).unwrap();
notify_connections.send(ConnectionsNotification::EOS).unwrap();
notify_connections
.send(ConnectionsNotification::SongChange(song))
.unwrap();
notify_connections
.send(ConnectionsNotification::EOS)
.unwrap();
}
}
std::thread::sleep(Duration::from_millis(100));
});});
});
});
let notify_connections = notify_connections_.clone();
s.spawn(move || {
@ -63,7 +81,9 @@ impl Controller {
if _state != state {
state = _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));
}

View file

@ -1,6 +1,10 @@
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 {
pub(super) async fn queue_loop(
@ -15,45 +19,55 @@ impl Controller {
QueueItemType::Single(song) => queue.add_item(song, by_human),
_ => unimplemented!(),
}
res_rx
.send(QueueResponse::Empty(Ok(())))
.await
.unwrap();
},
res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap();
}
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
.send(QueueResponse::Item(next.clone()))
.await
.unwrap();
}
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
.send(QueueResponse::Item(prev.clone()))
.await
.unwrap();
}
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();
}
QueueCommand::NowPlaying => {
let item = queue.current().map(|t| t.clone());
res_rx
.send(QueueResponse::Item(item))
.await
.unwrap();
res_rx.send(QueueResponse::Item(item)).await.unwrap();
}
QueueCommand::Get => {
res_rx.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap();
res_rx
.send(QueueResponse::GetAll(queue.items.clone()))
.await
.unwrap();
}
QueueCommand::Clear => {
queue.clear();
res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap();
}
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") {
count3 += 1;
//check for skipped IDs
if &count3.to_string()
!= song_tags.get_key_value("Track ID").unwrap().1
{
if &count3.to_string() != song_tags.get_key_value("Track ID").unwrap().1 {
count3 += 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 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 tag = match Probe::open(file)?.options(normal_options).read() {
@ -284,7 +283,9 @@ impl ITunesSong {
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();
//get the path with the first bit chopped off
let path_: String = map.get_key_value("Location").unwrap().1.clone();
@ -351,7 +352,7 @@ mod tests {
PathBuf::from("test-config/library2"),
String::from("library2"),
None,
None
None,
);
config.libraries.libraries.push(config_lib.clone());

View file

@ -66,7 +66,6 @@ pub enum Tag {
Field(String),
}
impl Display for Tag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let path_str: String = match self {
@ -228,7 +227,8 @@ impl Song {
/// Creates a [`Song`] from a music file
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 tagged_file: lofty::file::TaggedFile;
@ -480,8 +480,12 @@ impl Song {
match art {
AlbumArt::Embedded(j) => {
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) => {
let mut buf = vec![];
std::fs::File::open(path.path())?.read_to_end(&mut buf)?;
@ -491,7 +495,6 @@ impl Song {
} else {
Ok(None)
}
}
}
@ -1236,15 +1239,12 @@ impl MusicLibrary {
#[cfg(test)]
mod test {
use std::{path::PathBuf, time::Instant};
use crate::music_storage::library::Tag;
use std::{path::PathBuf, time::Instant};
use uuid::Uuid;
use crate::{
config::Config,
music_storage::library::MusicLibrary,
};
use crate::{config::Config, music_storage::library::MusicLibrary};
#[test]
fn library_init() {
@ -1263,10 +1263,12 @@ mod test {
let lib = MusicLibrary::init(
PathBuf::from("/media/g2/Storage4/Media-Files/Music/Albums/library.dlib"),
Uuid::new_v4(),
).unwrap();
)
.unwrap();
let timer = Instant::now();
let result = lib.query_tracks(
let result = lib
.query_tracks(
&String::from(""),
&vec![],
&vec![
@ -1275,8 +1277,13 @@ mod test {
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 {

View file

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

View file

@ -26,13 +26,14 @@ pub enum QueueItemType<
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
> {
Single(T),
Multi(U)
Multi(U),
}
impl<
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
> QueueItemType<T, U> {
> QueueItemType<T, U>
{
pub fn from_single(item: T) -> Self {
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 {
QueueItem {
item,
@ -69,10 +65,7 @@ pub struct Queue<
}
// TODO: HAndle the First QueueState[looping] and shuffle
impl<
T: Debug + Clone + PartialEq,
U: Debug + PartialEq + Clone + IntoIterator,
> Queue<T, U> {
impl<T: Debug + Clone + PartialEq, U: Debug + PartialEq + Clone + IntoIterator> Queue<T, U> {
fn has_addhere(&self) -> bool {
for item in &self.items {
if item.state == QueueState::AddHere {
@ -98,7 +91,7 @@ impl<
items: Vec::new(),
played: Vec::new(),
loop_,
shuffle
shuffle,
}
}
@ -130,7 +123,10 @@ impl<
let empty = self.items.is_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 {
@ -143,13 +139,11 @@ impl<
},
);
} else {
self.items.push(
QueueItem {
self.items.push(QueueItem {
item,
state: QueueState::NoState,
by_human,
}
);
});
}
}
@ -196,7 +190,10 @@ impl<
let empty = self.items.is_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();
@ -211,13 +208,11 @@ impl<
},
);
} else {
self.items.push(
QueueItem {
self.items.push(QueueItem {
item,
state: QueueState::NoState,
by_human, // false
},
);
});
}
}
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 {
unimplemented!(); // TODO: Handle Multi items here?
} if let QueueItemType::Multi(_) = item.item {
}
if let QueueItemType::Multi(_) = item.item {
unimplemented!(); // TODO: Handle Multi items here?
}
@ -447,7 +443,6 @@ impl<
}
}
use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)]

View file

@ -1,6 +1,9 @@
use std::{fs::OpenOptions, io::Write};
use dmp_core::music_controller::{controller::{ControllerHandle, PlayerLocation}, queue::QueueSong};
use dmp_core::music_controller::{
controller::{ControllerHandle, PlayerLocation},
queue::QueueSong,
};
use kushi::QueueItem;
use tauri::{AppHandle, Emitter, State, Wry};
use tempfile::TempDir;
@ -8,25 +11,38 @@ 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> {
pub async fn add_song_to_queue(
app: AppHandle<Wry>,
ctrl_handle: State<'_, ControllerHandle>,
uuid: Uuid,
location: PlayerLocation,
) -> Result<(), String> {
dbg!(&location);
let (song, _) = ctrl_handle.lib_get_song(uuid).await;
match ctrl_handle.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location }))).await {
match ctrl_handle
.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(
QueueSong { song, location },
)))
.await
{
Ok(()) => (),
Err(e) => return Err(e.to_string())
Err(e) => return Err(e.to_string()),
}
app.emit("queue_updated", ()).unwrap();
Ok(())
}
#[tauri::command]
pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
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 {
Ok(song) => song,
Err(e) => return Err(e.to_string())
Err(e) => return Err(e.to_string()),
};
app.emit("queue_updated", ()).unwrap();
app.emit("now_playing_change", _Song::from(&song)).unwrap();
@ -35,11 +51,18 @@ pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHand
}
#[tauri::command]
pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_dir: State<'_, TempDir>, uuid: Uuid) -> Result<(), String> {
pub async fn display_album_art(
ctrl_handle: State<'_, ControllerHandle>,
temp_dir: State<'_, TempDir>,
uuid: Uuid,
) -> 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()));
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()
@ -53,7 +76,7 @@ pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_di
}
opener::open(path).unwrap();
}
Err(e) => return Err(e.to_string())
Err(e) => return Err(e.to_string()),
};
Ok(())
}

View file

@ -1,21 +1,36 @@
#![allow(while_true)]
use std::{fs, path::PathBuf, sync::Arc, thread::{scope, spawn}, time::Duration};
use std::{
fs,
path::PathBuf,
sync::Arc,
thread::{scope, spawn},
time::Duration,
};
use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::{connections::ConnectionsInput, controller::{Controller, ControllerHandle, PlaybackInfo}}, music_storage::library::{MusicLibrary, Song}};
use dmp_core::{
config::{Config, ConfigLibrary},
music_controller::{
connections::ConnectionsInput,
controller::{Controller, ControllerHandle, PlaybackInfo},
},
music_storage::library::{MusicLibrary, Song},
};
use futures::channel::oneshot;
use parking_lot::RwLock;
use tauri::{http::Response, Emitter, Manager, State, Wry};
use uuid::Uuid;
use wrappers::{_Song, stop};
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};
use commands::{add_song_to_queue, play_now, display_album_art};
use crate::wrappers::{
get_library, get_playlist, get_playlists, get_queue, get_song, import_playlist, next, pause,
play, prev, remove_from_queue, seek, set_volume,
};
use commands::{add_song_to_queue, display_album_art, play_now};
pub mod wrappers;
pub mod commands;
pub mod wrappers;
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png");
@ -46,18 +61,32 @@ pub fn run() {
} else {
_lib.path.clone()
};
println!("save_path: {}\nscan_path:{scan_path:?}", save_path.display());
println!(
"save_path: {}\nscan_path:{scan_path:?}",
save_path.display()
);
let mut library = MusicLibrary::init(
save_path.clone(),
_lib.uuid
).unwrap();
let mut library = MusicLibrary::init(save_path.clone(), _lib.uuid).unwrap();
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone());
let scan_path = scan_path.unwrap_or_else(|| {
config
.libraries
.get_default()
.unwrap()
.scan_folders
.as_ref()
.unwrap()[0]
.clone()
});
if config.libraries.get_default().is_err() {
library.scan_folder(&scan_path).unwrap();
config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()]), Some(library.uuid)));
config.push_library(ConfigLibrary::new(
save_path.clone(),
String::from("Library"),
Some(vec![scan_path.clone()]),
Some(library.uuid),
));
}
if library.library.is_empty() {
println!("library is empty");
@ -68,16 +97,12 @@ pub fn run() {
library.save(save_path).unwrap();
let (
handle,
input,
playback_info,
next_song_notification,
) = ControllerHandle::new(
let (handle, input, playback_info, next_song_notification) = ControllerHandle::new(
library,
std::sync::Arc::new(RwLock::new(config)),
Some(ConnectionsInput {
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID").map(|id| id.parse::<u64>().unwrap()),
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID")
.map(|id| id.parse::<u64>().unwrap()),
}),
);
@ -86,9 +111,6 @@ pub fn run() {
next_rx.send(next_song_notification).unwrap();
let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
});
let app = tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
@ -113,7 +135,8 @@ pub fn run() {
remove_from_queue,
display_album_art,
seek,
]).manage(ConfigRx(rx))
])
.manage(ConfigRx(rx))
.manage(LibRx(lib_rx))
.manage(HandleTx(handle_tx))
.manage(tempfile::TempDir::new().unwrap())
@ -124,7 +147,8 @@ pub fn run() {
std::thread::Builder::new()
.name("PlaybackInfo handler".to_string())
.spawn(move || {
let mut _info: Arc<RwLock<PlaybackInfo>> = Arc::new(RwLock::new(PlaybackInfo::default()));
let mut _info: Arc<RwLock<PlaybackInfo>> =
Arc::new(RwLock::new(PlaybackInfo::default()));
let mut _now_playing: Arc<RwLock<Option<Song>>> = Arc::new(RwLock::new(None));
scope(|s| {
@ -153,7 +177,8 @@ pub fn run() {
}
});
});
}).unwrap();
})
.unwrap();
Ok(())
})
@ -171,11 +196,20 @@ pub fn run() {
let bytes = if query.as_str() == "default" {
Some(DEFAULT_IMAGE.to_vec())
} else {futures::executor::block_on(async move {
} else {
futures::executor::block_on(async move {
let controller = ctx.app_handle().state::<ControllerHandle>();
let song = controller.lib_get_song(Uuid::parse_str(query.as_str()).unwrap()).await.0;
Some(song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec()))
})};
let song = controller
.lib_get_song(Uuid::parse_str(query.as_str()).unwrap())
.await
.0;
Some(
song.album_art(0)
.unwrap_or_else(|_| None)
.unwrap_or(DEFAULT_IMAGE.to_vec()),
)
})
};
res.respond(
Response::builder()
@ -183,14 +217,13 @@ pub fn run() {
.header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len())
.status(200)
.body(bytes.unwrap_or_default())
.unwrap()
.unwrap(),
);
})
.build(tauri::generate_context!())
.expect("error while building tauri application");
app
.run(|_app_handle, event| match event {
app.run(|_app_handle, event| match event {
tauri::RunEvent::ExitRequested { .. } => {
// api.prevent_exit();
//panic!("does this kill the player?")
@ -204,18 +237,19 @@ struct ConfigRx(Sender<Config>);
struct LibRx(Sender<Option<PathBuf>>);
struct HandleTx(Receiver<ControllerHandle>);
#[tauri::command]
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") {
let path = dir.config_dir();
fs::create_dir_all(path).or_else(|err| {
fs::create_dir_all(path)
.or_else(|err| {
if err.kind() == std::io::ErrorKind::AlreadyExists {
Ok(())
} else {
Err(err)
}
}).unwrap();
})
.unwrap();
let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) {
if c.state_path == PathBuf::default() {
@ -268,7 +302,11 @@ async fn create_new_library(
}
#[tauri::command]
async fn lib_already_created(app: tauri::AppHandle<Wry>, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> {
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());

View file

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

View file

@ -1,8 +1,11 @@
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 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 kushi::QueueItemType;
use serde::Serialize;
@ -12,40 +15,52 @@ use uuid::Uuid;
pub struct ArtworkRx(pub Sender<Vec<u8>>);
#[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 {
Ok(()) => {
app.emit("playing", ()).unwrap();
Ok(())
},
Err(e) => Err(e.to_string())
}
Err(e) => Err(e.to_string()),
}
}
#[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 {
Ok(()) => {
app.emit("paused", ()).unwrap();
Ok(())
}
Err(e) => Err(e.to_string())
Err(e) => Err(e.to_string()),
}
}
#[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 {
Ok(()) => {
app.emit("stop", ()).unwrap();
Ok(())
}
Err(e) => Err(e.to_string())
Err(e) => Err(e.to_string()),
}
}
#[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;
ctrl_handle.set_volume(volume).await;
Ok(())
@ -57,10 +72,13 @@ pub async fn get_volume(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
}
#[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 {
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("queue_updated", ()).unwrap();
@ -69,10 +87,13 @@ pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
}
#[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 {
Ok(s) => s,
Err(e) => return Err(e.to_string())
Err(e) => return Err(e.to_string()),
};
println!("prev");
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]
pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
Ok(())
}
#[tauri::command]
pub async fn get_queue(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<(_Song, PlayerLocation)>, String> {
Ok(
ctrl_handle
pub async fn get_queue(
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<Vec<(_Song, PlayerLocation)>, String> {
Ok(ctrl_handle
.queue_get_all()
.await
.into_iter()
.map(|item| {
let QueueItemType::Single(song) = item.item else { unreachable!("There should be no albums in the queue right now") };
let QueueItemType::Single(song) = item.item else {
unreachable!("There should be no albums in the queue right now")
};
(_Song::from(&song.song), song.location)
}
).collect_vec()
)
})
.collect_vec())
}
#[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 {
Ok(_) => {
app.emit("queue_updated", ()).unwrap();
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,
date_added: value.date_added,
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,26 +187,49 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec
}
#[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 {
Ok(list) => list,
Err(_) => todo!()
Err(_) => todo!(),
};
let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len());
let songs = playlist
.tracks
.iter()
.map(|song| _Song::from(song))
.collect::<Vec<_>>();
println!(
"Got Playlist {}, len {}",
playlist.title,
playlist.tracks.len()
);
Ok(songs)
}
#[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;
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(())
}
#[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()
.add_filter("m3u8 Playlist", &["m3u8", "m3u"])
.set_title("Import a Playlist")
@ -184,7 +237,10 @@ pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result
.await
.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;
println!("Imported Playlist {name}");
@ -194,13 +250,19 @@ pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result
#[derive(Serialize, Clone)]
pub struct PlaylistPayload {
uuid: Uuid,
name: String
name: String,
}
#[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;
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))
}