diff --git a/musicdb-client/Cargo.toml b/musicdb-client/Cargo.toml index e884b76..e7aaf3d 100644 --- a/musicdb-client/Cargo.toml +++ b/musicdb-client/Cargo.toml @@ -12,7 +12,7 @@ directories = "5.0.1" regex = "1.9.3" speedy2d = { version = "1.12.0", optional = true } toml = "0.7.6" -musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true } +# musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true } uianimator = "0.1.1" [features] @@ -26,8 +26,8 @@ default = ["gui", "default-playback"] # playback: # enables syncplayer modes, where the client mirrors the server's playback gui = ["speedy2d"] -merscfg = ["mers", "gui"] -mers = ["musicdb-mers"] +# merscfg = ["mers", "gui"] +# mers = ["musicdb-mers"] playback = [] default-playback = ["playback", "musicdb-lib/default-playback"] playback-via-playback-rs = ["playback", "musicdb-lib/playback-via-playback-rs"] diff --git a/musicdb-client/Cross.toml b/musicdb-client/Cross.toml index d222800..b62bc46 100644 --- a/musicdb-client/Cross.toml +++ b/musicdb-client/Cross.toml @@ -1,6 +1,13 @@ +# [build] +# pre-build = [ +# "dpkg --add-architecture $CROSS_DEB_ARCH", +# "apt-get update && apt-get --assume-yes install libasound2-dev libasound2-dev:$CROSS_DEB_ARCH" +# ] +# default-target = "aarch64-unknown-linux-gnu" + [build] pre-build = [ "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get --assume-yes install libasound2-dev libasound2-dev:$CROSS_DEB_ARCH" + "apt-get update && apt-get --assume-yes install libasound2-dev" ] -default-target = "aarch64-unknown-linux-gnu" +default-target = "x86_64-unknown-linux-musl" diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index 34828e2..c6215e2 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -362,90 +362,124 @@ impl Gui { } Ok(Ok(Ok(()))) => eprintln!("Info: using merscfg"), } - database.lock().unwrap().update_endpoints.push( - musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| { - match &cmd.action { - Action::Resume - | Action::Pause - | Action::Stop - | Action::Save - | Action::InitComplete => {} - Action::NextSong - | Action::QueueUpdate(..) - | Action::QueueAdd(..) - | Action::QueueInsert(..) - | Action::QueueRemove(..) - | Action::QueueMove(..) - | Action::QueueMoveInto(..) - | Action::QueueGoto(..) - | Action::QueueShuffle(..) - | Action::QueueSetShuffle(..) - | Action::QueueUnshuffle(..) => { - if let Some(s) = &*event_sender_arc.lock().unwrap() { - _ = s.send_event(GuiEvent::UpdatedQueue); + { + let mut db = database.lock().unwrap(); + let udepid = db.update_endpoints_id; + db.update_endpoints_id += 1; + db.update_endpoints.push(( + udepid, + musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new( + move |cmd| match &cmd.action { + Action::Resume + | Action::Pause + | Action::Stop + | Action::Save + | Action::InitComplete => {} + Action::NextSong + | Action::QueueUpdate(..) + | Action::QueueAdd(..) + | Action::QueueInsert(..) + | Action::QueueRemove(..) + | Action::QueueMove(..) + | Action::QueueMoveInto(..) + | Action::QueueGoto(..) + | Action::QueueShuffle(..) + | Action::QueueSetShuffle(..) + | Action::QueueUnshuffle(..) => { + if let Some(s) = &*event_sender_arc.lock().unwrap() { + _ = s.send_event(GuiEvent::UpdatedQueue); + } } - } - Action::SyncDatabase(..) - | Action::AddSong(_) - | Action::AddAlbum(_) - | Action::AddArtist(_) - | Action::AddCover(_) - | Action::ModifySong(_) - | Action::ModifyAlbum(_) - | Action::ModifyArtist(_) - | Action::RemoveSong(_) - | Action::RemoveAlbum(_) - | Action::RemoveArtist(_) - | Action::TagSongFlagSet(..) - | Action::TagSongFlagUnset(..) - | Action::TagAlbumFlagSet(..) - | Action::TagAlbumFlagUnset(..) - | Action::TagArtistFlagSet(..) - | Action::TagArtistFlagUnset(..) - | Action::TagSongPropertySet(..) - | Action::TagSongPropertyUnset(..) - | Action::TagAlbumPropertySet(..) - | Action::TagAlbumPropertyUnset(..) - | Action::TagArtistPropertySet(..) - | Action::TagArtistPropertyUnset(..) - | Action::SetSongDuration(..) => { - if let Some(s) = &*event_sender_arc.lock().unwrap() { - _ = s.send_event(GuiEvent::UpdatedLibrary); + Action::SyncDatabase(..) + | Action::AddSong(_, _) + | Action::AddAlbum(_, _) + | Action::AddArtist(_, _) + | Action::AddCover(_, _) + | Action::ModifySong(_, _) + | Action::ModifyAlbum(_, _) + | Action::ModifyArtist(_, _) + | Action::RemoveSong(_) + | Action::RemoveAlbum(_) + | Action::RemoveArtist(_) + | Action::TagSongFlagSet(..) + | Action::TagSongFlagUnset(..) + | Action::TagAlbumFlagSet(..) + | Action::TagAlbumFlagUnset(..) + | Action::TagArtistFlagSet(..) + | Action::TagArtistFlagUnset(..) + | Action::TagSongPropertySet(..) + | Action::TagSongPropertyUnset(..) + | Action::TagAlbumPropertySet(..) + | Action::TagAlbumPropertyUnset(..) + | Action::TagArtistPropertySet(..) + | Action::TagArtistPropertyUnset(..) + | Action::SetSongDuration(..) => { + if let Some(s) = &*event_sender_arc.lock().unwrap() { + _ = s.send_event(GuiEvent::UpdatedLibrary); + } } - } - Action::ErrorInfo(t, d) => { - let (t, d) = (t.clone(), d.clone()); - notif_sender_two - .send(Box::new(move |_| { - ( - Box::new(Panel::with_background( - GuiElemCfg::default(), - [Label::new( + Action::ErrorInfo(t, d) => { + let (t, d) = (t.clone(), d.clone()); + notif_sender_two + .send(Box::new(move |_| { + ( + Box::new(Panel::with_background( GuiElemCfg::default(), - if t.is_empty() { - format!("Server message\n{d}") - } else { - format!("Server error ({t})\n{d}") - }, - Color::WHITE, - None, - Vec2::new(0.5, 0.5), - )], - Color::from_rgba(0.0, 0.0, 0.0, 0.8), - )), - if t.is_empty() { - NotifInfo::new(Duration::from_secs(2)) - } else { - NotifInfo::new(Duration::from_secs(5)) - .with_highlight(Color::RED) - }, - ) - })) - .unwrap(); - } - } - })), - ); + [Label::new( + GuiElemCfg::default(), + if t.is_empty() { + format!("Server message\n{d}") + } else { + format!("Server error ({t})\n{d}") + }, + Color::WHITE, + None, + Vec2::new(0.5, 0.5), + )], + Color::from_rgba(0.0, 0.0, 0.0, 0.8), + )), + if t.is_empty() { + NotifInfo::new(Duration::from_secs(2)) + } else { + NotifInfo::new(Duration::from_secs(5)) + .with_highlight(Color::RED) + }, + ) + })) + .unwrap(); + } + Action::Denied(req) => { + let req = *req; + notif_sender_two + .send(Box::new(move |_| { + ( + Box::new(Panel::with_background( + GuiElemCfg::default(), + [Label::new( + GuiElemCfg::default(), + format!( + "server denied {}", + if req.is_some() { + "request, maybe desynced" + } else { + "action, likely desynced" + }, + ), + Color::WHITE, + None, + Vec2::new(0.5, 0.5), + )], + Color::from_rgba(0.0, 0.0, 0.0, 0.8), + )), + NotifInfo::new(Duration::from_secs(1)), + ) + })) + .unwrap(); + } + }, + )), + )); + } let no_animations = false; Gui { event_sender, @@ -1307,16 +1341,10 @@ impl Gui { } } GuiAction::SendToServer(action) => { + let command = self.database.lock().unwrap().seq.pack(action); #[cfg(debug_assertions)] - eprintln!("[DEBUG] Sending command to server: {action:?}"); - if let Err(e) = self - .database - .lock() - .unwrap() - .seq - .pack(action) - .to_bytes(&mut self.connection) - { + eprintln!("[DEBUG] Sending command to server: {command:?}"); + if let Err(e) = command.to_bytes(&mut self.connection) { eprintln!("Error sending command to server: {e}"); } } diff --git a/musicdb-client/src/gui_edit_song.rs b/musicdb-client/src/gui_edit_song.rs index 52436a3..cf2f975 100644 --- a/musicdb-client/src/gui_edit_song.rs +++ b/musicdb-client/src/gui_edit_song.rs @@ -2,7 +2,7 @@ use std::time::Instant; use musicdb_lib::{ data::{song::Song, ArtistId}, - server::Action, + server::{Action, Req}, }; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle}; @@ -189,7 +189,10 @@ impl GuiElem for EditorForSongs { song.album = None; } info.actions - .push(GuiAction::SendToServer(Action::ModifySong(song))); + .push(GuiAction::SendToServer(Action::ModifySong( + song, + Req::none(), + ))); } } Event::SetArtist(name, id) => { diff --git a/musicdb-client/src/gui_queue.rs b/musicdb-client/src/gui_queue.rs index 3a08f0d..b49ae35 100755 --- a/musicdb-client/src/gui_queue.rs +++ b/musicdb-client/src/gui_queue.rs @@ -5,7 +5,7 @@ use musicdb_lib::{ song::Song, AlbumId, ArtistId, }, - server::Action, + server::{Action, Req}, }; use speedy2d::{ color::Color, @@ -404,7 +404,7 @@ impl GuiElem for QueueEmptySpaceDragHandler { dragged_add_to_queue( dragged, (), - |_, q| Action::QueueAdd(vec![], q), + |_, q| Action::QueueAdd(vec![], q, Req::none()), |_, q| Action::QueueMoveInto(q, vec![]), ) } @@ -631,9 +631,9 @@ impl GuiElem for QueueSong { self.path.clone(), move |mut p: Vec, q| { if let Some(j) = p.pop() { - Action::QueueInsert(p, if insert_below { j + 1 } else { j }, q) + Action::QueueInsert(p, if insert_below { j + 1 } else { j }, q, Req::none()) } else { - Action::QueueAdd(p, q) + Action::QueueAdd(p, q, Req::none()) } }, move |mut p, q| { @@ -826,7 +826,7 @@ impl GuiElem for QueueFolder { dragged_add_to_queue( dragged, self.path.clone(), - |p, q| Action::QueueAdd(p, q), + |p, q| Action::QueueAdd(p, q, Req::none()), |p, q| Action::QueueMoveInto(q, p), ) } else { @@ -835,7 +835,7 @@ impl GuiElem for QueueFolder { self.path.clone(), |mut p, q| { let j = p.pop().unwrap_or(0); - Action::QueueInsert(p, j, q) + Action::QueueInsert(p, j, q, Req::none()) }, |p, q| Action::QueueMove(q, p), ) @@ -903,7 +903,7 @@ impl GuiElem for QueueIndentEnd { dragged_add_to_queue( dragged, self.path_insert.clone(), - |(p, j), q| Action::QueueInsert(p, j, q), + |(p, j), q| Action::QueueInsert(p, j, q, Req::none()), |(mut p, j), q| { p.push(j); Action::QueueMove(q, p) @@ -1066,7 +1066,7 @@ impl GuiElem for QueueLoop { dragged_add_to_queue( dragged, p, - |p, q| Action::QueueAdd(p, q), + |p, q| Action::QueueAdd(p, q, Req::none()), |p, q| Action::QueueMoveInto(q, p), ) } else { diff --git a/musicdb-client/src/gui_screen.rs b/musicdb-client/src/gui_screen.rs index f3f3599..776c1f0 100755 --- a/musicdb-client/src/gui_screen.rs +++ b/musicdb-client/src/gui_screen.rs @@ -2,7 +2,7 @@ use std::time::Instant; use musicdb_lib::{ data::queue::{QueueContent, QueueFolder}, - server::Action, + server::{Action, Req}, }; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D}; use uianimator::{default_animator_f64_quadratic::DefaultAnimatorF64Quadratic, Animator}; @@ -126,6 +126,7 @@ impl GuiScreen { musicdb_lib::data::queue::QueueFolder::default(), ) .into(), + Req::none(), ))] }, [Label::new( diff --git a/musicdb-client/src/main.rs b/musicdb-client/src/main.rs index aea10f5..fd72e0d 100755 --- a/musicdb-client/src/main.rs +++ b/musicdb-client/src/main.rs @@ -203,7 +203,7 @@ fn main() { action(action); break 'feature_if; } - db.apply_action_unchecked_seq(action); + db.apply_action_unchecked_seq(action, None); } #[cfg(feature = "playback")] if let Some(player) = &mut player { diff --git a/musicdb-lib/src/data/album.rs b/musicdb-lib/src/data/album.rs index 5cb7fe6..9de535b 100755 --- a/musicdb-lib/src/data/album.rs +++ b/musicdb-lib/src/data/album.rs @@ -4,7 +4,7 @@ use crate::load::ToFromBytes; use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Album { pub id: AlbumId, pub name: String, diff --git a/musicdb-lib/src/data/artist.rs b/musicdb-lib/src/data/artist.rs index e5067dd..3b3fd90 100755 --- a/musicdb-lib/src/data/artist.rs +++ b/musicdb-lib/src/data/artist.rs @@ -4,7 +4,7 @@ use crate::load::ToFromBytes; use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Artist { pub id: ArtistId, pub name: String, diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index d9518de..4bb0fd8 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -13,7 +13,7 @@ use rand::thread_rng; use crate::{ load::ToFromBytes, - server::{Action, Command, Commander}, + server::{Action, Command, Commander, Req}, }; use super::{ @@ -45,10 +45,11 @@ pub struct Database { pub queue: Queue, /// if the database receives an update, it will inform all of its clients so they can stay in sync. /// this is a list containing all the clients. - pub update_endpoints: Vec, + pub update_endpoints: Vec<(u64, UpdateEndpoint)>, + pub update_endpoints_id: u64, /// true if a song is/should be playing pub playing: bool, - pub command_sender: Option>, + pub command_sender: Option)>>, pub remote_server_as_song_file_source: Option>>>>, /// only relevant for clients. true if init is done @@ -65,7 +66,7 @@ pub enum UpdateEndpoint { Bytes(Box), CmdChannel(mpsc::Sender>), Custom(Box), - CustomArc(Box) + Send>), + CustomArc(Box) + Send>), CustomBytes(Box), } @@ -507,7 +508,7 @@ impl Database { )) .to_bytes(con)?; self.seq - .pack(Action::QueueUpdate(vec![], self.queue.clone())) + .pack(Action::QueueUpdate(vec![], self.queue.clone(), Req::none())) .to_bytes(con)?; if self.playing { self.seq.pack(Action::Resume).to_bytes(con)?; @@ -521,8 +522,29 @@ impl Database { } /// `apply_action_unchecked_seq(command.action)` if `command.seq` is correct or `0xFF` - pub fn apply_command(&mut self, command: Command) { + pub fn apply_command(&mut self, mut command: Command, client: Option) { if command.seq != self.seq.seq() && command.seq != 0xFF { + if let Some(client) = client { + for (udepid, udep) in &mut self.update_endpoints { + if client == *udepid { + let denied = + Action::Denied(command.action.get_req().unwrap_or_else(Req::none)) + .cmd(0xFFu8); + match udep { + UpdateEndpoint::Bytes(w) => { + let _ = w.write(&denied.to_bytes_vec()); + } + UpdateEndpoint::CmdChannel(w) => { + let _ = w.send(Arc::new(denied)); + } + UpdateEndpoint::Custom(w) => w(&denied), + UpdateEndpoint::CustomArc(w) => w(Arc::new(denied)), + UpdateEndpoint::CustomBytes(w) => w(&denied.to_bytes_vec()), + } + return; + } + } + } eprintln!( "Invalid sequence number: got {} but expected {}.", command.seq, @@ -530,9 +552,9 @@ impl Database { ); return; } - self.apply_action_unchecked_seq(command.action) + self.apply_action_unchecked_seq(command.action, client) } - pub fn apply_action_unchecked_seq(&mut self, mut action: Action) { + pub fn apply_action_unchecked_seq(&mut self, mut action: Action, client: Option) { if !self.is_client() { if let Action::ErrorInfo(t, _) = &mut action { // clients can send ErrorInfo to the server and it will show up on other clients, @@ -548,7 +570,7 @@ impl Database { Action::Pause if !self.playing => (), Action::Resume if self.playing => (), // since db.update_endpoints is empty for clients, this won't cause unwanted back and forth - _ => action = self.broadcast_update(action), + _ => action = self.broadcast_update(action, client), } match action { Action::Resume => self.playing = true, @@ -557,7 +579,7 @@ impl Database { Action::NextSong => { if !Queue::advance_index_db(self) { // end of queue - self.apply_action_unchecked_seq(Action::Pause); + self.apply_action_unchecked_seq(Action::Pause, client); self.queue.init(); } } @@ -567,17 +589,17 @@ impl Database { } } Action::SyncDatabase(a, b, c) => self.sync(a, b, c), - Action::QueueUpdate(index, new_data) => { + Action::QueueUpdate(index, new_data, _) => { if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { *v = new_data; } } - Action::QueueAdd(index, new_data) => { + Action::QueueAdd(index, new_data, _) => { if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { v.add_to_end(new_data, false); } } - Action::QueueInsert(index, pos, new_data) => { + Action::QueueInsert(index, pos, new_data, _) => { if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { v.insert(new_data, pos, false); } @@ -662,7 +684,7 @@ impl Database { { let mut ord: Vec = (0..content.len()).collect(); ord.shuffle(&mut thread_rng()); - self.apply_action_unchecked_seq(Action::QueueSetShuffle(path, ord)); + self.apply_action_unchecked_seq(Action::QueueSetShuffle(path, ord), client); } else { eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!"); } @@ -719,23 +741,23 @@ impl Database { } } } - Action::AddSong(song) => { + Action::AddSong(song, _) => { self.add_song_new(song); } - Action::AddAlbum(album) => { + Action::AddAlbum(album, _) => { self.add_album_new(album); } - Action::AddArtist(artist) => { + Action::AddArtist(artist, _) => { self.add_artist_new(artist); } - Action::AddCover(cover) => _ = self.add_cover_new(cover), - Action::ModifySong(song) => { + Action::AddCover(cover, _) => _ = self.add_cover_new(cover), + Action::ModifySong(song, _) => { _ = self.update_song(song); } - Action::ModifyAlbum(album) => { + Action::ModifyAlbum(album, _) => { _ = self.update_album(album); } - Action::ModifyArtist(artist) => { + Action::ModifyArtist(artist, _) => { _ = self.update_artist(artist); } Action::RemoveSong(song) => { @@ -846,6 +868,7 @@ impl Database { self.client_is_init = true; } Action::ErrorInfo(..) => {} + Action::Denied(..) => {} } } } @@ -875,6 +898,7 @@ impl Database { custom_files: None, queue: QueueContent::Folder(QueueFolder::default()).into(), update_endpoints: vec![], + update_endpoints_id: 0, playing: false, command_sender: None, remote_server_as_song_file_source: None, @@ -896,6 +920,7 @@ impl Database { custom_files: None, queue: QueueContent::Folder(QueueFolder::default()).into(), update_endpoints: vec![], + update_endpoints_id: 0, playing: false, command_sender: None, remote_server_as_song_file_source: None, @@ -922,6 +947,7 @@ impl Database { custom_files: None, queue: QueueContent::Folder(QueueFolder::default()).into(), update_endpoints: vec![], + update_endpoints_id: 0, playing: false, command_sender: None, remote_server_as_song_file_source: None, @@ -973,7 +999,7 @@ impl Database { self.times_data_modified = None; Ok(path) } - pub fn broadcast_update(&mut self, update: Action) -> Action { + pub fn broadcast_update(&mut self, update: Action, client: Option) -> Action { match update { Action::InitComplete => return update, _ => {} @@ -981,11 +1007,36 @@ impl Database { if !self.is_client() { self.seq.inc(); } - let update = self.seq.pack(update); + let mut update = self.seq.pack(update); + let req = update.action.take_req(); let mut remove = vec![]; let mut bytes = None; let mut arc = None; - for (i, udep) in self.update_endpoints.iter_mut().enumerate() { + for (i, (udepid, udep)) in self.update_endpoints.iter_mut().enumerate() { + if req.is_some_and(|r| r.is_some()) && client.is_some_and(|v| *udepid == v) { + update.action.put_req(req.unwrap()); + match udep { + UpdateEndpoint::Bytes(writer) => { + if writer.write_all(&update.to_bytes_vec()).is_err() { + remove.push(i); + } + } + UpdateEndpoint::CmdChannel(sender) => { + if sender.send(Arc::new(update.clone())).is_err() { + remove.push(i); + } + } + UpdateEndpoint::Custom(func) => func(&update), + UpdateEndpoint::CustomArc(func) => func(Arc::new(update.clone())), + UpdateEndpoint::CustomBytes(func) => { + if bytes.is_none() { + bytes = Some(update.to_bytes_vec()); + } + func(bytes.as_ref().unwrap()) + } + } + update.action.take_req(); + } match udep { UpdateEndpoint::Bytes(writer) => { if bytes.is_none() { @@ -1008,7 +1059,7 @@ impl Database { if arc.is_none() { arc = Some(Arc::new(update.clone())); } - func(arc.as_ref().unwrap()) + func(Arc::clone(arc.as_ref().unwrap())) } UpdateEndpoint::CustomBytes(func) => { if bytes.is_none() { @@ -1028,6 +1079,9 @@ impl Database { self.update_endpoints.remove(i); } } + if let Some(req) = req { + update.action.put_req(req); + } update.action } pub fn sync(&mut self, artists: Vec, albums: Vec, songs: Vec) { @@ -1078,6 +1132,11 @@ pub struct Cover { pub location: DatabaseLocation, pub data: Arc)>)>>, } +impl PartialEq for Cover { + fn eq(&self, other: &Self) -> bool { + self.location == other.location + } +} impl Cover { pub fn get_bytes_from_file( &self, diff --git a/musicdb-lib/src/data/mod.rs b/musicdb-lib/src/data/mod.rs index 62c0dae..e9a616b 100755 --- a/musicdb-lib/src/data/mod.rs +++ b/musicdb-lib/src/data/mod.rs @@ -17,13 +17,13 @@ pub type AlbumId = u64; pub type ArtistId = u64; pub type CoverId = u64; -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq)] /// general data for songs, albums and artists pub struct GeneralData { pub tags: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] /// the location of a file relative to the lib directory, often Artist/Album/Song.ext or similar pub struct DatabaseLocation { pub rel_path: PathBuf, diff --git a/musicdb-lib/src/data/queue.rs b/musicdb-lib/src/data/queue.rs index cce4742..27acde9 100755 --- a/musicdb-lib/src/data/queue.rs +++ b/musicdb-lib/src/data/queue.rs @@ -4,18 +4,18 @@ use crate::load::ToFromBytes; use super::{database::Database, SongId}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Queue { enabled: bool, content: QueueContent, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum QueueContent { Song(SongId), Folder(QueueFolder), Loop(usize, usize, Box), } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct QueueFolder { pub index: usize, pub content: Vec, diff --git a/musicdb-lib/src/data/song.rs b/musicdb-lib/src/data/song.rs index b3837fd..6198ad3 100755 --- a/musicdb-lib/src/data/song.rs +++ b/musicdb-lib/src/data/song.rs @@ -17,7 +17,7 @@ use super::{ AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Song { pub id: SongId, pub location: DatabaseLocation, @@ -303,6 +303,12 @@ pub struct CachedData( )>, >, ); +impl PartialEq for CachedData { + fn eq(&self, _other: &Self) -> bool { + // for testing + true + } +} impl Clone for CachedData { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) diff --git a/musicdb-lib/src/player/mod.rs b/musicdb-lib/src/player/mod.rs index 0151f5e..502ad53 100755 --- a/musicdb-lib/src/player/mod.rs +++ b/musicdb-lib/src/player/mod.rs @@ -126,7 +126,7 @@ impl> Player { pub fn update_uncache_opt(&mut self, db: &mut Database, allow_uncaching: bool) { if self.allow_sending_commands { if self.allow_sending_commands && self.backend.song_finished() { - db.apply_action_unchecked_seq(Action::NextSong); + db.apply_action_unchecked_seq(Action::NextSong, None); } } @@ -145,7 +145,7 @@ impl> Player { self.backend.next(db.playing, load_duration); if self.allow_sending_commands && load_duration { if let Some(dur) = self.backend.current_song_duration() { - db.apply_action_unchecked_seq(Action::SetSongDuration(id, dur)) + db.apply_action_unchecked_seq(Action::SetSongDuration(id, dur), None) } } } else if let Some(song) = db.get_song(&id) { @@ -169,21 +169,27 @@ impl> Player { self.backend.next(db.playing, load_duration); if self.allow_sending_commands && load_duration { if let Some(dur) = self.backend.current_song_duration() { - db.apply_action_unchecked_seq(Action::SetSongDuration(id, dur)) + db.apply_action_unchecked_seq( + Action::SetSongDuration(id, dur), + None, + ) } } } else { // only show an error if the user tries to play the song. // otherwise, the error might be spammed. if self.allow_sending_commands && db.playing { - db.apply_action_unchecked_seq(Action::ErrorInfo( - format!("Couldn't load bytes for song {id}"), - format!( - "Song: {}\nby {:?} on {:?}", - song.title, song.artist, song.album + db.apply_action_unchecked_seq( + Action::ErrorInfo( + format!("Couldn't load bytes for song {id}"), + format!( + "Song: {}\nby {:?} on {:?}", + song.title, song.artist, song.album + ), ), - )); - db.apply_action_unchecked_seq(Action::NextSong); + None, + ); + db.apply_action_unchecked_seq(Action::NextSong, None); } self.backend.clear(); } diff --git a/musicdb-lib/src/player/playback_rs.rs b/musicdb-lib/src/player/playback_rs.rs index 0b31263..9f38a89 100644 --- a/musicdb-lib/src/player/playback_rs.rs +++ b/musicdb-lib/src/player/playback_rs.rs @@ -13,12 +13,12 @@ pub struct PlayerBackendPlaybackRs { player: playback_rs::Player, current: Option<(SongId, Option, T)>, next: Option<(SongId, Option, T)>, - command_sender: Option>, + command_sender: Option)>>, } impl PlayerBackendPlaybackRs { pub fn new( - command_sender: std::sync::mpsc::Sender, + command_sender: std::sync::mpsc::Sender<(Command, Option)>, ) -> Result> { Self::new_with_optional_command_sending(Some(command_sender)) } @@ -26,7 +26,7 @@ impl PlayerBackendPlaybackRs { Self::new_with_optional_command_sending(None) } pub fn new_with_optional_command_sending( - command_sender: Option>, + command_sender: Option)>>, ) -> Result> { Ok(Self { player: playback_rs::Player::new(None)?, @@ -55,13 +55,14 @@ impl PlayerBackend for PlayerBackendPlaybackRs { Ok(v) => Some(v), Err(e) => { if let Some(s) = &self.command_sender { - s.send( + s.send(( Action::ErrorInfo( format!("Couldn't decode song #{id}!"), format!("Error: {e}"), ) .cmd(0xFFu8), - ) + None, + )) .unwrap(); } None @@ -101,21 +102,22 @@ impl PlayerBackend for PlayerBackendPlaybackRs { if let Some(song) = song { if let Err(e) = self.player.play_song_now(song, None) { if let Some(s) = &self.command_sender { - s.send( + s.send(( Action::ErrorInfo( format!("Couldn't play song #{id}!"), format!("Error: {e}"), ) .cmd(0xFFu8), - ) + None, + )) .unwrap(); - s.send(Action::NextSong.cmd(0xFFu8)).unwrap(); + s.send((Action::NextSong.cmd(0xFFu8), None)).unwrap(); } } else { self.player.set_playing(play); } } else if let Some(s) = &self.command_sender { - s.send(Action::NextSong.cmd(0xFFu8)).unwrap(); + s.send((Action::NextSong.cmd(0xFFu8), None)).unwrap(); } } } diff --git a/musicdb-lib/src/player/rodio.rs b/musicdb-lib/src/player/rodio.rs index b3f20d8..a5b7fe6 100644 --- a/musicdb-lib/src/player/rodio.rs +++ b/musicdb-lib/src/player/rodio.rs @@ -19,12 +19,12 @@ pub struct PlayerBackendRodio { stopped: bool, current: Option<(SongId, Arc>, Option, T)>, next: Option<(SongId, Arc>, Option, T)>, - command_sender: Option>, + command_sender: Option)>>, } impl PlayerBackendRodio { pub fn new( - command_sender: std::sync::mpsc::Sender, + command_sender: std::sync::mpsc::Sender<(Command, Option)>, ) -> Result> { Self::new_with_optional_command_sending(Some(command_sender)) } @@ -32,7 +32,7 @@ impl PlayerBackendRodio { Self::new_with_optional_command_sending(None) } pub fn new_with_optional_command_sending( - command_sender: Option>, + command_sender: Option)>>, ) -> Result> { let (output_stream, output_stream_handle) = rodio::OutputStream::try_default()?; let sink = Sink::try_new(&output_stream_handle)?; @@ -60,13 +60,14 @@ impl PlayerBackend for PlayerBackendRodio { let decoder = decoder_from_bytes(Arc::clone(&bytes)); if let Err(e) = &decoder { if let Some(s) = &self.command_sender { - s.send( + s.send(( Action::ErrorInfo( format!("Couldn't decode song #{id}!"), format!("Error: '{e}'"), ) .cmd(0xFFu8), - ) + None, + )) .unwrap(); } } diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index 859eae3..9c533da 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -43,11 +43,73 @@ impl Action { pub fn cmd(self, seq: u8) -> Command { Command::new(seq, self) } + pub fn take_req(&mut self) -> Option { + self.req_mut() + .map(|r| std::mem::replace(r, Req::none())) + .filter(|r| r.is_some()) + } + pub fn get_req(&mut self) -> Option { + self.req_mut().map(|r| *r).filter(|r| r.is_some()) + } + pub fn put_req(&mut self, req: Req) { + if let Some(r) = self.req_mut() { + *r = req; + } + } + fn req_mut(&mut self) -> Option<&mut Req> { + match self { + Self::QueueUpdate(_, _, req) + | Self::QueueAdd(_, _, req) + | Self::QueueInsert(_, _, _, req) + | Self::AddSong(_, req) + | Self::AddAlbum(_, req) + | Self::AddArtist(_, req) + | Self::AddCover(_, req) + | Self::ModifySong(_, req) + | Self::ModifyAlbum(_, req) + | Self::ModifyArtist(_, req) + | Self::Denied(req) => Some(req), + Self::Resume + | Self::Pause + | Self::Stop + | Self::NextSong + | Self::SyncDatabase(_, _, _) + | Self::QueueRemove(_) + | Self::QueueMove(_, _) + | Self::QueueMoveInto(_, _) + | Self::QueueGoto(_) + | Self::QueueShuffle(_) + | Self::QueueSetShuffle(_, _) + | Self::QueueUnshuffle(_) + | Self::RemoveSong(_) + | Self::RemoveAlbum(_) + | Self::RemoveArtist(_) + | Self::SetSongDuration(_, _) + | Self::TagSongFlagSet(_, _) + | Self::TagSongFlagUnset(_, _) + | Self::TagAlbumFlagSet(_, _) + | Self::TagAlbumFlagUnset(_, _) + | Self::TagArtistFlagSet(_, _) + | Self::TagArtistFlagUnset(_, _) + | Self::TagSongPropertySet(_, _, _) + | Self::TagSongPropertyUnset(_, _) + | Self::TagAlbumPropertySet(_, _, _) + | Self::TagAlbumPropertyUnset(_, _) + | Self::TagArtistPropertySet(_, _, _) + | Self::TagArtistPropertyUnset(_, _) + | Self::InitComplete + | Self::Save + | Self::ErrorInfo(_, _) => None, + } + } } /// Should be stored in the same lock as the database pub struct Commander { seq: u8, } +pub struct Requester { + req: u8, +} impl Commander { pub fn new(ff: bool) -> Self { Self { @@ -72,16 +134,43 @@ impl Commander { self.seq } } -#[derive(Clone, Debug)] +impl Requester { + pub fn new() -> Self { + Self { req: 0 } + } + pub fn inc(&mut self) -> Req { + if self.req < 0xFFu8 { + self.req += 1; + } else { + self.req = 1; + } + Req(self.req) + } +} +/// A request ID, or None +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Req(u8); +impl Req { + pub fn none() -> Self { + Self(0) + } + pub fn is_none(self) -> bool { + self.0 == 0 + } + pub fn is_some(self) -> bool { + self.0 != 0 + } +} +#[derive(Clone, Debug, PartialEq)] pub enum Action { Resume, Pause, Stop, NextSong, SyncDatabase(Vec, Vec, Vec), - QueueUpdate(Vec, Queue), - QueueAdd(Vec, Vec), - QueueInsert(Vec, usize, Vec), + QueueUpdate(Vec, Queue, Req), + QueueAdd(Vec, Vec, Req), + QueueInsert(Vec, usize, Vec, Req), QueueRemove(Vec), /// Move an element from A to B QueueMove(Vec, Vec), @@ -95,18 +184,18 @@ pub enum Action { QueueUnshuffle(Vec), /// .id field is ignored! - AddSong(Song), + AddSong(Song, Req), /// .id field is ignored! - AddAlbum(Album), + AddAlbum(Album, Req), /// .id field is ignored! - AddArtist(Artist), - AddCover(Cover), - ModifySong(Song), - ModifyAlbum(Album), + AddArtist(Artist, Req), + AddCover(Cover, Req), + ModifySong(Song, Req), + ModifyAlbum(Album, Req), + ModifyArtist(Artist, Req), RemoveSong(SongId), RemoveAlbum(AlbumId), RemoveArtist(ArtistId), - ModifyArtist(Artist), SetSongDuration(SongId, u64), /// Add the given Tag to the song's tags, if it isn't set already. TagSongFlagSet(SongId, String), @@ -129,21 +218,25 @@ pub enum Action { InitComplete, Save, ErrorInfo(String, String), + + /// The server denied a request or an action. + /// Contains the Request ID that was rejected, if there was a request ID. + Denied(Req), } impl Command { - pub fn send_to_server(self, db: &Database) -> Result<(), Self> { + pub fn send_to_server(self, db: &Database, client: Option) -> Result<(), Self> { if let Some(sender) = &db.command_sender { - sender.send(self).unwrap(); + sender.send((self, client)).unwrap(); Ok(()) } else { Err(self) } } - pub fn send_to_server_or_apply(self, db: &mut Database) { + pub fn send_to_server_or_apply(self, db: &mut Database, client: Option) { if let Some(sender) = &db.command_sender { - sender.send(self).unwrap(); + sender.send((self, client)).unwrap(); } else { - db.apply_command(self); + db.apply_command(self, client); } } } @@ -164,7 +257,7 @@ impl Command { pub fn run_server( database: Arc>, addr_tcp: Option, - sender_sender: Option)>>, + sender_sender: Option)>)>>, play_audio: bool, ) { run_server_caching_thread_opt(database, addr_tcp, sender_sender, None, play_audio) @@ -172,7 +265,7 @@ pub fn run_server( pub fn run_server_caching_thread_opt( database: Arc>, addr_tcp: Option, - sender_sender: Option)>>, + sender_sender: Option)>)>>, caching_thread: Option>, play_audio: bool, ) { @@ -251,6 +344,7 @@ pub fn run_server_caching_thread_opt( "control" => handle_one_connection_as_control( &mut connection, &command_sender, + None, ), "get" => _ = handle_one_connection_as_get(db, &mut connection), _ => { @@ -312,13 +406,13 @@ pub fn run_server_caching_thread_opt( } } } - if let Ok(command) = command_receiver.recv_timeout(dur) { + if let Ok((command, client)) = command_receiver.recv_timeout(dur) { checkf = true; #[cfg(feature = "playback")] if let Some(player) = &mut player { player.handle_action(&command.action); } - database.lock().unwrap().apply_command(command); + database.lock().unwrap().apply_command(command, client); } } } @@ -327,30 +421,36 @@ pub fn handle_one_connection_as_main( db: Arc>, connection: &mut impl Read, mut send_to: (impl Write + Sync + Send + 'static), - command_sender: &mpsc::Sender, + command_sender: &mpsc::Sender<(Command, Option)>, ) -> Result<(), std::io::Error> { // sync database let mut db = db.lock().unwrap(); db.init_connection(&mut send_to)?; // keep the client in sync: // the db will send all updates to the client once it is added to update_endpoints - db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new( - // try_clone is used here to split a TcpStream into Writer and Reader - send_to, - ))); + let udepid = db.update_endpoints_id; + db.update_endpoints_id += 1; + db.update_endpoints.push(( + udepid, + UpdateEndpoint::Bytes(Box::new( + // try_clone is used here to split a TcpStream into Writer and Reader + send_to, + )), + )); // drop the mutex lock drop(db); - handle_one_connection_as_control(connection, command_sender); + handle_one_connection_as_control(connection, command_sender, Some(udepid)); Ok(()) } pub fn handle_one_connection_as_control( connection: &mut impl Read, - command_sender: &mpsc::Sender, + command_sender: &mpsc::Sender<(Command, Option)>, + client: Option, ) { // read updates from the tcp stream and send them to the database, exit on EOF or Err loop { if let Ok(command) = Command::from_bytes(connection) { - command_sender.send(command).unwrap(); + command_sender.send((command, client)).unwrap(); } else { break; } @@ -375,6 +475,7 @@ const BYTE_INIT_COMPLETE: u8 = 0b01_010_000; const BYTE_SET_SONG_DURATION: u8 = 0b01_010_001; const BYTE_SAVE: u8 = 0b01_010_010; const BYTE_ERRORINFO: u8 = 0b01_100_010; +const BYTE_DENIED: u8 = 0b01_100_011; const BYTE_QUEUE_UPDATE: u8 = 0b10_000_000; const BYTE_QUEUE_ADD: u8 = 0b10_000_001; @@ -430,6 +531,21 @@ impl ToFromBytes for Command { } } +impl ToFromBytes for Req { + fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> + where + T: Write, + { + self.0.to_bytes(s)?; + Ok(()) + } + fn from_bytes(s: &mut T) -> Result + where + T: Read, + { + Ok(Self(ToFromBytes::from_bytes(s)?)) + } +} // impl ToFromBytes for Action { impl Action { fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> @@ -447,21 +563,24 @@ impl Action { b.to_bytes(s)?; c.to_bytes(s)?; } - Self::QueueUpdate(index, new_data) => { + Self::QueueUpdate(index, new_data, req) => { s.write_all(&[BYTE_QUEUE_UPDATE])?; index.to_bytes(s)?; new_data.to_bytes(s)?; + req.to_bytes(s)?; } - Self::QueueAdd(index, new_data) => { + Self::QueueAdd(index, new_data, req) => { s.write_all(&[BYTE_QUEUE_ADD])?; index.to_bytes(s)?; new_data.to_bytes(s)?; + req.to_bytes(s)?; } - Self::QueueInsert(index, pos, new_data) => { + Self::QueueInsert(index, pos, new_data, req) => { s.write_all(&[BYTE_QUEUE_INSERT])?; index.to_bytes(s)?; pos.to_bytes(s)?; new_data.to_bytes(s)?; + req.to_bytes(s)?; } Self::QueueRemove(index) => { s.write_all(&[BYTE_QUEUE_REMOVE])?; @@ -497,40 +616,47 @@ impl Action { s.write_all(&[SUBBYTE_ACTION_UNSHUFFLE])?; path.to_bytes(s)?; } - Self::AddSong(song) => { + Self::AddSong(song, req) => { s.write_all(&[BYTE_LIB_ADD])?; s.write_all(&[SUBBYTE_SONG])?; song.to_bytes(s)?; + req.to_bytes(s)?; } - Self::AddAlbum(album) => { + Self::AddAlbum(album, req) => { s.write_all(&[BYTE_LIB_ADD])?; s.write_all(&[SUBBYTE_ALBUM])?; album.to_bytes(s)?; + req.to_bytes(s)?; } - Self::AddArtist(artist) => { + Self::AddArtist(artist, req) => { s.write_all(&[BYTE_LIB_ADD])?; s.write_all(&[SUBBYTE_ARTIST])?; artist.to_bytes(s)?; + req.to_bytes(s)?; } - Self::AddCover(cover) => { + Self::AddCover(cover, req) => { s.write_all(&[BYTE_LIB_ADD])?; s.write_all(&[SUBBYTE_COVER])?; cover.to_bytes(s)?; + req.to_bytes(s)?; } - Self::ModifySong(song) => { + Self::ModifySong(song, req) => { s.write_all(&[BYTE_LIB_MODIFY])?; s.write_all(&[SUBBYTE_SONG])?; song.to_bytes(s)?; + req.to_bytes(s)?; } - Self::ModifyAlbum(album) => { + Self::ModifyAlbum(album, req) => { s.write_all(&[BYTE_LIB_MODIFY])?; s.write_all(&[SUBBYTE_ALBUM])?; album.to_bytes(s)?; + req.to_bytes(s)?; } - Self::ModifyArtist(artist) => { + Self::ModifyArtist(artist, req) => { s.write_all(&[BYTE_LIB_MODIFY])?; s.write_all(&[SUBBYTE_ARTIST])?; artist.to_bytes(s)?; + req.to_bytes(s)?; } Self::RemoveSong(song) => { s.write_all(&[BYTE_LIB_REMOVE])?; @@ -636,6 +762,10 @@ impl Action { t.to_bytes(s)?; d.to_bytes(s)?; } + Self::Denied(req) => { + s.write_all(&[BYTE_DENIED])?; + req.to_bytes(s)?; + } } Ok(()) } @@ -654,9 +784,11 @@ impl Action { BYTE_STOP => Self::Stop, BYTE_NEXT_SONG => Self::NextSong, BYTE_SYNC_DATABASE => Self::SyncDatabase(from_bytes!(), from_bytes!(), from_bytes!()), - BYTE_QUEUE_UPDATE => Self::QueueUpdate(from_bytes!(), from_bytes!()), - BYTE_QUEUE_ADD => Self::QueueAdd(from_bytes!(), from_bytes!()), - BYTE_QUEUE_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()), + BYTE_QUEUE_UPDATE => Self::QueueUpdate(from_bytes!(), from_bytes!(), from_bytes!()), + BYTE_QUEUE_ADD => Self::QueueAdd(from_bytes!(), from_bytes!(), from_bytes!()), + BYTE_QUEUE_INSERT => { + Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!(), from_bytes!()) + } BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()), BYTE_QUEUE_MOVE => Self::QueueMove(from_bytes!(), from_bytes!()), BYTE_QUEUE_MOVE_INTO => Self::QueueMoveInto(from_bytes!(), from_bytes!()), @@ -674,10 +806,10 @@ impl Action { } }, BYTE_LIB_ADD => match s.read_byte()? { - SUBBYTE_SONG => Self::AddSong(from_bytes!()), - SUBBYTE_ALBUM => Self::AddAlbum(from_bytes!()), - SUBBYTE_ARTIST => Self::AddArtist(from_bytes!()), - SUBBYTE_COVER => Self::AddCover(from_bytes!()), + SUBBYTE_SONG => Self::AddSong(from_bytes!(), from_bytes!()), + SUBBYTE_ALBUM => Self::AddAlbum(from_bytes!(), from_bytes!()), + SUBBYTE_ARTIST => Self::AddArtist(from_bytes!(), from_bytes!()), + SUBBYTE_COVER => Self::AddCover(from_bytes!(), from_bytes!()), _ => { eprintln!( "[{}] unexpected byte when reading command:libAdd; stopping playback.", @@ -687,9 +819,9 @@ impl Action { } }, BYTE_LIB_MODIFY => match s.read_byte()? { - SUBBYTE_SONG => Self::ModifySong(from_bytes!()), - SUBBYTE_ALBUM => Self::ModifyAlbum(from_bytes!()), - SUBBYTE_ARTIST => Self::ModifyArtist(from_bytes!()), + SUBBYTE_SONG => Self::ModifySong(from_bytes!(), from_bytes!()), + SUBBYTE_ALBUM => Self::ModifyAlbum(from_bytes!(), from_bytes!()), + SUBBYTE_ARTIST => Self::ModifyArtist(from_bytes!(), from_bytes!()), _ => { eprintln!( "[{}] unexpected byte when reading command:libModify; stopping playback.", @@ -751,6 +883,7 @@ impl Action { BYTE_INIT_COMPLETE => Self::InitComplete, BYTE_SAVE => Self::Save, BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()), + BYTE_DENIED => Self::Denied(from_bytes!()), _ => { eprintln!( "[{}] unexpected byte when reading command; stopping playback.", @@ -772,3 +905,61 @@ impl ReadByte for T { Ok(b[0]) } } + +#[test] +fn test_to_from_bytes() { + use crate::data::queue::QueueContent; + use std::io::Cursor; + for v in [ + Action::Resume, + Action::Pause, + Action::Stop, + Action::NextSong, + Action::SyncDatabase(vec![], vec![], vec![]), + Action::QueueUpdate(vec![], QueueContent::Song(12).into(), Req::none()), + Action::QueueAdd(vec![], vec![], Req::none()), + Action::QueueInsert(vec![], 5, vec![], Req::none()), + Action::QueueRemove(vec![]), + Action::QueueMove(vec![], vec![]), + Action::QueueMoveInto(vec![], vec![]), + Action::QueueGoto(vec![]), + Action::QueueShuffle(vec![]), + Action::QueueSetShuffle(vec![], vec![]), + Action::QueueUnshuffle(vec![]), + // Action::AddSong(Song, Req), + // Action::AddAlbum(Album, Req), + // Action::AddArtist(Artist, Req), + // Action::AddCover(Cover, Req), + // Action::ModifySong(Song, Req), + // Action::ModifyAlbum(Album, Req), + // Action::ModifyArtist(Artist, Req), + // Action::RemoveSong(SongId), + // Action::RemoveAlbum(AlbumId), + // Action::RemoveArtist(ArtistId), + // Action::SetSongDuration(SongId, u64), + // Action::TagSongFlagSet(SongId, String), + // Action::TagSongFlagUnset(SongId, String), + // Action::TagAlbumFlagSet(AlbumId, String), + // Action::TagAlbumFlagUnset(AlbumId, String), + // Action::TagArtistFlagSet(ArtistId, String), + // Action::TagArtistFlagUnset(ArtistId, String), + // Action::TagSongPropertySet(SongId, String, String), + // Action::TagSongPropertyUnset(SongId, String), + // Action::TagAlbumPropertySet(AlbumId, String, String), + // Action::TagAlbumPropertyUnset(AlbumId, String), + // Action::TagArtistPropertySet(ArtistId, String, String), + // Action::TagArtistPropertyUnset(ArtistId, String), + Action::InitComplete, + Action::Save, + Action::ErrorInfo(format!("some error"), format!("with a message")), + Action::Denied(Req::none()), + ] { + let v = v.cmd(0xFF); + assert_eq!( + v.action, + Command::from_bytes(&mut Cursor::new(v.to_bytes_vec())) + .unwrap() + .action + ); + } +} diff --git a/musicdb-server/Cross.toml b/musicdb-server/Cross.toml index 4804295..509f0dc 100644 --- a/musicdb-server/Cross.toml +++ b/musicdb-server/Cross.toml @@ -1,12 +1,12 @@ -# compile for aarch64 linux +# # compile for aarch64 linux [build] pre-build = [ "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get --assume-yes install libasound2-dev libasound2-dev:$CROSS_DEB_ARCH" + "apt-get update && apt-get --assume-yes install --force musl-dev libasound2-dev" ] -default-target = "aarch64-unknown-linux-gnu" +default-target = "x86_64-unknown-linux-musl" -# # compile for aarch64 android +# compile for aarch64 android # [build] # pre-build = [ # "dpkg --add-architecture $CROSS_DEB_ARCH", diff --git a/musicdb-server/src/main.rs b/musicdb-server/src/main.rs index 1e4489d..b0e84d4 100755 --- a/musicdb-server/src/main.rs +++ b/musicdb-server/src/main.rs @@ -151,11 +151,11 @@ fn main() { let cmd = musicdb_lib::server::Command::from_bytes(&mut con).unwrap(); use musicdb_lib::server::Action::*; match &cmd.action { - // ignore playback and queue commands + // ignore playback and queue commands, and denials Resume | Pause | Stop | NextSong | QueueUpdate(..) | QueueAdd(..) | QueueInsert(..) | QueueRemove(..) | QueueMove(..) | QueueMoveInto(..) | QueueGoto(..) | QueueShuffle(..) | QueueSetShuffle(..) - | QueueUnshuffle(..) => continue, + | QueueUnshuffle(..) | Denied(..) => continue, SyncDatabase(..) | AddSong(..) | AddAlbum(..) @@ -184,7 +184,7 @@ fn main() { | Save | ErrorInfo(..) => (), } - database.lock().unwrap().apply_command(cmd); + database.lock().unwrap().apply_command(cmd, None); } }); } diff --git a/musicdb-server/src/web.rs b/musicdb-server/src/web.rs index 9ddeac9..a2b2bcd 100755 --- a/musicdb-server/src/web.rs +++ b/musicdb-server/src/web.rs @@ -7,7 +7,7 @@ use musicdb_lib::data::database::Database; use musicdb_lib::data::queue::{Queue, QueueContent, QueueFolder}; use musicdb_lib::data::song::Song; use musicdb_lib::data::SongId; -use musicdb_lib::server::{Action, Command}; +use musicdb_lib::server::{Action, Command, Req}; use rocket::response::content::RawHtml; use rocket::{get, routes, Config, State}; @@ -40,7 +40,7 @@ const HTML_END: &'static str = ""; struct Data { db: Arc>, - command_sender: mpsc::Sender, + command_sender: mpsc::Sender<(Command, Option)>, } #[get("/")] @@ -258,7 +258,7 @@ fn gen_queue_html_impl( fn queue_remove(data: &State, path: &str) { if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() { data.command_sender - .send(Action::QueueRemove(path).cmd(0xFFu8)) + .send((Action::QueueRemove(path).cmd(0xFFu8), None)) .unwrap(); } } @@ -266,7 +266,7 @@ fn queue_remove(data: &State, path: &str) { fn queue_goto(data: &State, path: &str) { if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() { data.command_sender - .send(Action::QueueGoto(path).cmd(0xFFu8)) + .send((Action::QueueGoto(path).cmd(0xFFu8), None)) .unwrap(); } } @@ -274,27 +274,31 @@ fn queue_goto(data: &State, path: &str) { #[get("/play")] fn play(data: &State) { data.command_sender - .send(Action::Resume.cmd(0xFFu8)) + .send((Action::Resume.cmd(0xFFu8), None)) .unwrap(); } #[get("/pause")] fn pause(data: &State) { - data.command_sender.send(Action::Pause.cmd(0xFFu8)).unwrap(); + data.command_sender + .send((Action::Pause.cmd(0xFFu8), None)) + .unwrap(); } #[get("/stop")] fn stop(data: &State) { - data.command_sender.send(Action::Stop.cmd(0xFFu8)).unwrap(); + data.command_sender + .send((Action::Stop.cmd(0xFFu8), None)) + .unwrap(); } #[get("/skip")] fn skip(data: &State) { data.command_sender - .send(Action::NextSong.cmd(0xFFu8)) + .send((Action::NextSong.cmd(0xFFu8), None)) .unwrap(); } #[get("/clear-queue")] fn clear_queue(data: &State) { data.command_sender - .send( + .send(( Action::QueueUpdate( vec![], QueueContent::Folder(QueueFolder { @@ -304,16 +308,21 @@ fn clear_queue(data: &State) { order: None, }) .into(), + Req::none(), ) .cmd(0xFFu8), - ) + None, + )) .unwrap(); } #[get("/add-song/")] fn add_song(data: &State, id: SongId) { data.command_sender - .send(Action::QueueAdd(vec![], vec![QueueContent::Song(id).into()]).cmd(0xFFu8)) + .send(( + Action::QueueAdd(vec![], vec![QueueContent::Song(id).into()], Req::none()).cmd(0xFFu8), + None, + )) .unwrap(); } @@ -538,7 +547,7 @@ fn search( pub async fn main( db: Arc>, - command_sender: mpsc::Sender, + command_sender: mpsc::Sender<(Command, Option)>, addr: SocketAddr, ) { rocket::build()