From 0fe2648efef394314aabbe214e0013e8c46876f7 Mon Sep 17 00:00:00 2001 From: Mark <> Date: Sat, 30 Mar 2024 22:13:59 +0100 Subject: [PATCH] include shuffle functionality in folders, remove shuffle and random elements, change ToFromBytes impls --- musicdb-client/Cargo.toml | 2 +- musicdb-client/src/gui.rs | 4 +- musicdb-client/src/gui_library.rs | 24 +- musicdb-client/src/gui_queue.rs | 489 ++++---------- musicdb-client/src/gui_screen.rs | 11 +- musicdb-lib/src/data/cache_manager.rs | 16 +- musicdb-lib/src/data/database.rs | 112 ++-- musicdb-lib/src/data/queue.rs | 633 +++++++----------- musicdb-lib/src/load/mod.rs | 11 +- musicdb-lib/src/server/mod.rs | 303 ++++++--- musicdb-mers/src/lib.rs | 85 +-- musicdb-server/assets/queue_folder.html | 1 + .../assets/queue_folder_current.html | 1 + musicdb-server/src/web.rs | 299 +++++---- 14 files changed, 855 insertions(+), 1136 deletions(-) diff --git a/musicdb-client/Cargo.toml b/musicdb-client/Cargo.toml index 0b251fe..d028a70 100755 --- a/musicdb-client/Cargo.toml +++ b/musicdb-client/Cargo.toml @@ -24,7 +24,7 @@ default = ["gui", "mers", "merscfg"] # mers: # enables the run-mers mode # playback: -# enables Symcplayer modes, where the client mirrors the server's playback +# enables syncplayer modes, where the client mirrors the server's playback gui = ["speedy2d"] merscfg = ["mers_lib", "musicdb-mers", "speedy2d"] mers = ["mers_lib", "musicdb-mers"] diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index 7823c54..a6fb5b7 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -366,7 +366,9 @@ impl Gui { | Command::QueueInsert(..) | Command::QueueRemove(..) | Command::QueueGoto(..) - | Command::QueueSetShuffle(..) => { + | Command::QueueShuffle(..) + | Command::QueueSetShuffle(..) + | Command::QueueUnshuffle(..) => { if let Some(s) = &*event_sender_arc.lock().unwrap() { _ = s.send_event(GuiEvent::UpdatedQueue); } diff --git a/musicdb-client/src/gui_library.rs b/musicdb-client/src/gui_library.rs index 7498289..3f02770 100755 --- a/musicdb-client/src/gui_library.rs +++ b/musicdb-client/src/gui_library.rs @@ -2082,6 +2082,8 @@ impl FilterType { } mod selected { + use musicdb_lib::data::queue::QueueFolder; + use super::*; #[derive(Clone)] pub struct Selected( @@ -2187,28 +2189,30 @@ mod selected { } if album_selected { local_artist.push( - QueueContent::Folder( - 0, - local_album_owned, - match db.albums().get(album) { + QueueContent::Folder(QueueFolder { + index: 0, + content: local_album_owned, + name: match db.albums().get(album) { Some(v) => v.name.clone(), None => "< unknown album >".to_owned(), }, - ) + order: None, + }) .into(), ); } } if artist_selected { out.push( - QueueContent::Folder( - 0, - local_artist_owned, - match db.artists().get(artist) { + QueueContent::Folder(QueueFolder { + index: 0, + content: local_artist_owned, + name: match db.artists().get(artist) { Some(v) => v.name.to_owned(), None => "< unknown artist >".to_owned(), }, - ) + order: None, + }) .into(), ); } diff --git a/musicdb-client/src/gui_queue.rs b/musicdb-client/src/gui_queue.rs index d59380d..b4f7aca 100755 --- a/musicdb-client/src/gui_queue.rs +++ b/musicdb-client/src/gui_queue.rs @@ -1,9 +1,7 @@ -use std::collections::VecDeque; - use musicdb_lib::{ data::{ database::Database, - queue::{Queue, QueueContent, QueueDuration, ShuffleState}, + queue::{Queue, QueueContent, QueueDuration}, song::Song, AlbumId, ArtistId, }, @@ -18,8 +16,8 @@ use speedy2d::{ use crate::{ gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg}, - gui_base::{Panel, ScrollBox}, - gui_text::{self, AdvancedLabel, Label}, + gui_base::{Button, Panel, ScrollBox, ScrollBoxSizeUnit}, + gui_text::{self, AdvancedLabel, Label, TextField}, }; /* @@ -35,16 +33,21 @@ pub struct QueueViewer { config: GuiElemCfg, c_scroll_box: ScrollBox>>, c_empty_space_drag_handler: QueueEmptySpaceDragHandler, - c_control_flow_elements: Panel<(QueueLoop, QueueLoop, QueueRandom, QueueShuffle)>, + c_control_flow_elements: Panel<(QueueLoop, QueueLoop, QueueFolder, TextField)>, c_duration: AdvancedLabel, + recv: std::sync::mpsc::Receiver, queue_updated: bool, } +pub enum QVMsg { + ControlFlowElementsSetFolderName(String), +} const QP_QUEUE1: f32 = 0.0; const QP_QUEUE2: f32 = 0.95; const QP_INV1: f32 = QP_QUEUE2; const QP_INV2: f32 = 1.0; impl QueueViewer { pub fn new(config: GuiElemCfg) -> Self { + let (sender, recv) = std::sync::mpsc::channel(); let control_flow_elements = ( QueueLoop::new( GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5))).w_mouse(), @@ -52,7 +55,15 @@ impl QueueViewer { QueueContent::Loop( 0, 0, - Box::new(QueueContent::Folder(0, vec![], "in loop".to_string()).into()), + Box::new( + QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { + index: 0, + content: vec![], + name: "in loop".to_string(), + order: None, + }) + .into(), + ), ) .into(), false, @@ -64,30 +75,46 @@ impl QueueViewer { QueueContent::Loop( 2, 0, - Box::new(QueueContent::Folder(0, vec![], "in loop".to_string()).into()), + Box::new( + QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { + index: 0, + content: vec![], + name: "in loop".to_string(), + order: None, + }) + .into(), + ), ) .into(), false, ) .alwayscopy(), - QueueRandom::new( + QueueFolder::new( GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5))).w_mouse(), vec![], - QueueContent::Random(VecDeque::new()).into(), - false, - ) - .alwayscopy(), - QueueShuffle::new( - GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))).w_mouse(), - vec![], - QueueContent::Shuffle { - inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()), - state: ShuffleState::NotShuffled, - } - .into(), + musicdb_lib::data::queue::QueueFolder { + index: 0, + content: vec![], + name: format!("folder name"), + order: None, + }, false, ) .alwayscopy(), + { + let mut tf = TextField::new( + GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))), + format!("folder name"), + Color::from_rgb(0.0, 0.33, 0.0), + Color::from_rgb(0.0, 0.67, 0.0), + ); + tf.on_changed = Some(Box::new(move |folder_name| { + _ = sender.send(QVMsg::ControlFlowElementsSetFolderName( + folder_name.to_owned(), + )); + })); + tf + }, ); Self { config, @@ -111,6 +138,7 @@ impl QueueViewer { vec![], ), queue_updated: false, + recv, } } } @@ -145,6 +173,20 @@ impl GuiElem for QueueViewer { self } fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { + while let Ok(msg) = self.recv.try_recv() { + match msg { + QVMsg::ControlFlowElementsSetFolderName(name) => { + *self + .c_control_flow_elements + .children + .2 + .c_name + .content + .text() = name.clone(); + self.c_control_flow_elements.children.2.queue.name = name; + } + } + } if self.queue_updated { self.queue_updated = false; let label = &mut self.c_duration; @@ -254,17 +296,23 @@ fn queue_gui( target_h.push(line_height * 1.75); } } - QueueContent::Folder(ia, q, _) => { + QueueContent::Folder(qf) => { + let musicdb_lib::data::queue::QueueFolder { + index: ia, + content: _, + name: _, + order: _, + } = qf; if !skip_folder { target.push(Box::new(QueueFolder::new( cfg.clone(), path.clone(), - queue.clone(), + qf.clone(), current, ))); target_h.push(line_height * 0.8); } - for (i, q) in q.iter().enumerate() { + for (i, q) in qf.iter().enumerate() { let mut p = path.clone(); p.push(i); queue_gui( @@ -314,62 +362,6 @@ fn queue_gui( target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); target_h.push(line_height * 0.4); } - QueueContent::Random(q) => { - target.push(Box::new(QueueRandom::new( - cfg.clone(), - path.clone(), - queue.clone(), - current, - ))); - target_h.push(line_height); - for (i, inner) in q.iter().enumerate() { - let mut p = path.clone(); - p.push(i); - queue_gui( - inner, - db, - depth + depth_inc_by, - depth_inc_by, - line_height, - target, - target_h, - p, - current && i == q.len().saturating_sub(2), - false, - ); - } - let mut p1 = path.clone(); - let p2 = p1.pop().unwrap_or(0) + 1; - target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); - target_h.push(line_height * 0.4); - } - QueueContent::Shuffle { inner, state: _ } => { - target.push(Box::new(QueueShuffle::new( - cfg.clone(), - path.clone(), - queue.clone(), - current, - ))); - target_h.push(line_height * 0.8); - let mut p = path.clone(); - p.push(0); - queue_gui( - inner, - db, - depth + depth_inc_by, - depth_inc_by, - line_height, - target, - target_h, - p, - current, - true, - ); - let mut p1 = path.clone(); - let p2 = p1.pop().unwrap_or(0) + 1; - target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); - target_h.push(line_height * 0.4); - } } } @@ -638,9 +630,9 @@ impl GuiElem for QueueSong { struct QueueFolder { config: GuiElemCfg, - children: Vec>, + c_name: Label, path: Vec, - queue: Queue, + queue: musicdb_lib::data::queue::QueueFolder, current: bool, insert_into: bool, mouse: bool, @@ -650,7 +642,18 @@ struct QueueFolder { copy_on_mouse_down: bool, } impl QueueFolder { - pub fn new(config: GuiElemCfg, path: Vec, queue: Queue, current: bool) -> Self { + pub fn new( + config: GuiElemCfg, + path: Vec, + queue: musicdb_lib::data::queue::QueueFolder, + current: bool, + ) -> Self { + let musicdb_lib::data::queue::QueueFolder { + index: _, + content, + name, + order, + } = &queue; Self { config: if path.is_empty() { config @@ -658,24 +661,22 @@ impl QueueFolder { config.w_mouse().w_keyboard_watch() } .w_drag_target(), - children: vec![Box::new(Label::new( + c_name: Label::new( GuiElemCfg::default(), - match queue.content() { - QueueContent::Folder(_, q, n) => format!( - "{} ({})", - if path.is_empty() && n.is_empty() { - "Queue" - } else { - n - }, - q.len() - ), - _ => "[???]".to_string(), - }, + format!( + "{} ({}){}", + if path.is_empty() && name.is_empty() { + "Queue" + } else { + name + }, + content.len(), + if order.is_some() { " [shuffled]" } else { "" }, + ), Color::from_int_rgb(52, 132, 50), None, Vec2::new(0.0, 0.5), - ))], + ), path, queue, current, @@ -687,6 +688,12 @@ impl QueueFolder { copy_on_mouse_down: false, } } + fn alwayscopy(mut self) -> Self { + self.always_copy = true; + self.copy = true; + self.config.scroll_events = true; + self + } } impl GuiElem for QueueFolder { fn config(&self) -> &GuiElemCfg { @@ -696,7 +703,7 @@ impl GuiElem for QueueFolder { &mut self.config } fn children(&mut self) -> Box + '_> { - Box::new(self.children.iter_mut().map(|v| v.elem_mut())) + Box::new([self.c_name.elem_mut()].into_iter()) } fn any(&self) -> &dyn std::any::Any { self @@ -742,7 +749,7 @@ impl GuiElem for QueueFolder { } if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { info.actions.push(GuiAction::SetDragging(Some(( - Dragging::Queue(self.queue.clone()), + Dragging::Queue(QueueContent::Folder(self.queue.clone()).into()), None, )))); } @@ -751,6 +758,15 @@ impl GuiElem for QueueFolder { if button == MouseButton::Left { self.mouse = true; self.copy_on_mouse_down = self.copy; + } else if button == MouseButton::Right { + // return vec![GuiAction::ContextMenu(Some(vec![Box::new( + // Panel::with_background(GuiElemCfg::default(), (), Color::DARK_GRAY), + // )]))]; + return vec![GuiAction::SendToServer(if self.queue.order.is_some() { + Command::QueueUnshuffle(self.path.clone()) + } else { + Command::QueueShuffle(self.path.clone()) + })]; } vec![] } @@ -1007,259 +1023,6 @@ impl GuiElem for QueueLoop { } } -struct QueueRandom { - config: GuiElemCfg, - children: Vec>, - path: Vec, - queue: Queue, - current: bool, - mouse: bool, - mouse_pos: Vec2, - copy: bool, - always_copy: bool, - copy_on_mouse_down: bool, -} -impl QueueRandom { - pub fn new(config: GuiElemCfg, path: Vec, queue: Queue, current: bool) -> Self { - Self { - config: if path.is_empty() { - config - } else { - config.w_mouse().w_keyboard_watch() - } - .w_drag_target(), - children: vec![Box::new(Label::new( - GuiElemCfg::default(), - match queue.content() { - QueueContent::Random(_) => { - format!("random") - } - _ => "[???]".to_string(), - }, - Color::from_int_rgb(32, 27, 179), - None, - Vec2::new(0.0, 0.5), - ))], - path, - queue, - current, - mouse: false, - mouse_pos: Vec2::ZERO, - copy: false, - always_copy: false, - copy_on_mouse_down: false, - } - } - fn alwayscopy(mut self) -> Self { - self.always_copy = true; - self.copy = true; - self - } -} -impl GuiElem for QueueRandom { - fn config(&self) -> &GuiElemCfg { - &self.config - } - fn config_mut(&mut self) -> &mut GuiElemCfg { - &mut self.config - } - fn children(&mut self) -> Box + '_> { - Box::new(self.children.iter_mut().map(|v| v.elem_mut())) - } - fn any(&self) -> &dyn std::any::Any { - self - } - fn any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - fn elem(&self) -> &dyn GuiElem { - self - } - fn elem_mut(&mut self) -> &mut dyn GuiElem { - self - } - fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { - if !self.mouse { - self.mouse_pos = Vec2::new( - info.mouse_pos.x - self.config.pixel_pos.top_left().x, - info.mouse_pos.y - self.config.pixel_pos.top_left().y, - ); - } - if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { - info.actions.push(GuiAction::SetDragging(Some(( - Dragging::Queue(self.queue.clone()), - None, - )))); - } - } - fn mouse_down(&mut self, button: MouseButton) -> Vec { - if button == MouseButton::Left { - self.mouse = true; - self.copy_on_mouse_down = self.copy; - } - vec![] - } - fn mouse_up(&mut self, button: MouseButton) -> Vec { - if self.mouse && button == MouseButton::Left { - self.mouse = false; - if !self.always_copy { - vec![GuiAction::SendToServer(Command::QueueGoto( - self.path.clone(), - ))] - } else { - vec![] - } - } else { - vec![] - } - } - fn key_watch( - &mut self, - modifiers: ModifiersState, - _down: bool, - _key: Option, - _scan: speedy2d::window::KeyScancode, - ) -> Vec { - self.copy = modifiers.ctrl(); - vec![] - } - fn dragged(&mut self, dragged: Dragging) -> Vec { - if !self.always_copy { - let p = self.path.clone(); - dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p.clone(), q)) - } else { - vec![] - } - } -} - -struct QueueShuffle { - config: GuiElemCfg, - children: Vec>, - path: Vec, - queue: Queue, - current: bool, - mouse: bool, - mouse_pos: Vec2, - copy: bool, - always_copy: bool, - copy_on_mouse_down: bool, -} -impl QueueShuffle { - pub fn new(config: GuiElemCfg, path: Vec, queue: Queue, current: bool) -> Self { - Self { - config: if path.is_empty() { - config - } else { - config.w_mouse().w_keyboard_watch() - } - .w_drag_target(), - children: vec![Box::new(Label::new( - GuiElemCfg::default(), - match queue.content() { - QueueContent::Shuffle { .. } => { - format!("shuffle") - } - _ => "[???]".to_string(), - }, - Color::from_int_rgb(92, 52, 194), - None, - Vec2::new(0.0, 0.5), - ))], - path, - queue, - current, - mouse: false, - mouse_pos: Vec2::ZERO, - copy: false, - always_copy: false, - copy_on_mouse_down: false, - } - } - fn alwayscopy(mut self) -> Self { - self.always_copy = true; - self.copy = true; - self - } -} -impl GuiElem for QueueShuffle { - fn config(&self) -> &GuiElemCfg { - &self.config - } - fn config_mut(&mut self) -> &mut GuiElemCfg { - &mut self.config - } - fn children(&mut self) -> Box + '_> { - Box::new(self.children.iter_mut().map(|v| v.elem_mut())) - } - fn any(&self) -> &dyn std::any::Any { - self - } - fn any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - fn elem(&self) -> &dyn GuiElem { - self - } - fn elem_mut(&mut self) -> &mut dyn GuiElem { - self - } - fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { - if !self.mouse { - self.mouse_pos = Vec2::new( - info.mouse_pos.x - self.config.pixel_pos.top_left().x, - info.mouse_pos.y - self.config.pixel_pos.top_left().y, - ); - } - if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { - info.actions.push(GuiAction::SetDragging(Some(( - Dragging::Queue(self.queue.clone()), - None, - )))); - } - } - fn mouse_down(&mut self, button: MouseButton) -> Vec { - if button == MouseButton::Left { - self.mouse = true; - self.copy_on_mouse_down = self.copy; - } - vec![] - } - fn mouse_up(&mut self, button: MouseButton) -> Vec { - if self.mouse && button == MouseButton::Left { - self.mouse = false; - if !self.always_copy { - vec![GuiAction::SendToServer(Command::QueueGoto( - self.path.clone(), - ))] - } else { - vec![] - } - } else { - vec![] - } - } - fn key_watch( - &mut self, - modifiers: ModifiersState, - _down: bool, - _key: Option, - _scan: speedy2d::window::KeyScancode, - ) -> Vec { - self.copy = modifiers.ctrl(); - vec![] - } - fn dragged(&mut self, dragged: Dragging) -> Vec { - if !self.always_copy { - let mut p = self.path.clone(); - p.push(0); - dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p.clone(), q)) - } else { - vec![] - } - } -} - fn dragged_add_to_queue) -> Command + 'static>( dragged: Dragging, f: F, @@ -1297,15 +1060,16 @@ fn dragged_add_to_queue) -> Command + 'static>( fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option { if let Some(album) = db.albums().get(&id) { Some( - QueueContent::Folder( - 0, - album + QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { + index: 0, + content: album .songs .iter() .map(|id| QueueContent::Song(*id).into()) .collect(), - album.name.clone(), - ) + name: album.name.clone(), + order: None, + }) .into(), ) } else { @@ -1315,9 +1079,9 @@ fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option { fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option { if let Some(artist) = db.artists().get(&id) { Some( - QueueContent::Folder( - 0, - artist + QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { + index: 0, + content: artist .singles .iter() .map(|id| QueueContent::Song(*id).into()) @@ -1328,8 +1092,9 @@ fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option { .filter_map(|id| add_to_queue_album_by_id(*id, db)), ) .collect(), - artist.name.clone(), - ) + name: artist.name.clone(), + order: None, + }) .into(), ) } else { diff --git a/musicdb-client/src/gui_screen.rs b/musicdb-client/src/gui_screen.rs index 63b53b2..c4c9795 100755 --- a/musicdb-client/src/gui_screen.rs +++ b/musicdb-client/src/gui_screen.rs @@ -1,6 +1,9 @@ use std::time::Instant; -use musicdb_lib::{data::queue::QueueContent, server::Command}; +use musicdb_lib::{ + data::queue::{QueueContent, QueueFolder}, + server::Command, +}; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D}; use crate::{ @@ -117,9 +120,7 @@ impl GuiScreen { musicdb_lib::server::Command::QueueUpdate( vec![], musicdb_lib::data::queue::QueueContent::Folder( - 0, - vec![], - String::new(), + musicdb_lib::data::queue::QueueFolder::default(), ) .into(), ), @@ -337,7 +338,7 @@ impl GuiElem for GuiScreen { self.not_idle(); } if !(!info.database.playing - || matches!(info.database.queue.content(), QueueContent::Folder(_, v, _) if v.is_empty())) + || matches!(info.database.queue.content(), QueueContent::Folder(QueueFolder { content: v, .. }) if v.is_empty())) { // skip idle_check if paused or queue is empty self.idle_check(); diff --git a/musicdb-lib/src/data/cache_manager.rs b/musicdb-lib/src/data/cache_manager.rs index 4039c26..96a57bf 100644 --- a/musicdb-lib/src/data/cache_manager.rs +++ b/musicdb-lib/src/data/cache_manager.rs @@ -19,6 +19,7 @@ pub struct CacheManager { /// Amount of bytes. If free system memory is greater than this number, consider caching more songs. pub max_avail_mem: Arc, pub songs_to_cache: Arc, + #[allow(unused)] thread: Arc>, } @@ -82,15 +83,9 @@ impl CacheManager { } else { let mut queue = db.queue.clone(); - let mut actions = vec![]; - let queue_current_song = queue.get_current_song().copied(); - queue.advance_index_inner(vec![], &mut actions); - let queue_next_song = if actions.is_empty() { - queue.get_current_song().copied() - } else { - None - }; + queue.advance_index_inner(); + let queue_next_song = queue.get_current_song().copied(); let mut ids_to_cache = queue_current_song .into_iter() @@ -98,10 +93,7 @@ impl CacheManager { .collect::>(); for _ in 2..songs_to_cache { - queue.advance_index_inner(vec![], &mut actions); - if !actions.is_empty() { - break; - } + queue.advance_index_inner(); if let Some(id) = queue.get_current_song() { if !ids_to_cache.contains(id) { ids_to_cache.push(*id); diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index 2afc7f9..2291d59 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -1,3 +1,4 @@ +use rand::prelude::SliceRandom; use std::{ collections::{BTreeSet, HashMap}, fs::{self, File}, @@ -8,13 +9,14 @@ use std::{ }; use colorize::AnsiColor; +use rand::thread_rng; use crate::{load::ToFromBytes, server::Command}; use super::{ album::Album, artist::Artist, - queue::{Queue, QueueContent, ShuffleState}, + queue::{Queue, QueueContent, QueueFolder}, song::Song, AlbumId, ArtistId, CoverId, DatabaseLocation, SongId, }; @@ -515,8 +517,13 @@ impl Database { t.clear(); } } - // since db.update_endpoints is empty for clients, this won't cause unwanted back and forth - self.broadcast_update(&command); + // some commands shouldn't be broadcast. these will broadcast a different command in their specific implementation. + match &command { + // Will broadcast `QueueSetShuffle` + Command::QueueShuffle(_) => (), + // since db.update_endpoints is empty for clients, this won't cause unwanted back and forth + _ => self.broadcast_update(&command), + } match command { Command::Resume => self.playing = true, Command::Pause => self.playing = false, @@ -525,9 +532,7 @@ impl Database { if !Queue::advance_index_db(self) { // end of queue self.apply_command(Command::Pause); - let mut actions = Vec::new(); - self.queue.init(vec![], &mut actions); - Queue::handle_actions(self, actions); + self.queue.init(); } } Command::Save => { @@ -537,51 +542,67 @@ impl Database { } Command::SyncDatabase(a, b, c) => self.sync(a, b, c), Command::QueueUpdate(index, new_data) => { - let mut actions = vec![]; - if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { + if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { *v = new_data; } - Queue::handle_actions(self, actions); } Command::QueueAdd(index, new_data) => { - let mut actions = vec![]; - if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { - v.add_to_end(new_data, index, &mut actions); + if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { + v.add_to_end(new_data); } - Queue::handle_actions(self, actions); } Command::QueueInsert(index, pos, new_data) => { - let mut actions = vec![]; - if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { - v.insert(new_data, pos, index, &mut actions); + if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { + v.insert(new_data, pos); } - Queue::handle_actions(self, actions); } Command::QueueRemove(index) => { self.queue.remove_by_index(&index, 0); } Command::QueueGoto(index) => Queue::set_index_db(self, &index), - Command::QueueSetShuffle(path, order) => { - let mut actions = vec![]; - if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0, &mut actions) { - if let QueueContent::Shuffle { inner, state } = elem.content_mut() { - if let QueueContent::Folder(_, v, _) = inner.content_mut() { - let mut o = std::mem::replace(v, vec![]) - .into_iter() - .map(|v| Some(v)) - .collect::>(); - for &i in order.iter() { - if let Some(a) = o.get_mut(i).and_then(Option::take) { - v.push(a); - } else { - eprintln!("[{}] Can't properly apply requested order to Queue/Shuffle: no element at index {i}. Index may be out of bounds or used twice. Len: {}, Order: {order:?}.", "WARN".yellow(), v.len()); - } + Command::QueueShuffle(path) => { + if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { + if let QueueContent::Folder(QueueFolder { + index: _, + content, + name: _, + order: _, + }) = elem.content_mut() + { + let mut ord: Vec = (0..content.len()).collect(); + ord.shuffle(&mut thread_rng()); + self.apply_command(Command::QueueSetShuffle(path, ord)); + } else { + eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!"); + } + } else { + eprintln!("(QueueShuffle) No QueueElement at {path:?}"); + } + } + Command::QueueSetShuffle(path, ord) => { + if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { + if let QueueContent::Folder(QueueFolder { + index, + content, + name: _, + order, + }) = elem.content_mut() + { + if ord.len() == content.len() { + if let Some(ni) = ord.iter().position(|v| *v == *index) { + *index = ni; } + *order = Some(ord); + } else { + eprintln!( + "[warn] can't QueueSetShuffle - length of new ord ({}) is not the same as length of content ({})!", + ord.len(), + content.len() + ); } - *state = ShuffleState::Shuffled; } else { eprintln!( - "[warn] can't QueueSetShuffle - element at path {path:?} isn't Shuffle" + "[warn] can't QueueSetShuffle - element at path {path:?} isn't a folder" ); } } else { @@ -590,7 +611,22 @@ impl Database { "WARN".yellow() ); } - Queue::handle_actions(self, actions); + } + Command::QueueUnshuffle(path) => { + if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { + if let QueueContent::Folder(QueueFolder { + index, + content: _, + name: _, + order, + }) = elem.content_mut() + { + if let Some(ni) = order.as_ref().and_then(|v| v.get(*index).copied()) { + *index = ni; + } + *order = None; + } + } } Command::AddSong(song) => { self.add_song_new(song); @@ -745,7 +781,7 @@ impl Database { songs: HashMap::new(), covers: HashMap::new(), custom_files: None, - queue: QueueContent::Folder(0, vec![], String::new()).into(), + queue: QueueContent::Folder(QueueFolder::default()).into(), update_endpoints: vec![], playing: false, command_sender: None, @@ -765,7 +801,7 @@ impl Database { songs: HashMap::new(), covers: HashMap::new(), custom_files: None, - queue: QueueContent::Folder(0, vec![], String::new()).into(), + queue: QueueContent::Folder(QueueFolder::default()).into(), update_endpoints: vec![], playing: false, command_sender: None, @@ -790,7 +826,7 @@ impl Database { songs: ToFromBytes::from_bytes(&mut file)?, covers: ToFromBytes::from_bytes(&mut file)?, custom_files: None, - queue: QueueContent::Folder(0, vec![], String::new()).into(), + queue: QueueContent::Folder(QueueFolder::default()).into(), update_endpoints: vec![], playing: false, command_sender: None, diff --git a/musicdb-lib/src/data/queue.rs b/musicdb-lib/src/data/queue.rs index 4bdfa15..259f9d0 100755 --- a/musicdb-lib/src/data/queue.rs +++ b/musicdb-lib/src/data/queue.rs @@ -1,8 +1,6 @@ -use std::{collections::VecDeque, ops::AddAssign}; +use std::ops::AddAssign; -use rand::seq::{IteratorRandom, SliceRandom}; - -use crate::{load::ToFromBytes, server::Command}; +use crate::load::ToFromBytes; use super::{database::Database, SongId}; @@ -14,25 +12,15 @@ pub struct Queue { #[derive(Clone, Debug)] pub enum QueueContent { Song(SongId), - Folder(usize, Vec, String), + Folder(QueueFolder), Loop(usize, usize, Box), - Random(VecDeque), - Shuffle { - inner: Box, - state: ShuffleState, - }, } -#[derive(Clone, Copy, Debug)] -pub enum ShuffleState { - NotShuffled, - Modified, - Shuffled, -} - -pub enum QueueAction { - AddRandomSong(Vec), - /// `partial: bool`, if true, indicates that we only shuffle what is beyond the current index - SetShuffle(Vec, bool), +#[derive(Clone, Debug, Default)] +pub struct QueueFolder { + pub index: usize, + pub content: Vec, + pub name: String, + pub order: Option>, } impl Queue { @@ -46,72 +34,18 @@ impl Queue { &mut self.content } - pub fn add_to_end( - &mut self, - v: Vec, - mut path: Vec, - actions: &mut Vec, - ) -> Option { + pub fn add_to_end(&mut self, v: Vec) -> Option { match &mut self.content { QueueContent::Song(_) => None, - QueueContent::Folder(_, vec, _) => { - let len = vec.len(); - for (i, mut v) in v.into_iter().enumerate() { - path.push(len + i); - v.init(path.clone(), actions); - vec.push(v); - path.pop(); - } - Some(len) - } + QueueContent::Folder(folder) => folder.add_to_end(v), QueueContent::Loop(..) => None, - QueueContent::Random(q) => { - // insert new elements - let len = q.len(); - for (i, mut v) in v.into_iter().enumerate() { - path.push(len + i); - v.init(path.clone(), actions); - q.push_back(v); - path.pop(); - } - Some(len) - } - QueueContent::Shuffle { .. } => None, } } - pub fn insert( - &mut self, - v: Vec, - index: usize, - mut path: Vec, - actions: &mut Vec, - ) -> bool { + pub fn insert(&mut self, v: Vec, index: usize) -> bool { match &mut self.content { QueueContent::Song(_) => false, - QueueContent::Folder(current, vec, _) => { - if index <= vec.len() { - if *current >= index { - *current += v.len(); - } - // remove the elements starting at the insertion point - let end = vec.split_off(index); - // insert new elements - for (i, mut v) in v.into_iter().enumerate() { - path.push(index + i); - v.init(path.clone(), actions); - vec.push(v); - path.pop(); - } - // re-add previously removed elements - vec.extend(end); - true - } else { - false - } - } - QueueContent::Loop(..) | QueueContent::Random(..) | QueueContent::Shuffle { .. } => { - false - } + QueueContent::Folder(folder) => folder.insert(v, index), + QueueContent::Loop(..) => false, } } @@ -121,16 +55,14 @@ impl Queue { } match &self.content { QueueContent::Song(_) => 1, - QueueContent::Folder(_, v, _) => v.iter().map(|v| v.len()).sum(), - QueueContent::Random(v) => v.iter().map(|v| v.len()).sum(), + QueueContent::Folder(folder) => folder.len(), QueueContent::Loop(total, _done, inner) => { if *total == 0 { inner.len() } else { - *total * inner.len() + total.saturating_mul(inner.len()) } } - QueueContent::Shuffle { inner, state: _ } => inner.len(), } } pub fn duration_total(&self, db: &Database) -> QueueDuration { @@ -150,9 +82,14 @@ impl Queue { QueueContent::Song(v) => { dur.millis += db.get_song(v).map(|s| s.duration_millis).unwrap_or(0) } - QueueContent::Folder(c, inner, _) => { - for (i, inner) in inner.iter().enumerate() { - if dur.include_past || i >= *c { + QueueContent::Folder(QueueFolder { + index, + content, + name: _, + order: _, + }) => { + for (i, inner) in content.iter().enumerate() { + if dur.include_past || i >= *index { inner.add_duration(dur, db); } } @@ -175,15 +112,6 @@ impl Queue { } } } - QueueContent::Random(q) => { - if let Some(el) = q.iter().next() { - dur.random_known_millis += el.duration_total(db).millis; - } - dur.random_counter += 1; - } - QueueContent::Shuffle { inner, state: _ } => { - inner.add_duration(dur, db); - } } } } @@ -192,17 +120,8 @@ impl Queue { pub fn get_current(&self) -> Option<&Self> { match &self.content { QueueContent::Song(_) => Some(self), - QueueContent::Folder(i, v, _) => { - let i = *i; - if let Some(v) = v.get(i) { - v.get_current() - } else { - None - } - } + QueueContent::Folder(folder) => folder.get_current_immut()?.get_current(), QueueContent::Loop(_, _, inner) => inner.get_current(), - QueueContent::Random(v) => v.get(v.len().saturating_sub(2))?.get_current(), - QueueContent::Shuffle { inner, state: _ } => inner.get_current(), } } pub fn get_current_song(&self) -> Option<&SongId> { @@ -222,22 +141,7 @@ impl Queue { pub fn get_next(&self) -> Option<&Self> { match &self.content { QueueContent::Song(_) => None, - QueueContent::Folder(i, vec, _) => { - let i = *i; - if let Some(v) = vec.get(i) { - if let Some(v) = v.get_next() { - Some(v) - } else { - if let Some(v) = vec.get(i + 1) { - v.get_current() - } else { - None - } - } - } else { - None - } - } + QueueContent::Folder(folder) => folder.get_next(), QueueContent::Loop(total, current, inner) => { if let Some(v) = inner.get_next() { Some(v) @@ -247,156 +151,43 @@ impl Queue { None } } - QueueContent::Random(v) => v.get(v.len().saturating_sub(1))?.get_current(), - QueueContent::Shuffle { inner, state: _ } => inner.get_next(), } } pub fn get_first(&self) -> Option<&Self> { match &self.content { QueueContent::Song(..) => Some(self), - QueueContent::Folder(_, v, _) => v.first(), + QueueContent::Folder(folder) => folder.get_first(), QueueContent::Loop(_, _, q) => q.get_first(), - QueueContent::Random(q) => q.front(), - QueueContent::Shuffle { inner, state: _ } => inner.get_first(), } } pub fn advance_index_db(db: &mut Database) -> bool { - let mut actions = vec![]; - let o = db.queue.advance_index_inner(vec![], &mut actions); - Self::handle_actions(db, actions); + let o = db.queue.advance_index_inner(); o } - pub fn init(&mut self, path: Vec, actions: &mut Vec) { + pub fn init(&mut self) { match &mut self.content { QueueContent::Song(..) => {} - QueueContent::Folder(i, v, _) => { - *i = 0; - if let Some(v) = v.first_mut() { - v.init( - { - let mut p = path.clone(); - p.push(0); - p - }, - actions, - ); + QueueContent::Folder(folder) => { + folder.index = 0; + for v in &mut folder.content { + v.init(); } } - QueueContent::Loop(_, _, inner) => inner.init( - { - let mut p = path.clone(); - p.push(0); - p - }, - actions, - ), - QueueContent::Random(q) => { - if q.len() == 0 { - actions.push(QueueAction::AddRandomSong(path.clone())); - actions.push(QueueAction::AddRandomSong(path.clone())); - } - if let Some(q) = q.get_mut(q.len().saturating_sub(2)) { - q.init(path, actions) - } - } - QueueContent::Shuffle { inner, state } => { - let mut p = path.clone(); - p.push(0); - if inner.len() == 0 { - *state = ShuffleState::NotShuffled; - } else if matches!(state, ShuffleState::NotShuffled | ShuffleState::Modified) { - actions.push(QueueAction::SetShuffle( - path, - matches!(state, ShuffleState::Modified), - )); - *state = ShuffleState::Shuffled; - } - inner.init(p, actions); - } + QueueContent::Loop(_, _, inner) => inner.init(), } } - pub fn handle_actions(db: &mut Database, actions: Vec) { - for action in actions { - match action { - QueueAction::AddRandomSong(path) => { - if !db.is_client() { - if let Some(song) = db.songs().keys().choose(&mut rand::thread_rng()) { - db.apply_command(Command::QueueAdd( - path, - vec![QueueContent::Song(*song).into()], - )); - } - } - } - QueueAction::SetShuffle(path, partial) => { - if !db.is_client() { - let mut actions = vec![]; - if let Some(QueueContent::Shuffle { inner, state: _ }) = db - .queue - .get_item_at_index_mut(&path, 0, &mut actions) - .map(|v| v.content_mut()) - { - if let QueueContent::Folder(i, v, _) = inner.content_mut() { - let mut order = (0..v.len()).collect::>(); - if partial && *i + 1 < v.len() { - // shuffle only elements after the current one - order[*i + 1..].shuffle(&mut rand::thread_rng()); - } else { - order.shuffle(&mut rand::thread_rng()); - } - db.apply_command(Command::QueueSetShuffle(path, order)); - } - } - Queue::handle_actions(db, actions); - } - } - } - } - } - pub fn advance_index_inner( - &mut self, - mut path: Vec, - actions: &mut Vec, - ) -> bool { + pub fn advance_index_inner(&mut self) -> bool { match &mut self.content { QueueContent::Song(_) => false, - QueueContent::Folder(index, contents, _) => { - if let Some(c) = contents.get_mut(*index) { - let mut p = path.clone(); - p.push(*index); - if c.advance_index_inner(p, actions) { - // inner value could advance index, do nothing. - true - } else { - loop { - if *index + 1 < contents.len() { - // can advance - *index += 1; - if contents[*index].enabled { - contents[*index].init(path, actions); - break true; - } - } else { - // can't advance: index would be out of bounds - *index = 0; - break false; - } - } - } - } else { - *index = 0; - false - } - } + QueueContent::Folder(folder) => folder.advance_index_inner(), QueueContent::Loop(total, current, inner) => { - path.push(0); - if inner.advance_index_inner(path.clone(), actions) { + if inner.advance_index_inner() { true } else { *current += 1; if *total == 0 || *current < *total { - inner.init(path, actions); + inner.init(); true } else { *current = 0; @@ -404,56 +195,18 @@ impl Queue { } } } - QueueContent::Random(q) => { - let i = q.len().saturating_sub(2); - let mut p = path.clone(); - p.push(i); - if q.get_mut(i) - .is_some_and(|inner| inner.advance_index_inner(p, actions)) - { - true - } else { - if q.len() >= 2 { - q.pop_front(); - } - // only sub 1 here because this is before the next random song is added - let i2 = q.len().saturating_sub(1); - if let Some(q) = q.get_mut(i2) { - let mut p = path.clone(); - p.push(i2); - q.init(p, actions); - } - actions.push(QueueAction::AddRandomSong(path)); - false - } - } - QueueContent::Shuffle { inner, state } => { - let mut p = path.clone(); - p.push(0); - if !inner.advance_index_inner(p, actions) { - // end of inner Folder element, reshuffle for next time - *state = ShuffleState::Shuffled; - actions.push(QueueAction::SetShuffle(path, false)); - false - } else { - true - } - } } } pub fn set_index_db(db: &mut Database, index: &Vec) { - let mut actions = vec![]; db.queue.reset_index(); - db.queue.set_index_inner(index, 0, vec![], &mut actions); - Self::handle_actions(db, actions); + db.queue.set_index_inner(index, 0, vec![]); } pub fn set_index_inner( &mut self, index: &Vec, depth: usize, mut build_index: Vec, - actions: &mut Vec, ) { let i = if let Some(i) = index.get(depth) { *i @@ -463,32 +216,25 @@ impl Queue { build_index.push(i); match &mut self.content { QueueContent::Song(_) => {} - QueueContent::Folder(idx, contents, _) => { - if i != *idx { - *idx = i; - } - if let Some(c) = contents.get_mut(i) { - c.init(build_index.clone(), actions); - c.set_index_inner(index, depth + 1, build_index, actions); + QueueContent::Folder(folder) => { + folder.index = i; + if let Some(c) = folder.get_current_mut() { + c.init(); + c.set_index_inner(index, depth + 1, build_index); } } QueueContent::Loop(_, _, inner) => { - inner.init(build_index.clone(), actions); - inner.set_index_inner(index, depth + 1, build_index, actions) - } - QueueContent::Random(_) => {} - QueueContent::Shuffle { inner, state: _ } => { - inner.init(build_index.clone(), actions); - inner.set_index_inner(index, depth + 1, build_index, actions) + inner.init(); + inner.set_index_inner(index, depth + 1, build_index) } } } pub fn reset_index(&mut self) { match self.content_mut() { QueueContent::Song(_) => {} - QueueContent::Folder(i, v, _) => { - *i = 0; - for v in v { + QueueContent::Folder(folder) => { + folder.index = 0; + for v in &mut folder.content { v.reset_index(); } } @@ -496,10 +242,6 @@ impl Queue { *done = 0; i.reset_index(); } - QueueContent::Random(_) => {} - QueueContent::Shuffle { inner, state: _ } => { - inner.reset_index(); - } } } @@ -507,61 +249,31 @@ impl Queue { if let Some(i) = index.get(depth) { match &self.content { QueueContent::Song(_) => None, - QueueContent::Folder(_, v, _) => { - if let Some(v) = v.get(*i) { + QueueContent::Folder(folder) => { + if let Some(v) = folder.get_at(*i) { v.get_item_at_index(index, depth + 1) } else { None } } QueueContent::Loop(_, _, inner) => inner.get_item_at_index(index, depth + 1), - QueueContent::Random(vec) => vec.get(*i)?.get_item_at_index(index, depth + 1), - QueueContent::Shuffle { inner, state: _ } => { - inner.get_item_at_index(index, depth + 1) - } } } else { Some(self) } } - pub fn get_item_at_index_mut( - &mut self, - index: &Vec, - depth: usize, - actions: &mut Vec, - ) -> Option<&mut Self> { + pub fn get_item_at_index_mut(&mut self, index: &Vec, depth: usize) -> Option<&mut Self> { if let Some(i) = index.get(depth) { match &mut self.content { QueueContent::Song(_) => None, - QueueContent::Folder(_, v, _) => { - if let Some(v) = v.get_mut(*i) { - v.get_item_at_index_mut(index, depth + 1, actions) + QueueContent::Folder(folder) => { + if let Some(v) = folder.get_mut_at(*i) { + v.get_item_at_index_mut(index, depth + 1) } else { None } } - QueueContent::Loop(_, _, inner) => { - inner.get_item_at_index_mut(index, depth + 1, actions) - } - QueueContent::Random(vec) => { - vec.get_mut(*i)? - .get_item_at_index_mut(index, depth + 1, actions) - } - QueueContent::Shuffle { inner, state } => { - // if getting a mutable reference to the Folder that holds our songs, - // it may have been modified - if depth + 1 == index.len() && matches!(state, ShuffleState::Shuffled) { - *state = ShuffleState::Modified; - } - if matches!(state, ShuffleState::NotShuffled | ShuffleState::Modified) { - actions.push(QueueAction::SetShuffle( - index[0..depth].to_vec(), - matches!(state, ShuffleState::Modified), - )); - *state = ShuffleState::Shuffled; - } - inner.get_item_at_index_mut(index, depth + 1, actions) - } + QueueContent::Loop(_, _, inner) => inner.get_item_at_index_mut(index, depth + 1), } } else { Some(self) @@ -572,21 +284,33 @@ impl Queue { if let Some(i) = index.get(depth) { match &mut self.content { QueueContent::Song(_) => None, - QueueContent::Folder(ci, v, _) => { + QueueContent::Folder(folder) => { if depth + 1 < index.len() { - if let Some(v) = v.get_mut(*i) { + if let Some(v) = folder.get_mut_at(*i) { v.remove_by_index(index, depth + 1) } else { None } } else { - if *i < v.len() { + if *i < folder.content.len() { // if current playback is past this point, // reduce the index by 1 so that it still points to the same element - if *ci > *i { - *ci -= 1; + if folder.index > *i { + folder.index -= 1; } - Some(v.remove(*i)) + let idx = if let Some(order) = &mut folder.order { + let idx = order.remove(*i); + // compensate for removal of element from .content + for o in order { + if *o > idx { + *o -= 1; + } + } + idx + } else { + *i + }; + Some(folder.content.remove(idx)) } else { None } @@ -599,15 +323,148 @@ impl Queue { None } } - QueueContent::Random(v) => v.remove(*i), - QueueContent::Shuffle { inner, state: _ } => { - inner.remove_by_index(index, depth + 1) + } + } else { + None + } + } +} + +impl QueueFolder { + pub fn iter(&self) -> QueueFolderIter { + QueueFolderIter { + folder: self, + index: 0, + } + } + pub fn add_to_end(&mut self, v: Vec) -> Option { + let add_len = v.len(); + let len = self.content.len(); + for mut v in v.into_iter() { + v.init(); + self.content.push(v); + } + if let Some(order) = &mut self.order { + for i in 0..add_len { + order.push(len + i); + } + } + Some(len) + } + pub fn insert(&mut self, v: Vec, index: usize) -> bool { + if index <= self.content.len() { + if self.index >= index { + self.index += v.len(); + } + fn insert_multiple(index: usize, vec: &mut Vec, v: impl IntoIterator) { + // remove the elements starting at the insertion point + let end = vec.split_off(index); + // insert new elements + for v in v { + vec.push(v); + } + // re-add previously removed elements + vec.extend(end); + } + let mapfunc = |mut v: Queue| { + v.init(); + v + }; + if let Some(order) = &mut self.order { + insert_multiple(index, order, (0..v.len()).map(|i| self.content.len() + i)); + self.content.extend(v.into_iter().map(mapfunc)); + } else { + insert_multiple(index, &mut self.content, v.into_iter().map(mapfunc)); + } + true + } else { + false + } + } + pub fn len(&self) -> usize { + self.content.iter().map(|v| v.len()).sum() + } + pub fn get_at(&self, mut i: usize) -> Option<&Queue> { + if let Some(order) = &self.order { + i = *order.get(i)?; + } + self.content.get(i) + } + pub fn get_mut_at(&mut self, mut i: usize) -> Option<&mut Queue> { + if let Some(order) = &self.order { + i = *order.get(i)?; + } + self.content.get_mut(i) + } + pub fn get_current_immut(&self) -> Option<&Queue> { + self.get_at(self.index) + } + pub fn get_current_mut(&mut self) -> Option<&mut Queue> { + self.get_mut_at(self.index) + } + pub fn get_next(&self) -> Option<&Queue> { + if let Some(v) = self.get_current_immut() { + if let Some(v) = v.get_next() { + Some(v) + } else { + if let Some(v) = self.get_at(self.index + 1) { + v.get_current() + } else { + None } } } else { None } } + pub fn get_first(&self) -> Option<&Queue> { + if let Some(order) = &self.order { + self.content.get(*order.first()?) + } else { + self.content.first() + } + } + pub fn advance_index_inner(&mut self) -> bool { + if let Some(c) = self.get_current_mut() { + if c.advance_index_inner() { + // inner value could advance index, do nothing. + true + } else { + loop { + if self.index + 1 < self.content.len() { + // can advance + self.index += 1; + if self.content[self.index].enabled { + self.content[self.index].init(); + break true; + } + } else { + // can't advance: index would be out of bounds + self.index = 0; + break false; + } + } + } + } else { + self.index = 0; + false + } + } +} +pub struct QueueFolderIter<'a> { + folder: &'a QueueFolder, + index: usize, +} +impl<'a> Iterator for QueueFolderIter<'a> { + type Item = &'a Queue; + fn next(&mut self) -> Option { + if let Some(v) = self.folder.get_at(self.index) { + self.index += 1; + Some(v) + } else { + None + } + } } impl From for Queue { @@ -651,11 +508,9 @@ impl ToFromBytes for QueueContent { s.write_all(&[0b11111111])?; id.to_bytes(s)?; } - Self::Folder(index, contents, name) => { + Self::Folder(folder) => { s.write_all(&[0b00000000])?; - index.to_bytes(s)?; - contents.to_bytes(s)?; - name.to_bytes(s)?; + ToFromBytes::to_bytes(folder, s)?; } Self::Loop(total, current, inner) => { s.write_all(&[0b11000000])?; @@ -663,15 +518,6 @@ impl ToFromBytes for QueueContent { current.to_bytes(s)?; inner.to_bytes(s)?; } - Self::Random(q) => { - s.write_all(&[0b00110000])?; - q.to_bytes(s)?; - } - Self::Shuffle { inner, state } => { - s.write_all(&[0b00001100])?; - inner.to_bytes(s)?; - state.to_bytes(s)?; - } } Ok(()) } @@ -683,54 +529,43 @@ impl ToFromBytes for QueueContent { s.read_exact(&mut switch_on)?; Ok(match switch_on[0] { 0b11111111 => Self::Song(ToFromBytes::from_bytes(s)?), - 0b00000000 => Self::Folder( - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ), + 0b00000000 => Self::Folder(ToFromBytes::from_bytes(s)?), 0b11000000 => Self::Loop( ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?, Box::new(ToFromBytes::from_bytes(s)?), ), - 0b00110000 => Self::Random(ToFromBytes::from_bytes(s)?), - 0b00001100 => Self::Shuffle { - inner: Box::new(ToFromBytes::from_bytes(s)?), - state: ToFromBytes::from_bytes(s)?, - }, - _ => Self::Folder(0, vec![], "".to_string()), + _ => Self::Folder(QueueFolder { + index: 0, + content: vec![], + name: "".to_string(), + order: None, + }), }) } } -impl ToFromBytes for ShuffleState { +impl ToFromBytes for QueueFolder { fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> where - T: std::io::Write, + T: std::io::prelude::Write, { - s.write_all(&[match self { - Self::NotShuffled => 1, - Self::Modified => 2, - Self::Shuffled => 4, - }]) + ToFromBytes::to_bytes(&self.index, s)?; + ToFromBytes::to_bytes(&self.content, s)?; + ToFromBytes::to_bytes(&self.name, s)?; + ToFromBytes::to_bytes(&self.order, s)?; + Ok(()) } fn from_bytes(s: &mut T) -> Result where - T: std::io::Read, + T: std::io::prelude::Read, { - let mut b = [0]; - s.read_exact(&mut b)?; - Ok(match b[0] { - 1 => Self::NotShuffled, - 2 => Self::Modified, - 4 => Self::Shuffled, - _ => { - eprintln!( - "[warn] received {} as ShuffleState, which is invalid. defaulting to Shuffled.", - b[0] - ); - Self::Shuffled - } - }) + let v = Self { + index: ToFromBytes::from_bytes(s)?, + content: ToFromBytes::from_bytes(s)?, + name: ToFromBytes::from_bytes(s)?, + order: ToFromBytes::from_bytes(s)?, + }; + Ok(v) } } diff --git a/musicdb-lib/src/load/mod.rs b/musicdb-lib/src/load/mod.rs index a5c053d..2240edf 100755 --- a/musicdb-lib/src/load/mod.rs +++ b/musicdb-lib/src/load/mod.rs @@ -118,7 +118,7 @@ where match self { None => s.write_all(&[0b11001100]), Some(v) => { - s.write_all(&[0b00111010])?; + s.write_all(&[0b00110011])?; v.to_bytes(s) } } @@ -129,10 +129,11 @@ where { let mut b = [0u8]; s.read_exact(&mut b)?; - match b[0] { - 0b00111010 => Ok(Some(ToFromBytes::from_bytes(s)?)), - _ => Ok(None), - } + Ok(if (b[0] ^ 0b11001100).count_ones() > 4 { + Some(ToFromBytes::from_bytes(s)?) + } else { + None + }) } } impl ToFromBytes for HashMap diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index 174ec58..03e0b12 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -40,7 +40,11 @@ pub enum Command { QueueInsert(Vec, usize, Vec), QueueRemove(Vec), QueueGoto(Vec), + // sent by clients when they want to shuffle a folder + QueueShuffle(Vec), + // sent by the server when the folder was shuffled QueueSetShuffle(Vec, Vec), + QueueUnshuffle(Vec), /// .id field is ignored! AddSong(Song), @@ -256,47 +260,57 @@ pub fn handle_one_connection_as_control( } } -const BYTE_RESUME: u8 = 0b11000000; -const BYTE_PAUSE: u8 = 0b00110000; -const BYTE_STOP: u8 = 0b11110000; -const BYTE_NEXT_SONG: u8 = 0b11110010; +// 01_***_*** => Simple commands +// 01_00*_*** => Playback +// 01_010_*** => Other +// 01_100_*** => Errors +// 10_***_*** => Complicated commands +// 10_00*_*** => Queue +// 10_010_*** => Misc +// 10_100_*** => Library -const BYTE_SYNC_DATABASE: u8 = 0b01011000; -const BYTE_QUEUE_UPDATE: u8 = 0b00011100; -const BYTE_QUEUE_ADD: u8 = 0b00011010; -const BYTE_QUEUE_INSERT: u8 = 0b00011110; -const BYTE_QUEUE_REMOVE: u8 = 0b00011001; -const BYTE_QUEUE_GOTO: u8 = 0b00011011; -const BYTE_QUEUE_SET_SHUFFLE: u8 = 0b10011011; +const BYTE_RESUME: u8 = 0b01_000_000; +const BYTE_PAUSE: u8 = 0b01_000_001; +const BYTE_STOP: u8 = 0b01_000_010; +const BYTE_NEXT_SONG: u8 = 0b01_000_100; -const BYTE_ADD_SONG: u8 = 0b01010000; -const BYTE_ADD_ALBUM: u8 = 0b01010011; -const BYTE_ADD_ARTIST: u8 = 0b01011100; -const BYTE_ADD_COVER: u8 = 0b01011101; -const BYTE_MODIFY_SONG: u8 = 0b10010000; -const BYTE_MODIFY_ALBUM: u8 = 0b10010011; -const BYTE_MODIFY_ARTIST: u8 = 0b10011100; -const BYTE_REMOVE_SONG: u8 = 0b11010000; -const BYTE_REMOVE_ALBUM: u8 = 0b11010011; -const BYTE_REMOVE_ARTIST: u8 = 0b11011100; -const BYTE_TAG_SONG_FLAG_SET: u8 = 0b11100000; -const BYTE_TAG_SONG_FLAG_UNSET: u8 = 0b11100001; -const BYTE_TAG_ALBUM_FLAG_SET: u8 = 0b11100010; -const BYTE_TAG_ALBUM_FLAG_UNSET: u8 = 0b11100011; -const BYTE_TAG_ARTIST_FLAG_SET: u8 = 0b11100110; -const BYTE_TAG_ARTIST_FLAG_UNSET: u8 = 0b11100111; -const BYTE_TAG_SONG_PROPERTY_SET: u8 = 0b11101001; -const BYTE_TAG_SONG_PROPERTY_UNSET: u8 = 0b11101010; -const BYTE_TAG_ALBUM_PROPERTY_SET: u8 = 0b11101011; -const BYTE_TAG_ALBUM_PROPERTY_UNSET: u8 = 0b11101100; -const BYTE_TAG_ARTIST_PROPERTY_SET: u8 = 0b11101110; -const BYTE_TAG_ARTIST_PROPERTY_UNSET: u8 = 0b11101111; +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_SET_SONG_DURATION: u8 = 0b11111000; +const BYTE_QUEUE_UPDATE: u8 = 0b10_000_000; +const BYTE_QUEUE_ADD: u8 = 0b10_000_001; +const BYTE_QUEUE_INSERT: u8 = 0b10_000_010; +const BYTE_QUEUE_REMOVE: u8 = 0b10_000_100; +const BYTE_QUEUE_GOTO: u8 = 0b10_001_000; +const BYTE_QUEUE_ACTION: u8 = 0b10_100; +const SUBBYTE_ACTION_SHUFFLE: u8 = 0b01_000_001; +const SUBBYTE_ACTION_SET_SHUFFLE: u8 = 0b01_000_010; +const SUBBYTE_ACTION_UNSHUFFLE: u8 = 0b01_000_100; -const BYTE_INIT_COMPLETE: u8 = 0b00110001; -const BYTE_SAVE: u8 = 0b11110011; -const BYTE_ERRORINFO: u8 = 0b11011011; +const BYTE_SYNC_DATABASE: u8 = 0b10_010_100; + +const BYTE_LIB_ADD: u8 = 0b10_100_000; +const BYTE_LIB_MODIFY: u8 = 0b10_100_001; +const BYTE_LIB_REMOVE: u8 = 0b10_100_010; +const BYTE_LIB_TAG: u8 = 0b10_100_100; +const SUBBYTE_SONG: u8 = 0b10_001_000; +const SUBBYTE_ALBUM: u8 = 0b10_001_001; +const SUBBYTE_ARTIST: u8 = 0b10_001_010; +const SUBBYTE_COVER: u8 = 0b10_001_100; +const SUBBYTE_TAG_SONG_FLAG_SET: u8 = 0b10_001_000; +const SUBBYTE_TAG_SONG_FLAG_UNSET: u8 = 0b10_001_001; +const SUBBYTE_TAG_ALBUM_FLAG_SET: u8 = 0b10_001_010; +const SUBBYTE_TAG_ALBUM_FLAG_UNSET: u8 = 0b10_001_100; +const SUBBYTE_TAG_ARTIST_FLAG_SET: u8 = 0b10_010_000; +const SUBBYTE_TAG_ARTIST_FLAG_UNSET: u8 = 0b10_010_001; +const SUBBYTE_TAG_SONG_PROPERTY_SET: u8 = 0b10_010_010; +const SUBBYTE_TAG_SONG_PROPERTY_UNSET: u8 = 0b10_010_100; +const SUBBYTE_TAG_ALBUM_PROPERTY_SET: u8 = 0b10_100_000; +const SUBBYTE_TAG_ALBUM_PROPERTY_UNSET: u8 = 0b10_100_001; +const SUBBYTE_TAG_ARTIST_PROPERTY_SET: u8 = 0b10_100_010; +const SUBBYTE_TAG_ARTIST_PROPERTY_UNSET: u8 = 0b10_100_100; impl ToFromBytes for Command { fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> @@ -338,111 +352,144 @@ impl ToFromBytes for Command { s.write_all(&[BYTE_QUEUE_GOTO])?; index.to_bytes(s)?; } + Self::QueueShuffle(path) => { + s.write_all(&[BYTE_QUEUE_ACTION])?; + s.write_all(&[SUBBYTE_ACTION_SHUFFLE])?; + path.to_bytes(s)?; + } Self::QueueSetShuffle(path, map) => { - s.write_all(&[BYTE_QUEUE_SET_SHUFFLE])?; + s.write_all(&[BYTE_QUEUE_ACTION])?; + s.write_all(&[SUBBYTE_ACTION_SET_SHUFFLE])?; path.to_bytes(s)?; map.to_bytes(s)?; } + Self::QueueUnshuffle(path) => { + s.write_all(&[BYTE_QUEUE_ACTION])?; + s.write_all(&[SUBBYTE_ACTION_UNSHUFFLE])?; + path.to_bytes(s)?; + } Self::AddSong(song) => { - s.write_all(&[BYTE_ADD_SONG])?; + s.write_all(&[BYTE_LIB_ADD])?; + s.write_all(&[SUBBYTE_SONG])?; song.to_bytes(s)?; } Self::AddAlbum(album) => { - s.write_all(&[BYTE_ADD_ALBUM])?; + s.write_all(&[BYTE_LIB_ADD])?; + s.write_all(&[SUBBYTE_ALBUM])?; album.to_bytes(s)?; } Self::AddArtist(artist) => { - s.write_all(&[BYTE_ADD_ARTIST])?; + s.write_all(&[BYTE_LIB_ADD])?; + s.write_all(&[SUBBYTE_ARTIST])?; artist.to_bytes(s)?; } Self::AddCover(cover) => { - s.write_all(&[BYTE_ADD_COVER])?; + s.write_all(&[BYTE_LIB_ADD])?; + s.write_all(&[SUBBYTE_COVER])?; cover.to_bytes(s)?; } Self::ModifySong(song) => { - s.write_all(&[BYTE_MODIFY_SONG])?; + s.write_all(&[BYTE_LIB_MODIFY])?; + s.write_all(&[SUBBYTE_SONG])?; song.to_bytes(s)?; } Self::ModifyAlbum(album) => { - s.write_all(&[BYTE_MODIFY_ALBUM])?; + s.write_all(&[BYTE_LIB_MODIFY])?; + s.write_all(&[SUBBYTE_ALBUM])?; album.to_bytes(s)?; } Self::ModifyArtist(artist) => { - s.write_all(&[BYTE_MODIFY_ARTIST])?; + s.write_all(&[BYTE_LIB_MODIFY])?; + s.write_all(&[SUBBYTE_ARTIST])?; artist.to_bytes(s)?; } Self::RemoveSong(song) => { - s.write_all(&[BYTE_REMOVE_SONG])?; + s.write_all(&[BYTE_LIB_REMOVE])?; + s.write_all(&[SUBBYTE_SONG])?; song.to_bytes(s)?; } Self::RemoveAlbum(album) => { - s.write_all(&[BYTE_REMOVE_ALBUM])?; + s.write_all(&[BYTE_LIB_REMOVE])?; + s.write_all(&[SUBBYTE_ALBUM])?; album.to_bytes(s)?; } Self::RemoveArtist(artist) => { - s.write_all(&[BYTE_REMOVE_ARTIST])?; + s.write_all(&[BYTE_LIB_REMOVE])?; + s.write_all(&[SUBBYTE_ARTIST])?; artist.to_bytes(s)?; } Self::TagSongFlagSet(id, tag) => { - s.write_all(&[BYTE_TAG_SONG_FLAG_SET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_SONG_FLAG_SET])?; id.to_bytes(s)?; tag.to_bytes(s)?; } Self::TagSongFlagUnset(id, tag) => { - s.write_all(&[BYTE_TAG_SONG_FLAG_UNSET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_SONG_FLAG_UNSET])?; id.to_bytes(s)?; tag.to_bytes(s)?; } Self::TagAlbumFlagSet(id, tag) => { - s.write_all(&[BYTE_TAG_ALBUM_FLAG_SET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ALBUM_FLAG_SET])?; id.to_bytes(s)?; tag.to_bytes(s)?; } Self::TagAlbumFlagUnset(id, tag) => { - s.write_all(&[BYTE_TAG_ALBUM_FLAG_UNSET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ALBUM_FLAG_UNSET])?; id.to_bytes(s)?; tag.to_bytes(s)?; } Self::TagArtistFlagSet(id, tag) => { - s.write_all(&[BYTE_TAG_ARTIST_FLAG_SET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ARTIST_FLAG_SET])?; id.to_bytes(s)?; tag.to_bytes(s)?; } Self::TagArtistFlagUnset(id, tag) => { - s.write_all(&[BYTE_TAG_ARTIST_FLAG_UNSET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ARTIST_FLAG_UNSET])?; id.to_bytes(s)?; tag.to_bytes(s)?; } Self::TagSongPropertySet(id, key, val) => { - s.write_all(&[BYTE_TAG_SONG_PROPERTY_SET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_SONG_PROPERTY_SET])?; id.to_bytes(s)?; key.to_bytes(s)?; val.to_bytes(s)?; } Self::TagSongPropertyUnset(id, key) => { - s.write_all(&[BYTE_TAG_SONG_PROPERTY_UNSET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_SONG_PROPERTY_UNSET])?; id.to_bytes(s)?; key.to_bytes(s)?; } Self::TagAlbumPropertySet(id, key, val) => { - s.write_all(&[BYTE_TAG_ALBUM_PROPERTY_SET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ALBUM_PROPERTY_SET])?; id.to_bytes(s)?; key.to_bytes(s)?; val.to_bytes(s)?; } Self::TagAlbumPropertyUnset(id, key) => { - s.write_all(&[BYTE_TAG_ALBUM_PROPERTY_UNSET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ALBUM_PROPERTY_UNSET])?; id.to_bytes(s)?; key.to_bytes(s)?; } Self::TagArtistPropertySet(id, key, val) => { - s.write_all(&[BYTE_TAG_ARTIST_PROPERTY_SET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ARTIST_PROPERTY_SET])?; id.to_bytes(s)?; key.to_bytes(s)?; val.to_bytes(s)?; } Self::TagArtistPropertyUnset(id, key) => { - s.write_all(&[BYTE_TAG_ARTIST_PROPERTY_UNSET])?; + s.write_all(&[BYTE_LIB_TAG])?; + s.write_all(&[SUBBYTE_TAG_ARTIST_PROPERTY_UNSET])?; id.to_bytes(s)?; key.to_bytes(s)?; } @@ -467,14 +514,12 @@ impl ToFromBytes for Command { where T: std::io::Read, { - let mut kind = [0]; - s.read_exact(&mut kind)?; macro_rules! from_bytes { () => { ToFromBytes::from_bytes(s)? }; } - Ok(match kind[0] { + Ok(match s.read_byte()? { BYTE_RESUME => Self::Resume, BYTE_PAUSE => Self::Pause, BYTE_STOP => Self::Stop, @@ -485,42 +530,93 @@ impl ToFromBytes for Command { BYTE_QUEUE_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()), BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()), BYTE_QUEUE_GOTO => Self::QueueGoto(from_bytes!()), - BYTE_QUEUE_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()), - BYTE_ADD_SONG => Self::AddSong(from_bytes!()), - BYTE_ADD_ALBUM => Self::AddAlbum(from_bytes!()), - BYTE_ADD_ARTIST => Self::AddArtist(from_bytes!()), - BYTE_MODIFY_SONG => Self::ModifySong(from_bytes!()), - BYTE_MODIFY_ALBUM => Self::ModifyAlbum(from_bytes!()), - BYTE_MODIFY_ARTIST => Self::ModifyArtist(from_bytes!()), - BYTE_REMOVE_SONG => Self::RemoveSong(from_bytes!()), - BYTE_REMOVE_ALBUM => Self::RemoveAlbum(from_bytes!()), - BYTE_REMOVE_ARTIST => Self::RemoveArtist(from_bytes!()), - BYTE_TAG_SONG_FLAG_SET => Self::TagSongFlagSet(from_bytes!(), from_bytes!()), - BYTE_TAG_SONG_FLAG_UNSET => Self::TagSongFlagUnset(from_bytes!(), from_bytes!()), - BYTE_TAG_ALBUM_FLAG_SET => Self::TagAlbumFlagSet(from_bytes!(), from_bytes!()), - BYTE_TAG_ALBUM_FLAG_UNSET => Self::TagAlbumFlagUnset(from_bytes!(), from_bytes!()), - BYTE_TAG_ARTIST_FLAG_SET => Self::TagArtistFlagSet(from_bytes!(), from_bytes!()), - BYTE_TAG_ARTIST_FLAG_UNSET => Self::TagArtistFlagUnset(from_bytes!(), from_bytes!()), - BYTE_TAG_SONG_PROPERTY_SET => { - Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) - } - BYTE_TAG_SONG_PROPERTY_UNSET => { - Self::TagSongPropertyUnset(from_bytes!(), from_bytes!()) - } - BYTE_TAG_ALBUM_PROPERTY_SET => { - Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) - } - BYTE_TAG_ALBUM_PROPERTY_UNSET => { - Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!()) - } - BYTE_TAG_ARTIST_PROPERTY_SET => { - Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) - } - BYTE_TAG_ARTIST_PROPERTY_UNSET => { - Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!()) - } + BYTE_QUEUE_ACTION => match s.read_byte()? { + SUBBYTE_ACTION_SHUFFLE => Self::QueueShuffle(from_bytes!()), + SUBBYTE_ACTION_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()), + SUBBYTE_ACTION_UNSHUFFLE => Self::QueueUnshuffle(from_bytes!()), + _ => { + eprintln!( + "[{}] unexpected byte when reading command:queueAction; stopping playback.", + "WARN".yellow() + ); + Self::Stop + } + }, + 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!()), + _ => { + eprintln!( + "[{}] unexpected byte when reading command:libAdd; stopping playback.", + "WARN".yellow() + ); + Self::Stop + } + }, + 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!()), + _ => { + eprintln!( + "[{}] unexpected byte when reading command:libModify; stopping playback.", + "WARN".yellow() + ); + Self::Stop + } + }, + BYTE_LIB_REMOVE => match s.read_byte()? { + SUBBYTE_SONG => Self::RemoveSong(from_bytes!()), + SUBBYTE_ALBUM => Self::RemoveAlbum(from_bytes!()), + SUBBYTE_ARTIST => Self::RemoveArtist(from_bytes!()), + _ => { + eprintln!( + "[{}] unexpected byte when reading command:libRemove; stopping playback.", + "WARN".yellow() + ); + Self::Stop + } + }, + BYTE_LIB_TAG => match s.read_byte()? { + SUBBYTE_TAG_SONG_FLAG_SET => Self::TagSongFlagSet(from_bytes!(), from_bytes!()), + SUBBYTE_TAG_SONG_FLAG_UNSET => Self::TagSongFlagUnset(from_bytes!(), from_bytes!()), + SUBBYTE_TAG_ALBUM_FLAG_SET => Self::TagAlbumFlagSet(from_bytes!(), from_bytes!()), + SUBBYTE_TAG_ALBUM_FLAG_UNSET => { + Self::TagAlbumFlagUnset(from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_ARTIST_FLAG_SET => Self::TagArtistFlagSet(from_bytes!(), from_bytes!()), + SUBBYTE_TAG_ARTIST_FLAG_UNSET => { + Self::TagArtistFlagUnset(from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_SONG_PROPERTY_SET => { + Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_SONG_PROPERTY_UNSET => { + Self::TagSongPropertyUnset(from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_ALBUM_PROPERTY_SET => { + Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_ALBUM_PROPERTY_UNSET => { + Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_ARTIST_PROPERTY_SET => { + Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) + } + SUBBYTE_TAG_ARTIST_PROPERTY_UNSET => { + Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!()) + } + _ => { + eprintln!( + "[{}] unexpected byte when reading command:libTag; stopping playback.", + "WARN".yellow() + ); + Self::Stop + } + }, BYTE_SET_SONG_DURATION => Self::SetSongDuration(from_bytes!(), from_bytes!()), - BYTE_ADD_COVER => Self::AddCover(from_bytes!()), BYTE_INIT_COMPLETE => Self::InitComplete, BYTE_SAVE => Self::Save, BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()), @@ -534,3 +630,14 @@ impl ToFromBytes for Command { }) } } + +trait ReadByte { + fn read_byte(&mut self) -> std::io::Result; +} +impl ReadByte for T { + fn read_byte(&mut self) -> std::io::Result { + let mut b = [0]; + self.read_exact(&mut b)?; + Ok(b[0]) + } +} diff --git a/musicdb-mers/src/lib.rs b/musicdb-mers/src/lib.rs index 29b5d45..79973e5 100644 --- a/musicdb-mers/src/lib.rs +++ b/musicdb-mers/src/lib.rs @@ -13,7 +13,7 @@ use musicdb_lib::{ album::Album, artist::Artist, database::Database, - queue::{Queue, QueueContent}, + queue::{Queue, QueueContent, QueueFolder}, song::Song, }, server::Command, @@ -88,7 +88,9 @@ pub fn add( | Command::QueueInsert(..) | Command::QueueRemove(..) | Command::QueueGoto(..) - | Command::QueueSetShuffle(..) => { + | Command::QueueShuffle(..) + | Command::QueueSetShuffle(..) + | Command::QueueUnshuffle(..) => { handle(&handler_queue_changed, move || (Data::empty_tuple(), ())); } Command::AddSong(_) @@ -409,7 +411,7 @@ pub fn add( move |_, _| { cmd(Command::QueueUpdate( vec![], - QueueContent::Folder(0, vec![], String::new()).into(), + QueueContent::Folder(QueueFolder::default()).into(), )); Data::empty_tuple() } @@ -479,7 +481,7 @@ pub fn add( vec![QueueContent::Loop( repeat_count.max(0) as _, 0, - Box::new(QueueContent::Folder(0, vec![], String::new()).into()), + Box::new(QueueContent::Folder(QueueFolder::default()).into()), ) .into()], )); @@ -518,60 +520,12 @@ pub fn add( .clone(); cmd(Command::QueueAdd( path, - vec![QueueContent::Folder(0, vec![], name).into()], - )); - Data::empty_tuple() - } - } - ), - ) - .add_var( - "queue_add_random".to_owned(), - func!( - |a, _| { - if a.is_included_in(&mers_lib::program::configs::with_list::ListT(Type::new( - data::int::IntT, - ))) { - Ok(Type::empty_tuple()) - } else { - Err(format!("Function argument must be `List`.").into()) - } - }, - { - let cmd = Arc::clone(cmd); - move |a, _| { - let path = int_list_to_usize_vec(&a); - cmd(Command::QueueAdd( - path, - vec![QueueContent::Random(Default::default()).into()], - )); - Data::empty_tuple() - } - } - ), - ) - .add_var( - "queue_add_shuffle".to_owned(), - func!( - |a, _| { - if a.is_included_in(&mers_lib::program::configs::with_list::ListT(Type::new( - data::int::IntT, - ))) { - Ok(Type::empty_tuple()) - } else { - Err(format!("Function argument must be `List`.").into()) - } - }, - { - let cmd = Arc::clone(cmd); - move |a, _| { - let path = int_list_to_usize_vec(&a); - cmd(Command::QueueAdd( - path, - vec![QueueContent::Shuffle { - inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()), - state: musicdb_lib::data::queue::ShuffleState::NotShuffled, - } + vec![QueueContent::Folder(QueueFolder { + index: 0, + content: vec![], + name, + order: None, + }) .into()], )); Data::empty_tuple() @@ -1007,24 +961,23 @@ fn gen_queue_elem(queue_elem: &Queue) -> Data { ("done".to_owned(), Data::new(data::int::Int(*done as _))), ])), ), - QueueContent::Random(_) => ("random".to_owned(), Data::empty_tuple()), - QueueContent::Folder(index, inner, name) => ( + QueueContent::Folder(folder) => ( "folder".to_owned(), Data::new(data::object::Object(vec![ - ("index".to_owned(), Data::new(data::int::Int(*index as _))), + ( + "index".to_owned(), + Data::new(data::int::Int(folder.index as _)), + ), ( "length".to_owned(), - Data::new(data::int::Int(inner.len() as _)), + Data::new(data::int::Int(folder.content.len() as _)), ), ( "name".to_owned(), - Data::new(data::string::String(name.clone())), + Data::new(data::string::String(folder.name.clone())), ), ])), ), - QueueContent::Shuffle { inner: _, state: _ } => { - ("shuffle".to_owned(), Data::empty_tuple()) - } }, ])) } diff --git a/musicdb-server/assets/queue_folder.html b/musicdb-server/assets/queue_folder.html index 2fd42dd..d01126d 100755 --- a/musicdb-server/assets/queue_folder.html +++ b/musicdb-server/assets/queue_folder.html @@ -2,6 +2,7 @@ >> \:name + \?shuffled? (shuffled)\;\; \:content
diff --git a/musicdb-server/assets/queue_folder_current.html b/musicdb-server/assets/queue_folder_current.html index 8384afc..739c48d 100755 --- a/musicdb-server/assets/queue_folder_current.html +++ b/musicdb-server/assets/queue_folder_current.html @@ -2,6 +2,7 @@ >> \:name + \?shuffled? (shuffled)\;\;
\:content
diff --git a/musicdb-server/src/web.rs b/musicdb-server/src/web.rs index ed2ca12..bc4fee0 100755 --- a/musicdb-server/src/web.rs +++ b/musicdb-server/src/web.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::convert::Infallible; use std::mem; use std::net::SocketAddr; @@ -12,7 +13,7 @@ use axum::routing::{get, post}; use axum::{Router, TypedHeader}; use futures::{stream, Stream}; use musicdb_lib::data::database::{Database, UpdateEndpoint}; -use musicdb_lib::data::queue::{Queue, QueueContent}; +use musicdb_lib::data::queue::{Queue, QueueContent, QueueFolder}; use musicdb_lib::server::Command; use tokio_stream::StreamExt as _; @@ -75,8 +76,10 @@ pub struct AppHtml { /// can use: path, title queue_song_current: Vec, /// can use: path, content, name + /// can use in `\?key?then\;else\;`: `shuffled` queue_folder: Vec, /// can use: path, content, name + /// can use in `\?key?then\;else\;`: `shuffled` queue_folder_current: Vec, /// can use: path, total, current, inner queue_loop: Vec, @@ -86,14 +89,6 @@ pub struct AppHtml { queue_loopinf: Vec, /// can use: path, current, inner queue_loopinf_current: Vec, - /// can use: path, content - queue_random: Vec, - /// can use: path, content - queue_random_current: Vec, - /// can use: path, content - queue_shuffle: Vec, - /// can use: path, content - queue_shuffle_current: Vec, } impl AppHtml { pub fn from_dir>(dir: P) -> std::io::Result { @@ -123,22 +118,24 @@ impl AppHtml { queue_loopinf_current: Self::parse(&std::fs::read_to_string( dir.join("queue_loopinf_current.html"), )?), - queue_random: Self::parse(&std::fs::read_to_string(dir.join("queue_random.html"))?), - queue_random_current: Self::parse(&std::fs::read_to_string( - dir.join("queue_random_current.html"), - )?), - queue_shuffle: Self::parse(&std::fs::read_to_string(dir.join("queue_shuffle.html"))?), - queue_shuffle_current: Self::parse(&std::fs::read_to_string( - dir.join("queue_shuffle_current.html"), - )?), }) } pub fn parse(s: &str) -> Vec { + Self::parsei(&mut s.chars().peekable()) + } + pub fn parsei(chars: &mut std::iter::Peekable>) -> Vec { let mut o = Vec::new(); let mut c = String::new(); - let mut chars = s.chars().peekable(); loop { - if let Some(ch) = chars.next() { + // if there is a char (iter not empty) and it is not `\;`. If it is `\;`, consume both chars. + if let Some(ch) = chars.next().filter(|ch| { + if *ch == '\\' && chars.peek().copied() == Some(';') { + chars.next(); + false + } else { + true + } + }) { if ch == '\\' && chars.peek().is_some_and(|ch| *ch == ':') { chars.next(); o.push(HtmlPart::Plain(mem::replace(&mut c, String::new()))); @@ -158,6 +155,20 @@ impl AppHtml { return o; } } + } else if ch == '\\' && chars.peek().is_some_and(|ch| *ch == '?') { + chars.next(); + o.push(HtmlPart::Plain(mem::replace(&mut c, String::new()))); + let mut key = String::new(); + while let Some(ch) = chars.next() { + if ch != '?' { + key.push(ch); + } + } + o.push(HtmlPart::IfElse( + key, + Self::parsei(chars), + Self::parsei(chars), + )); } else { c.push(ch); } @@ -176,6 +187,8 @@ pub enum HtmlPart { Plain(String), /// insert some value depending on context and key Insert(String), + /// If the key exists, use the first one, else use the second one. + IfElse(String, Vec, Vec), } pub async fn main(db: Arc>, sender: mpsc::Sender, addr: SocketAddr) { @@ -210,6 +223,7 @@ pub async fn main(db: Arc>, sender: mpsc::Sender, addr: .map(|v| match v { HtmlPart::Plain(v) => v, HtmlPart::Insert(_) => "", + HtmlPart::IfElse(_, _, _) => "", }) .collect::(), ) @@ -250,7 +264,7 @@ pub async fn main(db: Arc>, sender: mpsc::Sender, addr: post(move || async move { _ = s5.send(Command::QueueUpdate( vec![], - QueueContent::Folder(0, vec![], String::new()).into(), + QueueContent::Folder(QueueFolder::default()).into(), )); }), ) @@ -297,15 +311,16 @@ pub async fn main(db: Arc>, sender: mpsc::Sender, addr: if let Some(album) = db1.lock().unwrap().albums().get(&album_id) { _ = s7.send(Command::QueueAdd( vec![], - vec![QueueContent::Folder( - 0, - album + vec![QueueContent::Folder(QueueFolder { + index: 0, + content: album .songs .iter() .map(|id| QueueContent::Song(*id).into()) .collect(), - album.name.clone(), - ) + name: album.name.clone(), + order: None, + }) .into()], )); } @@ -371,17 +386,26 @@ async fn sse_handler( let mut a = db.artists().iter().collect::>(); a.sort_unstable_by_key(|(_id, artist)| &artist.name); let mut artists = String::new(); - for (id, artist) in a { - for v in &state.html.artists_one { - match v { - HtmlPart::Plain(v) => artists.push_str(v), - HtmlPart::Insert(key) => match key.as_str() { - "id" => artists.push_str(&id.to_string()), - "name" => artists.push_str(&artist.name), - _ => {} - }, + for &(id, artist) in &a { + fn gen( + id: &u64, + artist: &musicdb_lib::data::artist::Artist, + v: &Vec, + arts: &mut String, + ) { + for v in v { + match v { + HtmlPart::Plain(v) => arts.push_str(v), + HtmlPart::Insert(key) => match key.as_str() { + "id" => arts.push_str(&id.to_string()), + "name" => arts.push_str(&artist.name), + _ => {} + }, + HtmlPart::IfElse(_, _, e) => gen(id, artist, e, arts), + } } } + gen(id, artist, &state.html.artists_one, &mut artists); } state .html @@ -393,6 +417,7 @@ async fn sse_handler( "artists" => &artists, _ => "", }, + HtmlPart::IfElse(_, _, _) => "", }) .collect::() }), @@ -402,7 +427,9 @@ async fn sse_handler( | Command::QueueInsert(..) | Command::QueueRemove(..) | Command::QueueGoto(..) - | Command::QueueSetShuffle(..) => { + | Command::QueueShuffle(..) + | Command::QueueSetShuffle(..) + | Command::QueueUnshuffle(..) => { let db = state.db.lock().unwrap(); let current = db .queue @@ -422,34 +449,48 @@ async fn sse_handler( true, false, ); - Event::default().event("queue").data( - state - .html - .queue - .iter() - .map(|v| match v { - HtmlPart::Plain(v) => v, - HtmlPart::Insert(key) => match key.as_str() { - "currentTitle" => { - if let Some(s) = current { - &s.title + Event::default().event("queue").data({ + fn gen( + v: &Vec, + current: Option<&musicdb_lib::data::song::Song>, + next: Option<&musicdb_lib::data::song::Song>, + content: &str, + ) -> String { + v.iter() + .map(|v| match v { + HtmlPart::Plain(v) => Cow::Borrowed(v.as_str()), + HtmlPart::Insert(key) => match key.as_str() { + "currentTitle" => { + if let Some(s) = current { + Cow::Borrowed(s.title.as_str()) + } else { + Cow::Borrowed("") + } + } + "nextTitle" => { + if let Some(s) = next { + Cow::Borrowed(s.title.as_str()) + } else { + Cow::Borrowed("") + } + } + "content" => Cow::Borrowed(content), + _ => Cow::Borrowed(""), + }, + HtmlPart::IfElse(k, t, e) => { + if (k == "current" && current.is_some()) + || (k == "next" && next.is_some()) + { + Cow::Owned(gen(t, current, next, content)) } else { - "" + Cow::Owned(gen(e, current, next, content)) } } - "nextTitle" => { - if let Some(s) = next { - &s.title - } else { - "" - } - } - "content" => &content, - _ => "", - }, - }) - .collect::(), - ) + }) + .collect::() + } + gen(&state.html.queue, current, next, content.as_str()) + }) } Command::Save | Command::InitComplete | Command::ErrorInfo(..) => { return Poll::Pending @@ -483,6 +524,7 @@ async fn artist_view_handler( "name" => albums.push_str(&album.name), _ => {} }, + HtmlPart::IfElse(_, _, _) => (), } } } @@ -501,6 +543,7 @@ async fn artist_view_handler( "albums" => &albums, _ => "", }, + HtmlPart::IfElse(_, _, _) => "", }) .collect(), ) @@ -528,6 +571,7 @@ async fn album_view_handler( "title" => songs.push_str(&song.title), _ => {} }, + HtmlPart::IfElse(_, _, _) => (), } } } @@ -546,6 +590,7 @@ async fn album_view_handler( "songs" => &songs, _ => "", }, + HtmlPart::IfElse(_, _, _) => "", }) .collect(), ) @@ -582,45 +627,72 @@ fn build_queue_content_build( "title" => html.push_str(&song.title), _ => {} }, + HtmlPart::IfElse(_, _, _) => (), } } } } - QueueContent::Folder(ci, c, name) => { + QueueContent::Folder(folder) => { if skip_folder || path.is_empty() { - for (i, c) in c.iter().enumerate() { - let current = current && *ci == i; + for (i, c) in folder.iter().enumerate() { + let current = current && folder.index == i; build_queue_content_build(db, state, html, c, i.to_string(), current, false) } } else { - for v in if current { - &state.html.queue_folder_current - } else { - &state.html.queue_folder - } { - match v { - HtmlPart::Plain(v) => html.push_str(v), - HtmlPart::Insert(key) => match key.as_str() { - "path" => html.push_str(&path), - "name" => html.push_str(name), - "content" => { - for (i, c) in c.iter().enumerate() { - let current = current && *ci == i; - build_queue_content_build( - db, - state, - html, - c, - format!("{path}-{i}"), - current, - false, - ) + fn gen( + v: &Vec, + folder: &QueueFolder, + db: &Database, + state: &AppState, + html: &mut String, + path: &str, + current: bool, + ) { + for v in v { + match v { + HtmlPart::Plain(v) => html.push_str(v), + HtmlPart::Insert(key) => match key.as_str() { + "path" => html.push_str(&path), + "name" => html.push_str(&folder.name), + "content" => { + for (i, c) in folder.iter().enumerate() { + let current = current && folder.index == i; + build_queue_content_build( + db, + state, + html, + c, + format!("{path}-{i}"), + current, + false, + ) + } + } + _ => {} + }, + HtmlPart::IfElse(i, t, e) => { + if i == "shuffled" && folder.order.is_some() { + gen(t, folder, db, state, html, path, current) + } else { + gen(e, folder, db, state, html, path, current) } } - _ => {} - }, + } } } + gen( + if current { + &state.html.queue_folder_current + } else { + &state.html.queue_folder + }, + folder, + db, + state, + html, + &path, + current, + ) } } QueueContent::Loop(total, cur, inner) => { @@ -647,58 +719,7 @@ fn build_queue_content_build( ), _ => {} }, - } - } - } - QueueContent::Random(q) => { - for v in if current { - &state.html.queue_random_current - } else { - &state.html.queue_random - } { - match v { - HtmlPart::Plain(v) => html.push_str(v), - HtmlPart::Insert(key) => match key.as_str() { - "path" => html.push_str(&path), - "content" => { - for (i, v) in q.iter().enumerate() { - build_queue_content_build( - db, - state, - html, - &v, - format!("{path}-0"), - current && i == q.len().saturating_sub(2), - true, - ) - } - } - _ => {} - }, - } - } - } - QueueContent::Shuffle { inner, state: _ } => { - for v in if current { - &state.html.queue_shuffle_current - } else { - &state.html.queue_shuffle - } { - match v { - HtmlPart::Plain(v) => html.push_str(v), - HtmlPart::Insert(key) => match key.as_str() { - "path" => html.push_str(&path), - "content" => build_queue_content_build( - db, - state, - html, - &inner, - format!("{path}-0"), - current, - true, - ), - _ => {} - }, + HtmlPart::IfElse(_, _, _) => (), } } }