include shuffle functionality in folders, remove shuffle and random elements, change ToFromBytes impls

This commit is contained in:
Mark 2024-03-30 22:13:59 +01:00
parent 8b8f13f98a
commit 0fe2648efe
14 changed files with 855 additions and 1136 deletions

View File

@ -24,7 +24,7 @@ default = ["gui", "mers", "merscfg"]
# mers: # mers:
# enables the run-mers mode # enables the run-mers mode
# playback: # 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"] gui = ["speedy2d"]
merscfg = ["mers_lib", "musicdb-mers", "speedy2d"] merscfg = ["mers_lib", "musicdb-mers", "speedy2d"]
mers = ["mers_lib", "musicdb-mers"] mers = ["mers_lib", "musicdb-mers"]

View File

@ -366,7 +366,9 @@ impl Gui {
| Command::QueueInsert(..) | Command::QueueInsert(..)
| Command::QueueRemove(..) | Command::QueueRemove(..)
| Command::QueueGoto(..) | Command::QueueGoto(..)
| Command::QueueSetShuffle(..) => { | Command::QueueShuffle(..)
| Command::QueueSetShuffle(..)
| Command::QueueUnshuffle(..) => {
if let Some(s) = &*event_sender_arc.lock().unwrap() { if let Some(s) = &*event_sender_arc.lock().unwrap() {
_ = s.send_event(GuiEvent::UpdatedQueue); _ = s.send_event(GuiEvent::UpdatedQueue);
} }

View File

@ -2082,6 +2082,8 @@ impl FilterType {
} }
mod selected { mod selected {
use musicdb_lib::data::queue::QueueFolder;
use super::*; use super::*;
#[derive(Clone)] #[derive(Clone)]
pub struct Selected( pub struct Selected(
@ -2187,28 +2189,30 @@ mod selected {
} }
if album_selected { if album_selected {
local_artist.push( local_artist.push(
QueueContent::Folder( QueueContent::Folder(QueueFolder {
0, index: 0,
local_album_owned, content: local_album_owned,
match db.albums().get(album) { name: match db.albums().get(album) {
Some(v) => v.name.clone(), Some(v) => v.name.clone(),
None => "< unknown album >".to_owned(), None => "< unknown album >".to_owned(),
}, },
) order: None,
})
.into(), .into(),
); );
} }
} }
if artist_selected { if artist_selected {
out.push( out.push(
QueueContent::Folder( QueueContent::Folder(QueueFolder {
0, index: 0,
local_artist_owned, content: local_artist_owned,
match db.artists().get(artist) { name: match db.artists().get(artist) {
Some(v) => v.name.to_owned(), Some(v) => v.name.to_owned(),
None => "< unknown artist >".to_owned(), None => "< unknown artist >".to_owned(),
}, },
) order: None,
})
.into(), .into(),
); );
} }

View File

@ -1,9 +1,7 @@
use std::collections::VecDeque;
use musicdb_lib::{ use musicdb_lib::{
data::{ data::{
database::Database, database::Database,
queue::{Queue, QueueContent, QueueDuration, ShuffleState}, queue::{Queue, QueueContent, QueueDuration},
song::Song, song::Song,
AlbumId, ArtistId, AlbumId, ArtistId,
}, },
@ -18,8 +16,8 @@ use speedy2d::{
use crate::{ use crate::{
gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg}, gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg},
gui_base::{Panel, ScrollBox}, gui_base::{Button, Panel, ScrollBox, ScrollBoxSizeUnit},
gui_text::{self, AdvancedLabel, Label}, gui_text::{self, AdvancedLabel, Label, TextField},
}; };
/* /*
@ -35,16 +33,21 @@ pub struct QueueViewer {
config: GuiElemCfg, config: GuiElemCfg,
c_scroll_box: ScrollBox<Vec<Box<dyn GuiElem>>>, c_scroll_box: ScrollBox<Vec<Box<dyn GuiElem>>>,
c_empty_space_drag_handler: QueueEmptySpaceDragHandler, 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, c_duration: AdvancedLabel,
recv: std::sync::mpsc::Receiver<QVMsg>,
queue_updated: bool, queue_updated: bool,
} }
pub enum QVMsg {
ControlFlowElementsSetFolderName(String),
}
const QP_QUEUE1: f32 = 0.0; const QP_QUEUE1: f32 = 0.0;
const QP_QUEUE2: f32 = 0.95; const QP_QUEUE2: f32 = 0.95;
const QP_INV1: f32 = QP_QUEUE2; const QP_INV1: f32 = QP_QUEUE2;
const QP_INV2: f32 = 1.0; const QP_INV2: f32 = 1.0;
impl QueueViewer { impl QueueViewer {
pub fn new(config: GuiElemCfg) -> Self { pub fn new(config: GuiElemCfg) -> Self {
let (sender, recv) = std::sync::mpsc::channel();
let control_flow_elements = ( let control_flow_elements = (
QueueLoop::new( QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5))).w_mouse(), GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5))).w_mouse(),
@ -52,7 +55,15 @@ impl QueueViewer {
QueueContent::Loop( QueueContent::Loop(
0, 0,
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(), .into(),
false, false,
@ -64,30 +75,46 @@ impl QueueViewer {
QueueContent::Loop( QueueContent::Loop(
2, 2,
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(), .into(),
false, false,
) )
.alwayscopy(), .alwayscopy(),
QueueRandom::new( QueueFolder::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5))).w_mouse(), GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5))).w_mouse(),
vec![], vec![],
QueueContent::Random(VecDeque::new()).into(), musicdb_lib::data::queue::QueueFolder {
false, index: 0,
) content: vec![],
.alwayscopy(), name: format!("folder name"),
QueueShuffle::new( order: None,
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(),
false, false,
) )
.alwayscopy(), .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 { Self {
config, config,
@ -111,6 +138,7 @@ impl QueueViewer {
vec![], vec![],
), ),
queue_updated: false, queue_updated: false,
recv,
} }
} }
} }
@ -145,6 +173,20 @@ impl GuiElem for QueueViewer {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { 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 { if self.queue_updated {
self.queue_updated = false; self.queue_updated = false;
let label = &mut self.c_duration; let label = &mut self.c_duration;
@ -254,17 +296,23 @@ fn queue_gui(
target_h.push(line_height * 1.75); 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 { if !skip_folder {
target.push(Box::new(QueueFolder::new( target.push(Box::new(QueueFolder::new(
cfg.clone(), cfg.clone(),
path.clone(), path.clone(),
queue.clone(), qf.clone(),
current, current,
))); )));
target_h.push(line_height * 0.8); 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(); let mut p = path.clone();
p.push(i); p.push(i);
queue_gui( queue_gui(
@ -314,62 +362,6 @@ fn queue_gui(
target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
target_h.push(line_height * 0.4); 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 { struct QueueFolder {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElem>>, c_name: Label,
path: Vec<usize>, path: Vec<usize>,
queue: Queue, queue: musicdb_lib::data::queue::QueueFolder,
current: bool, current: bool,
insert_into: bool, insert_into: bool,
mouse: bool, mouse: bool,
@ -650,7 +642,18 @@ struct QueueFolder {
copy_on_mouse_down: bool, copy_on_mouse_down: bool,
} }
impl QueueFolder { impl QueueFolder {
pub fn new(config: GuiElemCfg, path: Vec<usize>, queue: Queue, current: bool) -> Self { pub fn new(
config: GuiElemCfg,
path: Vec<usize>,
queue: musicdb_lib::data::queue::QueueFolder,
current: bool,
) -> Self {
let musicdb_lib::data::queue::QueueFolder {
index: _,
content,
name,
order,
} = &queue;
Self { Self {
config: if path.is_empty() { config: if path.is_empty() {
config config
@ -658,24 +661,22 @@ impl QueueFolder {
config.w_mouse().w_keyboard_watch() config.w_mouse().w_keyboard_watch()
} }
.w_drag_target(), .w_drag_target(),
children: vec![Box::new(Label::new( c_name: Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
match queue.content() { format!(
QueueContent::Folder(_, q, n) => format!( "{} ({}){}",
"{} ({})", if path.is_empty() && name.is_empty() {
if path.is_empty() && n.is_empty() { "Queue"
"Queue" } else {
} else { name
n },
}, content.len(),
q.len() if order.is_some() { " [shuffled]" } else { "" },
), ),
_ => "[???]".to_string(),
},
Color::from_int_rgb(52, 132, 50), Color::from_int_rgb(52, 132, 50),
None, None,
Vec2::new(0.0, 0.5), Vec2::new(0.0, 0.5),
))], ),
path, path,
queue, queue,
current, current,
@ -687,6 +688,12 @@ impl QueueFolder {
copy_on_mouse_down: false, 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 { impl GuiElem for QueueFolder {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
@ -696,7 +703,7 @@ impl GuiElem for QueueFolder {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
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 { fn any(&self) -> &dyn std::any::Any {
self self
@ -742,7 +749,7 @@ impl GuiElem for QueueFolder {
} }
if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) {
info.actions.push(GuiAction::SetDragging(Some(( info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()), Dragging::Queue(QueueContent::Folder(self.queue.clone()).into()),
None, None,
)))); ))));
} }
@ -751,6 +758,15 @@ impl GuiElem for QueueFolder {
if button == MouseButton::Left { if button == MouseButton::Left {
self.mouse = true; self.mouse = true;
self.copy_on_mouse_down = self.copy; 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![] vec![]
} }
@ -1007,259 +1023,6 @@ impl GuiElem for QueueLoop {
} }
} }
struct QueueRandom {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>,
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<usize>, 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<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
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<GuiAction> {
if button == MouseButton::Left {
self.mouse = true;
self.copy_on_mouse_down = self.copy;
}
vec![]
}
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
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<VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.copy = modifiers.ctrl();
vec![]
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
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<Box<dyn GuiElem>>,
path: Vec<usize>,
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<usize>, 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<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
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<GuiAction> {
if button == MouseButton::Left {
self.mouse = true;
self.copy_on_mouse_down = self.copy;
}
vec![]
}
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
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<VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.copy = modifiers.ctrl();
vec![]
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
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<F: FnOnce(Vec<Queue>) -> Command + 'static>( fn dragged_add_to_queue<F: FnOnce(Vec<Queue>) -> Command + 'static>(
dragged: Dragging, dragged: Dragging,
f: F, f: F,
@ -1297,15 +1060,16 @@ fn dragged_add_to_queue<F: FnOnce(Vec<Queue>) -> Command + 'static>(
fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> { fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> {
if let Some(album) = db.albums().get(&id) { if let Some(album) = db.albums().get(&id) {
Some( Some(
QueueContent::Folder( QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
0, index: 0,
album content: album
.songs .songs
.iter() .iter()
.map(|id| QueueContent::Song(*id).into()) .map(|id| QueueContent::Song(*id).into())
.collect(), .collect(),
album.name.clone(), name: album.name.clone(),
) order: None,
})
.into(), .into(),
) )
} else { } else {
@ -1315,9 +1079,9 @@ fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> {
fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> { fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
if let Some(artist) = db.artists().get(&id) { if let Some(artist) = db.artists().get(&id) {
Some( Some(
QueueContent::Folder( QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
0, index: 0,
artist content: artist
.singles .singles
.iter() .iter()
.map(|id| QueueContent::Song(*id).into()) .map(|id| QueueContent::Song(*id).into())
@ -1328,8 +1092,9 @@ fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
.filter_map(|id| add_to_queue_album_by_id(*id, db)), .filter_map(|id| add_to_queue_album_by_id(*id, db)),
) )
.collect(), .collect(),
artist.name.clone(), name: artist.name.clone(),
) order: None,
})
.into(), .into(),
) )
} else { } else {

View File

@ -1,6 +1,9 @@
use std::time::Instant; 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 speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
use crate::{ use crate::{
@ -117,9 +120,7 @@ impl GuiScreen {
musicdb_lib::server::Command::QueueUpdate( musicdb_lib::server::Command::QueueUpdate(
vec![], vec![],
musicdb_lib::data::queue::QueueContent::Folder( musicdb_lib::data::queue::QueueContent::Folder(
0, musicdb_lib::data::queue::QueueFolder::default(),
vec![],
String::new(),
) )
.into(), .into(),
), ),
@ -337,7 +338,7 @@ impl GuiElem for GuiScreen {
self.not_idle(); self.not_idle();
} }
if !(!info.database.playing 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 // skip idle_check if paused or queue is empty
self.idle_check(); self.idle_check();

View File

@ -19,6 +19,7 @@ pub struct CacheManager {
/// Amount of bytes. If free system memory is greater than this number, consider caching more songs. /// Amount of bytes. If free system memory is greater than this number, consider caching more songs.
pub max_avail_mem: Arc<AtomicU64>, pub max_avail_mem: Arc<AtomicU64>,
pub songs_to_cache: Arc<AtomicU32>, pub songs_to_cache: Arc<AtomicU32>,
#[allow(unused)]
thread: Arc<JoinHandle<()>>, thread: Arc<JoinHandle<()>>,
} }
@ -82,15 +83,9 @@ impl CacheManager {
} else { } else {
let mut queue = db.queue.clone(); let mut queue = db.queue.clone();
let mut actions = vec![];
let queue_current_song = queue.get_current_song().copied(); let queue_current_song = queue.get_current_song().copied();
queue.advance_index_inner(vec![], &mut actions); queue.advance_index_inner();
let queue_next_song = if actions.is_empty() { let queue_next_song = queue.get_current_song().copied();
queue.get_current_song().copied()
} else {
None
};
let mut ids_to_cache = queue_current_song let mut ids_to_cache = queue_current_song
.into_iter() .into_iter()
@ -98,10 +93,7 @@ impl CacheManager {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for _ in 2..songs_to_cache { for _ in 2..songs_to_cache {
queue.advance_index_inner(vec![], &mut actions); queue.advance_index_inner();
if !actions.is_empty() {
break;
}
if let Some(id) = queue.get_current_song() { if let Some(id) = queue.get_current_song() {
if !ids_to_cache.contains(id) { if !ids_to_cache.contains(id) {
ids_to_cache.push(*id); ids_to_cache.push(*id);

View File

@ -1,3 +1,4 @@
use rand::prelude::SliceRandom;
use std::{ use std::{
collections::{BTreeSet, HashMap}, collections::{BTreeSet, HashMap},
fs::{self, File}, fs::{self, File},
@ -8,13 +9,14 @@ use std::{
}; };
use colorize::AnsiColor; use colorize::AnsiColor;
use rand::thread_rng;
use crate::{load::ToFromBytes, server::Command}; use crate::{load::ToFromBytes, server::Command};
use super::{ use super::{
album::Album, album::Album,
artist::Artist, artist::Artist,
queue::{Queue, QueueContent, ShuffleState}, queue::{Queue, QueueContent, QueueFolder},
song::Song, song::Song,
AlbumId, ArtistId, CoverId, DatabaseLocation, SongId, AlbumId, ArtistId, CoverId, DatabaseLocation, SongId,
}; };
@ -515,8 +517,13 @@ impl Database {
t.clear(); t.clear();
} }
} }
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth // some commands shouldn't be broadcast. these will broadcast a different command in their specific implementation.
self.broadcast_update(&command); 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 { match command {
Command::Resume => self.playing = true, Command::Resume => self.playing = true,
Command::Pause => self.playing = false, Command::Pause => self.playing = false,
@ -525,9 +532,7 @@ impl Database {
if !Queue::advance_index_db(self) { if !Queue::advance_index_db(self) {
// end of queue // end of queue
self.apply_command(Command::Pause); self.apply_command(Command::Pause);
let mut actions = Vec::new(); self.queue.init();
self.queue.init(vec![], &mut actions);
Queue::handle_actions(self, actions);
} }
} }
Command::Save => { Command::Save => {
@ -537,51 +542,67 @@ impl Database {
} }
Command::SyncDatabase(a, b, c) => self.sync(a, b, c), Command::SyncDatabase(a, b, c) => self.sync(a, b, c),
Command::QueueUpdate(index, new_data) => { Command::QueueUpdate(index, new_data) => {
let mut actions = vec![]; if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) {
*v = new_data; *v = new_data;
} }
Queue::handle_actions(self, actions);
} }
Command::QueueAdd(index, new_data) => { Command::QueueAdd(index, new_data) => {
let mut actions = vec![]; if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { v.add_to_end(new_data);
v.add_to_end(new_data, index, &mut actions);
} }
Queue::handle_actions(self, actions);
} }
Command::QueueInsert(index, pos, new_data) => { Command::QueueInsert(index, pos, new_data) => {
let mut actions = vec![]; if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { v.insert(new_data, pos);
v.insert(new_data, pos, index, &mut actions);
} }
Queue::handle_actions(self, actions);
} }
Command::QueueRemove(index) => { Command::QueueRemove(index) => {
self.queue.remove_by_index(&index, 0); self.queue.remove_by_index(&index, 0);
} }
Command::QueueGoto(index) => Queue::set_index_db(self, &index), Command::QueueGoto(index) => Queue::set_index_db(self, &index),
Command::QueueSetShuffle(path, order) => { Command::QueueShuffle(path) => {
let mut actions = vec![]; if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) {
if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0, &mut actions) { if let QueueContent::Folder(QueueFolder {
if let QueueContent::Shuffle { inner, state } = elem.content_mut() { index: _,
if let QueueContent::Folder(_, v, _) = inner.content_mut() { content,
let mut o = std::mem::replace(v, vec![]) name: _,
.into_iter() order: _,
.map(|v| Some(v)) }) = elem.content_mut()
.collect::<Vec<_>>(); {
for &i in order.iter() { let mut ord: Vec<usize> = (0..content.len()).collect();
if let Some(a) = o.get_mut(i).and_then(Option::take) { ord.shuffle(&mut thread_rng());
v.push(a); self.apply_command(Command::QueueSetShuffle(path, ord));
} else { } 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()); 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 { } else {
eprintln!( 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 { } else {
@ -590,7 +611,22 @@ impl Database {
"WARN".yellow() "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) => { Command::AddSong(song) => {
self.add_song_new(song); self.add_song_new(song);
@ -745,7 +781,7 @@ impl Database {
songs: HashMap::new(), songs: HashMap::new(),
covers: HashMap::new(), covers: HashMap::new(),
custom_files: None, custom_files: None,
queue: QueueContent::Folder(0, vec![], String::new()).into(), queue: QueueContent::Folder(QueueFolder::default()).into(),
update_endpoints: vec![], update_endpoints: vec![],
playing: false, playing: false,
command_sender: None, command_sender: None,
@ -765,7 +801,7 @@ impl Database {
songs: HashMap::new(), songs: HashMap::new(),
covers: HashMap::new(), covers: HashMap::new(),
custom_files: None, custom_files: None,
queue: QueueContent::Folder(0, vec![], String::new()).into(), queue: QueueContent::Folder(QueueFolder::default()).into(),
update_endpoints: vec![], update_endpoints: vec![],
playing: false, playing: false,
command_sender: None, command_sender: None,
@ -790,7 +826,7 @@ impl Database {
songs: ToFromBytes::from_bytes(&mut file)?, songs: ToFromBytes::from_bytes(&mut file)?,
covers: ToFromBytes::from_bytes(&mut file)?, covers: ToFromBytes::from_bytes(&mut file)?,
custom_files: None, custom_files: None,
queue: QueueContent::Folder(0, vec![], String::new()).into(), queue: QueueContent::Folder(QueueFolder::default()).into(),
update_endpoints: vec![], update_endpoints: vec![],
playing: false, playing: false,
command_sender: None, command_sender: None,

View File

@ -1,8 +1,6 @@
use std::{collections::VecDeque, ops::AddAssign}; use std::ops::AddAssign;
use rand::seq::{IteratorRandom, SliceRandom}; use crate::load::ToFromBytes;
use crate::{load::ToFromBytes, server::Command};
use super::{database::Database, SongId}; use super::{database::Database, SongId};
@ -14,25 +12,15 @@ pub struct Queue {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum QueueContent { pub enum QueueContent {
Song(SongId), Song(SongId),
Folder(usize, Vec<Queue>, String), Folder(QueueFolder),
Loop(usize, usize, Box<Queue>), Loop(usize, usize, Box<Queue>),
Random(VecDeque<Queue>),
Shuffle {
inner: Box<Queue>,
state: ShuffleState,
},
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug, Default)]
pub enum ShuffleState { pub struct QueueFolder {
NotShuffled, pub index: usize,
Modified, pub content: Vec<Queue>,
Shuffled, pub name: String,
} pub order: Option<Vec<usize>>,
pub enum QueueAction {
AddRandomSong(Vec<usize>),
/// `partial: bool`, if true, indicates that we only shuffle what is beyond the current index
SetShuffle(Vec<usize>, bool),
} }
impl Queue { impl Queue {
@ -46,72 +34,18 @@ impl Queue {
&mut self.content &mut self.content
} }
pub fn add_to_end( pub fn add_to_end(&mut self, v: Vec<Self>) -> Option<usize> {
&mut self,
v: Vec<Self>,
mut path: Vec<usize>,
actions: &mut Vec<QueueAction>,
) -> Option<usize> {
match &mut self.content { match &mut self.content {
QueueContent::Song(_) => None, QueueContent::Song(_) => None,
QueueContent::Folder(_, vec, _) => { QueueContent::Folder(folder) => folder.add_to_end(v),
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::Loop(..) => None, 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( pub fn insert(&mut self, v: Vec<Self>, index: usize) -> bool {
&mut self,
v: Vec<Self>,
index: usize,
mut path: Vec<usize>,
actions: &mut Vec<QueueAction>,
) -> bool {
match &mut self.content { match &mut self.content {
QueueContent::Song(_) => false, QueueContent::Song(_) => false,
QueueContent::Folder(current, vec, _) => { QueueContent::Folder(folder) => folder.insert(v, index),
if index <= vec.len() { QueueContent::Loop(..) => false,
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
}
} }
} }
@ -121,16 +55,14 @@ impl Queue {
} }
match &self.content { match &self.content {
QueueContent::Song(_) => 1, QueueContent::Song(_) => 1,
QueueContent::Folder(_, v, _) => v.iter().map(|v| v.len()).sum(), QueueContent::Folder(folder) => folder.len(),
QueueContent::Random(v) => v.iter().map(|v| v.len()).sum(),
QueueContent::Loop(total, _done, inner) => { QueueContent::Loop(total, _done, inner) => {
if *total == 0 { if *total == 0 {
inner.len() inner.len()
} else { } else {
*total * inner.len() total.saturating_mul(inner.len())
} }
} }
QueueContent::Shuffle { inner, state: _ } => inner.len(),
} }
} }
pub fn duration_total(&self, db: &Database) -> QueueDuration { pub fn duration_total(&self, db: &Database) -> QueueDuration {
@ -150,9 +82,14 @@ impl Queue {
QueueContent::Song(v) => { QueueContent::Song(v) => {
dur.millis += db.get_song(v).map(|s| s.duration_millis).unwrap_or(0) dur.millis += db.get_song(v).map(|s| s.duration_millis).unwrap_or(0)
} }
QueueContent::Folder(c, inner, _) => { QueueContent::Folder(QueueFolder {
for (i, inner) in inner.iter().enumerate() { index,
if dur.include_past || i >= *c { content,
name: _,
order: _,
}) => {
for (i, inner) in content.iter().enumerate() {
if dur.include_past || i >= *index {
inner.add_duration(dur, db); 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> { pub fn get_current(&self) -> Option<&Self> {
match &self.content { match &self.content {
QueueContent::Song(_) => Some(self), QueueContent::Song(_) => Some(self),
QueueContent::Folder(i, v, _) => { QueueContent::Folder(folder) => folder.get_current_immut()?.get_current(),
let i = *i;
if let Some(v) = v.get(i) {
v.get_current()
} else {
None
}
}
QueueContent::Loop(_, _, inner) => inner.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> { pub fn get_current_song(&self) -> Option<&SongId> {
@ -222,22 +141,7 @@ impl Queue {
pub fn get_next(&self) -> Option<&Self> { pub fn get_next(&self) -> Option<&Self> {
match &self.content { match &self.content {
QueueContent::Song(_) => None, QueueContent::Song(_) => None,
QueueContent::Folder(i, vec, _) => { QueueContent::Folder(folder) => folder.get_next(),
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::Loop(total, current, inner) => { QueueContent::Loop(total, current, inner) => {
if let Some(v) = inner.get_next() { if let Some(v) = inner.get_next() {
Some(v) Some(v)
@ -247,156 +151,43 @@ impl Queue {
None 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> { pub fn get_first(&self) -> Option<&Self> {
match &self.content { match &self.content {
QueueContent::Song(..) => Some(self), QueueContent::Song(..) => Some(self),
QueueContent::Folder(_, v, _) => v.first(), QueueContent::Folder(folder) => folder.get_first(),
QueueContent::Loop(_, _, q) => q.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 { pub fn advance_index_db(db: &mut Database) -> bool {
let mut actions = vec![]; let o = db.queue.advance_index_inner();
let o = db.queue.advance_index_inner(vec![], &mut actions);
Self::handle_actions(db, actions);
o o
} }
pub fn init(&mut self, path: Vec<usize>, actions: &mut Vec<QueueAction>) { pub fn init(&mut self) {
match &mut self.content { match &mut self.content {
QueueContent::Song(..) => {} QueueContent::Song(..) => {}
QueueContent::Folder(i, v, _) => { QueueContent::Folder(folder) => {
*i = 0; folder.index = 0;
if let Some(v) = v.first_mut() { for v in &mut folder.content {
v.init( v.init();
{
let mut p = path.clone();
p.push(0);
p
},
actions,
);
} }
} }
QueueContent::Loop(_, _, inner) => inner.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);
}
} }
} }
pub fn handle_actions(db: &mut Database, actions: Vec<QueueAction>) { pub fn advance_index_inner(&mut self) -> bool {
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::<Vec<usize>>();
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<usize>,
actions: &mut Vec<QueueAction>,
) -> bool {
match &mut self.content { match &mut self.content {
QueueContent::Song(_) => false, QueueContent::Song(_) => false,
QueueContent::Folder(index, contents, _) => { QueueContent::Folder(folder) => folder.advance_index_inner(),
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::Loop(total, current, inner) => { QueueContent::Loop(total, current, inner) => {
path.push(0); if inner.advance_index_inner() {
if inner.advance_index_inner(path.clone(), actions) {
true true
} else { } else {
*current += 1; *current += 1;
if *total == 0 || *current < *total { if *total == 0 || *current < *total {
inner.init(path, actions); inner.init();
true true
} else { } else {
*current = 0; *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<usize>) { pub fn set_index_db(db: &mut Database, index: &Vec<usize>) {
let mut actions = vec![];
db.queue.reset_index(); db.queue.reset_index();
db.queue.set_index_inner(index, 0, vec![], &mut actions); db.queue.set_index_inner(index, 0, vec![]);
Self::handle_actions(db, actions);
} }
pub fn set_index_inner( pub fn set_index_inner(
&mut self, &mut self,
index: &Vec<usize>, index: &Vec<usize>,
depth: usize, depth: usize,
mut build_index: Vec<usize>, mut build_index: Vec<usize>,
actions: &mut Vec<QueueAction>,
) { ) {
let i = if let Some(i) = index.get(depth) { let i = if let Some(i) = index.get(depth) {
*i *i
@ -463,32 +216,25 @@ impl Queue {
build_index.push(i); build_index.push(i);
match &mut self.content { match &mut self.content {
QueueContent::Song(_) => {} QueueContent::Song(_) => {}
QueueContent::Folder(idx, contents, _) => { QueueContent::Folder(folder) => {
if i != *idx { folder.index = i;
*idx = i; if let Some(c) = folder.get_current_mut() {
} c.init();
if let Some(c) = contents.get_mut(i) { c.set_index_inner(index, depth + 1, build_index);
c.init(build_index.clone(), actions);
c.set_index_inner(index, depth + 1, build_index, actions);
} }
} }
QueueContent::Loop(_, _, inner) => { QueueContent::Loop(_, _, inner) => {
inner.init(build_index.clone(), actions); inner.init();
inner.set_index_inner(index, depth + 1, build_index, actions) inner.set_index_inner(index, depth + 1, build_index)
}
QueueContent::Random(_) => {}
QueueContent::Shuffle { inner, state: _ } => {
inner.init(build_index.clone(), actions);
inner.set_index_inner(index, depth + 1, build_index, actions)
} }
} }
} }
pub fn reset_index(&mut self) { pub fn reset_index(&mut self) {
match self.content_mut() { match self.content_mut() {
QueueContent::Song(_) => {} QueueContent::Song(_) => {}
QueueContent::Folder(i, v, _) => { QueueContent::Folder(folder) => {
*i = 0; folder.index = 0;
for v in v { for v in &mut folder.content {
v.reset_index(); v.reset_index();
} }
} }
@ -496,10 +242,6 @@ impl Queue {
*done = 0; *done = 0;
i.reset_index(); 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) { if let Some(i) = index.get(depth) {
match &self.content { match &self.content {
QueueContent::Song(_) => None, QueueContent::Song(_) => None,
QueueContent::Folder(_, v, _) => { QueueContent::Folder(folder) => {
if let Some(v) = v.get(*i) { if let Some(v) = folder.get_at(*i) {
v.get_item_at_index(index, depth + 1) v.get_item_at_index(index, depth + 1)
} else { } else {
None None
} }
} }
QueueContent::Loop(_, _, inner) => inner.get_item_at_index(index, depth + 1), 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 { } else {
Some(self) Some(self)
} }
} }
pub fn get_item_at_index_mut( pub fn get_item_at_index_mut(&mut self, index: &Vec<usize>, depth: usize) -> Option<&mut Self> {
&mut self,
index: &Vec<usize>,
depth: usize,
actions: &mut Vec<QueueAction>,
) -> Option<&mut Self> {
if let Some(i) = index.get(depth) { if let Some(i) = index.get(depth) {
match &mut self.content { match &mut self.content {
QueueContent::Song(_) => None, QueueContent::Song(_) => None,
QueueContent::Folder(_, v, _) => { QueueContent::Folder(folder) => {
if let Some(v) = v.get_mut(*i) { if let Some(v) = folder.get_mut_at(*i) {
v.get_item_at_index_mut(index, depth + 1, actions) v.get_item_at_index_mut(index, depth + 1)
} else { } else {
None None
} }
} }
QueueContent::Loop(_, _, inner) => { QueueContent::Loop(_, _, inner) => inner.get_item_at_index_mut(index, depth + 1),
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)
}
} }
} else { } else {
Some(self) Some(self)
@ -572,21 +284,33 @@ impl Queue {
if let Some(i) = index.get(depth) { if let Some(i) = index.get(depth) {
match &mut self.content { match &mut self.content {
QueueContent::Song(_) => None, QueueContent::Song(_) => None,
QueueContent::Folder(ci, v, _) => { QueueContent::Folder(folder) => {
if depth + 1 < index.len() { 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) v.remove_by_index(index, depth + 1)
} else { } else {
None None
} }
} else { } else {
if *i < v.len() { if *i < folder.content.len() {
// if current playback is past this point, // if current playback is past this point,
// reduce the index by 1 so that it still points to the same element // reduce the index by 1 so that it still points to the same element
if *ci > *i { if folder.index > *i {
*ci -= 1; 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 { } else {
None None
} }
@ -599,15 +323,148 @@ impl Queue {
None None
} }
} }
QueueContent::Random(v) => v.remove(*i), }
QueueContent::Shuffle { inner, state: _ } => { } else {
inner.remove_by_index(index, depth + 1) None
}
}
}
impl QueueFolder {
pub fn iter(&self) -> QueueFolderIter {
QueueFolderIter {
folder: self,
index: 0,
}
}
pub fn add_to_end(&mut self, v: Vec<Queue>) -> Option<usize> {
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<Queue>, index: usize) -> bool {
if index <= self.content.len() {
if self.index >= index {
self.index += v.len();
}
fn insert_multiple<T>(index: usize, vec: &mut Vec<T>, v: impl IntoIterator<Item = T>) {
// 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 { } else {
None 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<Self::Item> {
if let Some(v) = self.folder.get_at(self.index) {
self.index += 1;
Some(v)
} else {
None
}
}
} }
impl From<QueueContent> for Queue { impl From<QueueContent> for Queue {
@ -651,11 +508,9 @@ impl ToFromBytes for QueueContent {
s.write_all(&[0b11111111])?; s.write_all(&[0b11111111])?;
id.to_bytes(s)?; id.to_bytes(s)?;
} }
Self::Folder(index, contents, name) => { Self::Folder(folder) => {
s.write_all(&[0b00000000])?; s.write_all(&[0b00000000])?;
index.to_bytes(s)?; ToFromBytes::to_bytes(folder, s)?;
contents.to_bytes(s)?;
name.to_bytes(s)?;
} }
Self::Loop(total, current, inner) => { Self::Loop(total, current, inner) => {
s.write_all(&[0b11000000])?; s.write_all(&[0b11000000])?;
@ -663,15 +518,6 @@ impl ToFromBytes for QueueContent {
current.to_bytes(s)?; current.to_bytes(s)?;
inner.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(()) Ok(())
} }
@ -683,54 +529,43 @@ impl ToFromBytes for QueueContent {
s.read_exact(&mut switch_on)?; s.read_exact(&mut switch_on)?;
Ok(match switch_on[0] { Ok(match switch_on[0] {
0b11111111 => Self::Song(ToFromBytes::from_bytes(s)?), 0b11111111 => Self::Song(ToFromBytes::from_bytes(s)?),
0b00000000 => Self::Folder( 0b00000000 => Self::Folder(ToFromBytes::from_bytes(s)?),
ToFromBytes::from_bytes(s)?,
ToFromBytes::from_bytes(s)?,
ToFromBytes::from_bytes(s)?,
),
0b11000000 => Self::Loop( 0b11000000 => Self::Loop(
ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?,
ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?,
Box::new(ToFromBytes::from_bytes(s)?), Box::new(ToFromBytes::from_bytes(s)?),
), ),
0b00110000 => Self::Random(ToFromBytes::from_bytes(s)?), _ => Self::Folder(QueueFolder {
0b00001100 => Self::Shuffle { index: 0,
inner: Box::new(ToFromBytes::from_bytes(s)?), content: vec![],
state: ToFromBytes::from_bytes(s)?, name: "<invalid byte received>".to_string(),
}, order: None,
_ => Self::Folder(0, vec![], "<invalid byte received>".to_string()), }),
}) })
} }
} }
impl ToFromBytes for ShuffleState { impl ToFromBytes for QueueFolder {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error> fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where where
T: std::io::Write, T: std::io::prelude::Write,
{ {
s.write_all(&[match self { ToFromBytes::to_bytes(&self.index, s)?;
Self::NotShuffled => 1, ToFromBytes::to_bytes(&self.content, s)?;
Self::Modified => 2, ToFromBytes::to_bytes(&self.name, s)?;
Self::Shuffled => 4, ToFromBytes::to_bytes(&self.order, s)?;
}]) Ok(())
} }
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error> fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
where where
T: std::io::Read, T: std::io::prelude::Read,
{ {
let mut b = [0]; let v = Self {
s.read_exact(&mut b)?; index: ToFromBytes::from_bytes(s)?,
Ok(match b[0] { content: ToFromBytes::from_bytes(s)?,
1 => Self::NotShuffled, name: ToFromBytes::from_bytes(s)?,
2 => Self::Modified, order: ToFromBytes::from_bytes(s)?,
4 => Self::Shuffled, };
_ => { Ok(v)
eprintln!(
"[warn] received {} as ShuffleState, which is invalid. defaulting to Shuffled.",
b[0]
);
Self::Shuffled
}
})
} }
} }

View File

@ -118,7 +118,7 @@ where
match self { match self {
None => s.write_all(&[0b11001100]), None => s.write_all(&[0b11001100]),
Some(v) => { Some(v) => {
s.write_all(&[0b00111010])?; s.write_all(&[0b00110011])?;
v.to_bytes(s) v.to_bytes(s)
} }
} }
@ -129,10 +129,11 @@ where
{ {
let mut b = [0u8]; let mut b = [0u8];
s.read_exact(&mut b)?; s.read_exact(&mut b)?;
match b[0] { Ok(if (b[0] ^ 0b11001100).count_ones() > 4 {
0b00111010 => Ok(Some(ToFromBytes::from_bytes(s)?)), Some(ToFromBytes::from_bytes(s)?)
_ => Ok(None), } else {
} None
})
} }
} }
impl<K, V> ToFromBytes for HashMap<K, V> impl<K, V> ToFromBytes for HashMap<K, V>

View File

@ -40,7 +40,11 @@ pub enum Command {
QueueInsert(Vec<usize>, usize, Vec<Queue>), QueueInsert(Vec<usize>, usize, Vec<Queue>),
QueueRemove(Vec<usize>), QueueRemove(Vec<usize>),
QueueGoto(Vec<usize>), QueueGoto(Vec<usize>),
// sent by clients when they want to shuffle a folder
QueueShuffle(Vec<usize>),
// sent by the server when the folder was shuffled
QueueSetShuffle(Vec<usize>, Vec<usize>), QueueSetShuffle(Vec<usize>, Vec<usize>),
QueueUnshuffle(Vec<usize>),
/// .id field is ignored! /// .id field is ignored!
AddSong(Song), AddSong(Song),
@ -256,47 +260,57 @@ pub fn handle_one_connection_as_control(
} }
} }
const BYTE_RESUME: u8 = 0b11000000; // 01_***_*** => Simple commands
const BYTE_PAUSE: u8 = 0b00110000; // 01_00*_*** => Playback
const BYTE_STOP: u8 = 0b11110000; // 01_010_*** => Other
const BYTE_NEXT_SONG: u8 = 0b11110010; // 01_100_*** => Errors
// 10_***_*** => Complicated commands
// 10_00*_*** => Queue
// 10_010_*** => Misc
// 10_100_*** => Library
const BYTE_SYNC_DATABASE: u8 = 0b01011000; const BYTE_RESUME: u8 = 0b01_000_000;
const BYTE_QUEUE_UPDATE: u8 = 0b00011100; const BYTE_PAUSE: u8 = 0b01_000_001;
const BYTE_QUEUE_ADD: u8 = 0b00011010; const BYTE_STOP: u8 = 0b01_000_010;
const BYTE_QUEUE_INSERT: u8 = 0b00011110; const BYTE_NEXT_SONG: u8 = 0b01_000_100;
const BYTE_QUEUE_REMOVE: u8 = 0b00011001;
const BYTE_QUEUE_GOTO: u8 = 0b00011011;
const BYTE_QUEUE_SET_SHUFFLE: u8 = 0b10011011;
const BYTE_ADD_SONG: u8 = 0b01010000; const BYTE_INIT_COMPLETE: u8 = 0b01_010_000;
const BYTE_ADD_ALBUM: u8 = 0b01010011; const BYTE_SET_SONG_DURATION: u8 = 0b01_010_001;
const BYTE_ADD_ARTIST: u8 = 0b01011100; const BYTE_SAVE: u8 = 0b01_010_010;
const BYTE_ADD_COVER: u8 = 0b01011101; const BYTE_ERRORINFO: u8 = 0b01_100_010;
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_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_SYNC_DATABASE: u8 = 0b10_010_100;
const BYTE_SAVE: u8 = 0b11110011;
const BYTE_ERRORINFO: u8 = 0b11011011; 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 { impl ToFromBytes for Command {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error> fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
@ -338,111 +352,144 @@ impl ToFromBytes for Command {
s.write_all(&[BYTE_QUEUE_GOTO])?; s.write_all(&[BYTE_QUEUE_GOTO])?;
index.to_bytes(s)?; 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) => { 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)?; path.to_bytes(s)?;
map.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) => { Self::AddSong(song) => {
s.write_all(&[BYTE_ADD_SONG])?; s.write_all(&[BYTE_LIB_ADD])?;
s.write_all(&[SUBBYTE_SONG])?;
song.to_bytes(s)?; song.to_bytes(s)?;
} }
Self::AddAlbum(album) => { Self::AddAlbum(album) => {
s.write_all(&[BYTE_ADD_ALBUM])?; s.write_all(&[BYTE_LIB_ADD])?;
s.write_all(&[SUBBYTE_ALBUM])?;
album.to_bytes(s)?; album.to_bytes(s)?;
} }
Self::AddArtist(artist) => { Self::AddArtist(artist) => {
s.write_all(&[BYTE_ADD_ARTIST])?; s.write_all(&[BYTE_LIB_ADD])?;
s.write_all(&[SUBBYTE_ARTIST])?;
artist.to_bytes(s)?; artist.to_bytes(s)?;
} }
Self::AddCover(cover) => { Self::AddCover(cover) => {
s.write_all(&[BYTE_ADD_COVER])?; s.write_all(&[BYTE_LIB_ADD])?;
s.write_all(&[SUBBYTE_COVER])?;
cover.to_bytes(s)?; cover.to_bytes(s)?;
} }
Self::ModifySong(song) => { Self::ModifySong(song) => {
s.write_all(&[BYTE_MODIFY_SONG])?; s.write_all(&[BYTE_LIB_MODIFY])?;
s.write_all(&[SUBBYTE_SONG])?;
song.to_bytes(s)?; song.to_bytes(s)?;
} }
Self::ModifyAlbum(album) => { Self::ModifyAlbum(album) => {
s.write_all(&[BYTE_MODIFY_ALBUM])?; s.write_all(&[BYTE_LIB_MODIFY])?;
s.write_all(&[SUBBYTE_ALBUM])?;
album.to_bytes(s)?; album.to_bytes(s)?;
} }
Self::ModifyArtist(artist) => { Self::ModifyArtist(artist) => {
s.write_all(&[BYTE_MODIFY_ARTIST])?; s.write_all(&[BYTE_LIB_MODIFY])?;
s.write_all(&[SUBBYTE_ARTIST])?;
artist.to_bytes(s)?; artist.to_bytes(s)?;
} }
Self::RemoveSong(song) => { Self::RemoveSong(song) => {
s.write_all(&[BYTE_REMOVE_SONG])?; s.write_all(&[BYTE_LIB_REMOVE])?;
s.write_all(&[SUBBYTE_SONG])?;
song.to_bytes(s)?; song.to_bytes(s)?;
} }
Self::RemoveAlbum(album) => { Self::RemoveAlbum(album) => {
s.write_all(&[BYTE_REMOVE_ALBUM])?; s.write_all(&[BYTE_LIB_REMOVE])?;
s.write_all(&[SUBBYTE_ALBUM])?;
album.to_bytes(s)?; album.to_bytes(s)?;
} }
Self::RemoveArtist(artist) => { Self::RemoveArtist(artist) => {
s.write_all(&[BYTE_REMOVE_ARTIST])?; s.write_all(&[BYTE_LIB_REMOVE])?;
s.write_all(&[SUBBYTE_ARTIST])?;
artist.to_bytes(s)?; artist.to_bytes(s)?;
} }
Self::TagSongFlagSet(id, tag) => { 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)?; id.to_bytes(s)?;
tag.to_bytes(s)?; tag.to_bytes(s)?;
} }
Self::TagSongFlagUnset(id, tag) => { 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)?; id.to_bytes(s)?;
tag.to_bytes(s)?; tag.to_bytes(s)?;
} }
Self::TagAlbumFlagSet(id, tag) => { 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)?; id.to_bytes(s)?;
tag.to_bytes(s)?; tag.to_bytes(s)?;
} }
Self::TagAlbumFlagUnset(id, tag) => { 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)?; id.to_bytes(s)?;
tag.to_bytes(s)?; tag.to_bytes(s)?;
} }
Self::TagArtistFlagSet(id, tag) => { 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)?; id.to_bytes(s)?;
tag.to_bytes(s)?; tag.to_bytes(s)?;
} }
Self::TagArtistFlagUnset(id, tag) => { 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)?; id.to_bytes(s)?;
tag.to_bytes(s)?; tag.to_bytes(s)?;
} }
Self::TagSongPropertySet(id, key, val) => { 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)?; id.to_bytes(s)?;
key.to_bytes(s)?; key.to_bytes(s)?;
val.to_bytes(s)?; val.to_bytes(s)?;
} }
Self::TagSongPropertyUnset(id, key) => { 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)?; id.to_bytes(s)?;
key.to_bytes(s)?; key.to_bytes(s)?;
} }
Self::TagAlbumPropertySet(id, key, val) => { 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)?; id.to_bytes(s)?;
key.to_bytes(s)?; key.to_bytes(s)?;
val.to_bytes(s)?; val.to_bytes(s)?;
} }
Self::TagAlbumPropertyUnset(id, key) => { 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)?; id.to_bytes(s)?;
key.to_bytes(s)?; key.to_bytes(s)?;
} }
Self::TagArtistPropertySet(id, key, val) => { 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)?; id.to_bytes(s)?;
key.to_bytes(s)?; key.to_bytes(s)?;
val.to_bytes(s)?; val.to_bytes(s)?;
} }
Self::TagArtistPropertyUnset(id, key) => { 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)?; id.to_bytes(s)?;
key.to_bytes(s)?; key.to_bytes(s)?;
} }
@ -467,14 +514,12 @@ impl ToFromBytes for Command {
where where
T: std::io::Read, T: std::io::Read,
{ {
let mut kind = [0];
s.read_exact(&mut kind)?;
macro_rules! from_bytes { macro_rules! from_bytes {
() => { () => {
ToFromBytes::from_bytes(s)? ToFromBytes::from_bytes(s)?
}; };
} }
Ok(match kind[0] { Ok(match s.read_byte()? {
BYTE_RESUME => Self::Resume, BYTE_RESUME => Self::Resume,
BYTE_PAUSE => Self::Pause, BYTE_PAUSE => Self::Pause,
BYTE_STOP => Self::Stop, 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_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()),
BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()), BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()),
BYTE_QUEUE_GOTO => Self::QueueGoto(from_bytes!()), BYTE_QUEUE_GOTO => Self::QueueGoto(from_bytes!()),
BYTE_QUEUE_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()), BYTE_QUEUE_ACTION => match s.read_byte()? {
BYTE_ADD_SONG => Self::AddSong(from_bytes!()), SUBBYTE_ACTION_SHUFFLE => Self::QueueShuffle(from_bytes!()),
BYTE_ADD_ALBUM => Self::AddAlbum(from_bytes!()), SUBBYTE_ACTION_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()),
BYTE_ADD_ARTIST => Self::AddArtist(from_bytes!()), SUBBYTE_ACTION_UNSHUFFLE => Self::QueueUnshuffle(from_bytes!()),
BYTE_MODIFY_SONG => Self::ModifySong(from_bytes!()), _ => {
BYTE_MODIFY_ALBUM => Self::ModifyAlbum(from_bytes!()), eprintln!(
BYTE_MODIFY_ARTIST => Self::ModifyArtist(from_bytes!()), "[{}] unexpected byte when reading command:queueAction; stopping playback.",
BYTE_REMOVE_SONG => Self::RemoveSong(from_bytes!()), "WARN".yellow()
BYTE_REMOVE_ALBUM => Self::RemoveAlbum(from_bytes!()), );
BYTE_REMOVE_ARTIST => Self::RemoveArtist(from_bytes!()), Self::Stop
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_LIB_ADD => match s.read_byte()? {
BYTE_TAG_ALBUM_FLAG_UNSET => Self::TagAlbumFlagUnset(from_bytes!(), from_bytes!()), SUBBYTE_SONG => Self::AddSong(from_bytes!()),
BYTE_TAG_ARTIST_FLAG_SET => Self::TagArtistFlagSet(from_bytes!(), from_bytes!()), SUBBYTE_ALBUM => Self::AddAlbum(from_bytes!()),
BYTE_TAG_ARTIST_FLAG_UNSET => Self::TagArtistFlagUnset(from_bytes!(), from_bytes!()), SUBBYTE_ARTIST => Self::AddArtist(from_bytes!()),
BYTE_TAG_SONG_PROPERTY_SET => { SUBBYTE_COVER => Self::AddCover(from_bytes!()),
Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) _ => {
} eprintln!(
BYTE_TAG_SONG_PROPERTY_UNSET => { "[{}] unexpected byte when reading command:libAdd; stopping playback.",
Self::TagSongPropertyUnset(from_bytes!(), from_bytes!()) "WARN".yellow()
} );
BYTE_TAG_ALBUM_PROPERTY_SET => { Self::Stop
Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) }
} },
BYTE_TAG_ALBUM_PROPERTY_UNSET => { BYTE_LIB_MODIFY => match s.read_byte()? {
Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!()) SUBBYTE_SONG => Self::ModifySong(from_bytes!()),
} SUBBYTE_ALBUM => Self::ModifyAlbum(from_bytes!()),
BYTE_TAG_ARTIST_PROPERTY_SET => { SUBBYTE_ARTIST => Self::ModifyArtist(from_bytes!()),
Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) _ => {
} eprintln!(
BYTE_TAG_ARTIST_PROPERTY_UNSET => { "[{}] unexpected byte when reading command:libModify; stopping playback.",
Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!()) "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_SET_SONG_DURATION => Self::SetSongDuration(from_bytes!(), from_bytes!()),
BYTE_ADD_COVER => Self::AddCover(from_bytes!()),
BYTE_INIT_COMPLETE => Self::InitComplete, BYTE_INIT_COMPLETE => Self::InitComplete,
BYTE_SAVE => Self::Save, BYTE_SAVE => Self::Save,
BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()), 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<u8>;
}
impl<T: Read> ReadByte for T {
fn read_byte(&mut self) -> std::io::Result<u8> {
let mut b = [0];
self.read_exact(&mut b)?;
Ok(b[0])
}
}

View File

@ -13,7 +13,7 @@ use musicdb_lib::{
album::Album, album::Album,
artist::Artist, artist::Artist,
database::Database, database::Database,
queue::{Queue, QueueContent}, queue::{Queue, QueueContent, QueueFolder},
song::Song, song::Song,
}, },
server::Command, server::Command,
@ -88,7 +88,9 @@ pub fn add(
| Command::QueueInsert(..) | Command::QueueInsert(..)
| Command::QueueRemove(..) | Command::QueueRemove(..)
| Command::QueueGoto(..) | Command::QueueGoto(..)
| Command::QueueSetShuffle(..) => { | Command::QueueShuffle(..)
| Command::QueueSetShuffle(..)
| Command::QueueUnshuffle(..) => {
handle(&handler_queue_changed, move || (Data::empty_tuple(), ())); handle(&handler_queue_changed, move || (Data::empty_tuple(), ()));
} }
Command::AddSong(_) Command::AddSong(_)
@ -409,7 +411,7 @@ pub fn add(
move |_, _| { move |_, _| {
cmd(Command::QueueUpdate( cmd(Command::QueueUpdate(
vec![], vec![],
QueueContent::Folder(0, vec![], String::new()).into(), QueueContent::Folder(QueueFolder::default()).into(),
)); ));
Data::empty_tuple() Data::empty_tuple()
} }
@ -479,7 +481,7 @@ pub fn add(
vec![QueueContent::Loop( vec![QueueContent::Loop(
repeat_count.max(0) as _, repeat_count.max(0) as _,
0, 0,
Box::new(QueueContent::Folder(0, vec![], String::new()).into()), Box::new(QueueContent::Folder(QueueFolder::default()).into()),
) )
.into()], .into()],
)); ));
@ -518,60 +520,12 @@ pub fn add(
.clone(); .clone();
cmd(Command::QueueAdd( cmd(Command::QueueAdd(
path, path,
vec![QueueContent::Folder(0, vec![], name).into()], vec![QueueContent::Folder(QueueFolder {
)); index: 0,
Data::empty_tuple() content: vec![],
} name,
} order: None,
), })
)
.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<Int>`.").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<Int>`.").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,
}
.into()], .into()],
)); ));
Data::empty_tuple() 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 _))), ("done".to_owned(), Data::new(data::int::Int(*done as _))),
])), ])),
), ),
QueueContent::Random(_) => ("random".to_owned(), Data::empty_tuple()), QueueContent::Folder(folder) => (
QueueContent::Folder(index, inner, name) => (
"folder".to_owned(), "folder".to_owned(),
Data::new(data::object::Object(vec![ 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(), "length".to_owned(),
Data::new(data::int::Int(inner.len() as _)), Data::new(data::int::Int(folder.content.len() as _)),
), ),
( (
"name".to_owned(), "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())
}
}, },
])) ]))
} }

View File

@ -2,6 +2,7 @@
<small>&gt;&gt;</small> <small>&gt;&gt;</small>
<button hx-post="/queue/goto/\:path" hx-swap="none">&#x23F5;</button> <button hx-post="/queue/goto/\:path" hx-swap="none">&#x23F5;</button>
<small>\:name</small> <small>\:name</small>
\?shuffled? (shuffled)\;\;
</div> </div>
\:content \:content
<div> <div>

View File

@ -2,6 +2,7 @@
<small>&gt;&gt;</small> <small>&gt;&gt;</small>
<button hx-post="/queue/goto/\:path" hx-swap="none">&#x23F5;</button> <button hx-post="/queue/goto/\:path" hx-swap="none">&#x23F5;</button>
<small><b>\:name</b></small> <small><b>\:name</b></small>
\?shuffled? (shuffled)\;\;
</div> </div>
\:content \:content
<div> <div>

View File

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::convert::Infallible; use std::convert::Infallible;
use std::mem; use std::mem;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -12,7 +13,7 @@ use axum::routing::{get, post};
use axum::{Router, TypedHeader}; use axum::{Router, TypedHeader};
use futures::{stream, Stream}; use futures::{stream, Stream};
use musicdb_lib::data::database::{Database, UpdateEndpoint}; 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 musicdb_lib::server::Command;
use tokio_stream::StreamExt as _; use tokio_stream::StreamExt as _;
@ -75,8 +76,10 @@ pub struct AppHtml {
/// can use: path, title /// can use: path, title
queue_song_current: Vec<HtmlPart>, queue_song_current: Vec<HtmlPart>,
/// can use: path, content, name /// can use: path, content, name
/// can use in `\?key?then\;else\;`: `shuffled`
queue_folder: Vec<HtmlPart>, queue_folder: Vec<HtmlPart>,
/// can use: path, content, name /// can use: path, content, name
/// can use in `\?key?then\;else\;`: `shuffled`
queue_folder_current: Vec<HtmlPart>, queue_folder_current: Vec<HtmlPart>,
/// can use: path, total, current, inner /// can use: path, total, current, inner
queue_loop: Vec<HtmlPart>, queue_loop: Vec<HtmlPart>,
@ -86,14 +89,6 @@ pub struct AppHtml {
queue_loopinf: Vec<HtmlPart>, queue_loopinf: Vec<HtmlPart>,
/// can use: path, current, inner /// can use: path, current, inner
queue_loopinf_current: Vec<HtmlPart>, queue_loopinf_current: Vec<HtmlPart>,
/// can use: path, content
queue_random: Vec<HtmlPart>,
/// can use: path, content
queue_random_current: Vec<HtmlPart>,
/// can use: path, content
queue_shuffle: Vec<HtmlPart>,
/// can use: path, content
queue_shuffle_current: Vec<HtmlPart>,
} }
impl AppHtml { impl AppHtml {
pub fn from_dir<P: AsRef<std::path::Path>>(dir: P) -> std::io::Result<Self> { pub fn from_dir<P: AsRef<std::path::Path>>(dir: P) -> std::io::Result<Self> {
@ -123,22 +118,24 @@ impl AppHtml {
queue_loopinf_current: Self::parse(&std::fs::read_to_string( queue_loopinf_current: Self::parse(&std::fs::read_to_string(
dir.join("queue_loopinf_current.html"), 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<HtmlPart> { pub fn parse(s: &str) -> Vec<HtmlPart> {
Self::parsei(&mut s.chars().peekable())
}
pub fn parsei(chars: &mut std::iter::Peekable<impl Iterator<Item = char>>) -> Vec<HtmlPart> {
let mut o = Vec::new(); let mut o = Vec::new();
let mut c = String::new(); let mut c = String::new();
let mut chars = s.chars().peekable();
loop { 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 == ':') { if ch == '\\' && chars.peek().is_some_and(|ch| *ch == ':') {
chars.next(); chars.next();
o.push(HtmlPart::Plain(mem::replace(&mut c, String::new()))); o.push(HtmlPart::Plain(mem::replace(&mut c, String::new())));
@ -158,6 +155,20 @@ impl AppHtml {
return o; 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 { } else {
c.push(ch); c.push(ch);
} }
@ -176,6 +187,8 @@ pub enum HtmlPart {
Plain(String), Plain(String),
/// insert some value depending on context and key /// insert some value depending on context and key
Insert(String), Insert(String),
/// If the key exists, use the first one, else use the second one.
IfElse(String, Vec<Self>, Vec<Self>),
} }
pub async fn main(db: Arc<Mutex<Database>>, sender: mpsc::Sender<Command>, addr: SocketAddr) { pub async fn main(db: Arc<Mutex<Database>>, sender: mpsc::Sender<Command>, addr: SocketAddr) {
@ -210,6 +223,7 @@ pub async fn main(db: Arc<Mutex<Database>>, sender: mpsc::Sender<Command>, addr:
.map(|v| match v { .map(|v| match v {
HtmlPart::Plain(v) => v, HtmlPart::Plain(v) => v,
HtmlPart::Insert(_) => "", HtmlPart::Insert(_) => "",
HtmlPart::IfElse(_, _, _) => "",
}) })
.collect::<String>(), .collect::<String>(),
) )
@ -250,7 +264,7 @@ pub async fn main(db: Arc<Mutex<Database>>, sender: mpsc::Sender<Command>, addr:
post(move || async move { post(move || async move {
_ = s5.send(Command::QueueUpdate( _ = s5.send(Command::QueueUpdate(
vec![], vec![],
QueueContent::Folder(0, vec![], String::new()).into(), QueueContent::Folder(QueueFolder::default()).into(),
)); ));
}), }),
) )
@ -297,15 +311,16 @@ pub async fn main(db: Arc<Mutex<Database>>, sender: mpsc::Sender<Command>, addr:
if let Some(album) = db1.lock().unwrap().albums().get(&album_id) { if let Some(album) = db1.lock().unwrap().albums().get(&album_id) {
_ = s7.send(Command::QueueAdd( _ = s7.send(Command::QueueAdd(
vec![], vec![],
vec![QueueContent::Folder( vec![QueueContent::Folder(QueueFolder {
0, index: 0,
album content: album
.songs .songs
.iter() .iter()
.map(|id| QueueContent::Song(*id).into()) .map(|id| QueueContent::Song(*id).into())
.collect(), .collect(),
album.name.clone(), name: album.name.clone(),
) order: None,
})
.into()], .into()],
)); ));
} }
@ -371,17 +386,26 @@ async fn sse_handler(
let mut a = db.artists().iter().collect::<Vec<_>>(); let mut a = db.artists().iter().collect::<Vec<_>>();
a.sort_unstable_by_key(|(_id, artist)| &artist.name); a.sort_unstable_by_key(|(_id, artist)| &artist.name);
let mut artists = String::new(); let mut artists = String::new();
for (id, artist) in a { for &(id, artist) in &a {
for v in &state.html.artists_one { fn gen(
match v { id: &u64,
HtmlPart::Plain(v) => artists.push_str(v), artist: &musicdb_lib::data::artist::Artist,
HtmlPart::Insert(key) => match key.as_str() { v: &Vec<HtmlPart>,
"id" => artists.push_str(&id.to_string()), arts: &mut String,
"name" => artists.push_str(&artist.name), ) {
_ => {} 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 state
.html .html
@ -393,6 +417,7 @@ async fn sse_handler(
"artists" => &artists, "artists" => &artists,
_ => "", _ => "",
}, },
HtmlPart::IfElse(_, _, _) => "",
}) })
.collect::<String>() .collect::<String>()
}), }),
@ -402,7 +427,9 @@ async fn sse_handler(
| Command::QueueInsert(..) | Command::QueueInsert(..)
| Command::QueueRemove(..) | Command::QueueRemove(..)
| Command::QueueGoto(..) | Command::QueueGoto(..)
| Command::QueueSetShuffle(..) => { | Command::QueueShuffle(..)
| Command::QueueSetShuffle(..)
| Command::QueueUnshuffle(..) => {
let db = state.db.lock().unwrap(); let db = state.db.lock().unwrap();
let current = db let current = db
.queue .queue
@ -422,34 +449,48 @@ async fn sse_handler(
true, true,
false, false,
); );
Event::default().event("queue").data( Event::default().event("queue").data({
state fn gen(
.html v: &Vec<HtmlPart>,
.queue current: Option<&musicdb_lib::data::song::Song>,
.iter() next: Option<&musicdb_lib::data::song::Song>,
.map(|v| match v { content: &str,
HtmlPart::Plain(v) => v, ) -> String {
HtmlPart::Insert(key) => match key.as_str() { v.iter()
"currentTitle" => { .map(|v| match v {
if let Some(s) = current { HtmlPart::Plain(v) => Cow::Borrowed(v.as_str()),
&s.title 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 { } else {
"" Cow::Owned(gen(e, current, next, content))
} }
} }
"nextTitle" => { })
if let Some(s) = next { .collect::<String>()
&s.title }
} else { gen(&state.html.queue, current, next, content.as_str())
"" })
}
}
"content" => &content,
_ => "",
},
})
.collect::<String>(),
)
} }
Command::Save | Command::InitComplete | Command::ErrorInfo(..) => { Command::Save | Command::InitComplete | Command::ErrorInfo(..) => {
return Poll::Pending return Poll::Pending
@ -483,6 +524,7 @@ async fn artist_view_handler(
"name" => albums.push_str(&album.name), "name" => albums.push_str(&album.name),
_ => {} _ => {}
}, },
HtmlPart::IfElse(_, _, _) => (),
} }
} }
} }
@ -501,6 +543,7 @@ async fn artist_view_handler(
"albums" => &albums, "albums" => &albums,
_ => "", _ => "",
}, },
HtmlPart::IfElse(_, _, _) => "",
}) })
.collect(), .collect(),
) )
@ -528,6 +571,7 @@ async fn album_view_handler(
"title" => songs.push_str(&song.title), "title" => songs.push_str(&song.title),
_ => {} _ => {}
}, },
HtmlPart::IfElse(_, _, _) => (),
} }
} }
} }
@ -546,6 +590,7 @@ async fn album_view_handler(
"songs" => &songs, "songs" => &songs,
_ => "", _ => "",
}, },
HtmlPart::IfElse(_, _, _) => "",
}) })
.collect(), .collect(),
) )
@ -582,45 +627,72 @@ fn build_queue_content_build(
"title" => html.push_str(&song.title), "title" => html.push_str(&song.title),
_ => {} _ => {}
}, },
HtmlPart::IfElse(_, _, _) => (),
} }
} }
} }
} }
QueueContent::Folder(ci, c, name) => { QueueContent::Folder(folder) => {
if skip_folder || path.is_empty() { if skip_folder || path.is_empty() {
for (i, c) in c.iter().enumerate() { for (i, c) in folder.iter().enumerate() {
let current = current && *ci == i; let current = current && folder.index == i;
build_queue_content_build(db, state, html, c, i.to_string(), current, false) build_queue_content_build(db, state, html, c, i.to_string(), current, false)
} }
} else { } else {
for v in if current { fn gen(
&state.html.queue_folder_current v: &Vec<HtmlPart>,
} else { folder: &QueueFolder,
&state.html.queue_folder db: &Database,
} { state: &AppState,
match v { html: &mut String,
HtmlPart::Plain(v) => html.push_str(v), path: &str,
HtmlPart::Insert(key) => match key.as_str() { current: bool,
"path" => html.push_str(&path), ) {
"name" => html.push_str(name), for v in v {
"content" => { match v {
for (i, c) in c.iter().enumerate() { HtmlPart::Plain(v) => html.push_str(v),
let current = current && *ci == i; HtmlPart::Insert(key) => match key.as_str() {
build_queue_content_build( "path" => html.push_str(&path),
db, "name" => html.push_str(&folder.name),
state, "content" => {
html, for (i, c) in folder.iter().enumerate() {
c, let current = current && folder.index == i;
format!("{path}-{i}"), build_queue_content_build(
current, db,
false, 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) => { QueueContent::Loop(total, cur, inner) => {
@ -647,58 +719,7 @@ fn build_queue_content_build(
), ),
_ => {} _ => {}
}, },
} HtmlPart::IfElse(_, _, _) => (),
}
}
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,
),
_ => {}
},
} }
} }
} }