mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 14:13:53 +01:00
include shuffle functionality in folders, remove shuffle and random elements, change ToFromBytes impls
This commit is contained in:
parent
8b8f13f98a
commit
0fe2648efe
@ -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"]
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
n
|
name
|
||||||
},
|
},
|
||||||
q.len()
|
content.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 {
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 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
|
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
|
||||||
self.broadcast_update(&command);
|
_ => 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;
|
||||||
}
|
}
|
||||||
*state = ShuffleState::Shuffled;
|
*order = Some(ord);
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[warn] can't QueueSetShuffle - element at path {path:?} isn't Shuffle"
|
"[warn] can't QueueSetShuffle - length of new ord ({}) is not the same as length of content ({})!",
|
||||||
|
ord.len(),
|
||||||
|
content.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"[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,
|
||||||
|
@ -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 } => {
|
pub fn advance_index_inner(&mut self) -> bool {
|
||||||
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>) {
|
|
||||||
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,11 +323,144 @@ 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 {
|
||||||
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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!()),
|
||||||
|
_ => {
|
||||||
|
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!())
|
Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||||
}
|
}
|
||||||
BYTE_TAG_SONG_PROPERTY_UNSET => {
|
SUBBYTE_TAG_SONG_PROPERTY_UNSET => {
|
||||||
Self::TagSongPropertyUnset(from_bytes!(), from_bytes!())
|
Self::TagSongPropertyUnset(from_bytes!(), from_bytes!())
|
||||||
}
|
}
|
||||||
BYTE_TAG_ALBUM_PROPERTY_SET => {
|
SUBBYTE_TAG_ALBUM_PROPERTY_SET => {
|
||||||
Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||||
}
|
}
|
||||||
BYTE_TAG_ALBUM_PROPERTY_UNSET => {
|
SUBBYTE_TAG_ALBUM_PROPERTY_UNSET => {
|
||||||
Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!())
|
Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!())
|
||||||
}
|
}
|
||||||
BYTE_TAG_ARTIST_PROPERTY_SET => {
|
SUBBYTE_TAG_ARTIST_PROPERTY_SET => {
|
||||||
Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||||
}
|
}
|
||||||
BYTE_TAG_ARTIST_PROPERTY_UNSET => {
|
SUBBYTE_TAG_ARTIST_PROPERTY_UNSET => {
|
||||||
Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!())
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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())
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<small>>></small>
|
<small>>></small>
|
||||||
<button hx-post="/queue/goto/\:path" hx-swap="none">⏵</button>
|
<button hx-post="/queue/goto/\:path" hx-swap="none">⏵</button>
|
||||||
<small>\:name</small>
|
<small>\:name</small>
|
||||||
|
\?shuffled? (shuffled)\;\;
|
||||||
</div>
|
</div>
|
||||||
\:content
|
\:content
|
||||||
<div>
|
<div>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<small>>></small>
|
<small>>></small>
|
||||||
<button hx-post="/queue/goto/\:path" hx-swap="none">⏵</button>
|
<button hx-post="/queue/goto/\:path" hx-swap="none">⏵</button>
|
||||||
<small><b>\:name</b></small>
|
<small><b>\:name</b></small>
|
||||||
|
\?shuffled? (shuffled)\;\;
|
||||||
</div>
|
</div>
|
||||||
\:content
|
\:content
|
||||||
<div>
|
<div>
|
||||||
|
@ -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,18 +386,27 @@ 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(
|
||||||
|
id: &u64,
|
||||||
|
artist: &musicdb_lib::data::artist::Artist,
|
||||||
|
v: &Vec<HtmlPart>,
|
||||||
|
arts: &mut String,
|
||||||
|
) {
|
||||||
|
for v in v {
|
||||||
match v {
|
match v {
|
||||||
HtmlPart::Plain(v) => artists.push_str(v),
|
HtmlPart::Plain(v) => arts.push_str(v),
|
||||||
HtmlPart::Insert(key) => match key.as_str() {
|
HtmlPart::Insert(key) => match key.as_str() {
|
||||||
"id" => artists.push_str(&id.to_string()),
|
"id" => arts.push_str(&id.to_string()),
|
||||||
"name" => artists.push_str(&artist.name),
|
"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
|
||||||
.artists
|
.artists
|
||||||
@ -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>,
|
||||||
|
content: &str,
|
||||||
|
) -> String {
|
||||||
|
v.iter()
|
||||||
.map(|v| match v {
|
.map(|v| match v {
|
||||||
HtmlPart::Plain(v) => v,
|
HtmlPart::Plain(v) => Cow::Borrowed(v.as_str()),
|
||||||
HtmlPart::Insert(key) => match key.as_str() {
|
HtmlPart::Insert(key) => match key.as_str() {
|
||||||
"currentTitle" => {
|
"currentTitle" => {
|
||||||
if let Some(s) = current {
|
if let Some(s) = current {
|
||||||
&s.title
|
Cow::Borrowed(s.title.as_str())
|
||||||
} else {
|
} else {
|
||||||
""
|
Cow::Borrowed("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"nextTitle" => {
|
"nextTitle" => {
|
||||||
if let Some(s) = next {
|
if let Some(s) = next {
|
||||||
&s.title
|
Cow::Borrowed(s.title.as_str())
|
||||||
} else {
|
} else {
|
||||||
""
|
Cow::Borrowed("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"content" => &content,
|
"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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
}
|
||||||
|
gen(&state.html.queue, current, next, content.as_str())
|
||||||
})
|
})
|
||||||
.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,30 +627,36 @@ 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,
|
||||||
|
html: &mut String,
|
||||||
|
path: &str,
|
||||||
|
current: bool,
|
||||||
|
) {
|
||||||
|
for v in v {
|
||||||
match v {
|
match v {
|
||||||
HtmlPart::Plain(v) => html.push_str(v),
|
HtmlPart::Plain(v) => html.push_str(v),
|
||||||
HtmlPart::Insert(key) => match key.as_str() {
|
HtmlPart::Insert(key) => match key.as_str() {
|
||||||
"path" => html.push_str(&path),
|
"path" => html.push_str(&path),
|
||||||
"name" => html.push_str(name),
|
"name" => html.push_str(&folder.name),
|
||||||
"content" => {
|
"content" => {
|
||||||
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(
|
build_queue_content_build(
|
||||||
db,
|
db,
|
||||||
state,
|
state,
|
||||||
@ -619,10 +670,31 @@ fn build_queue_content_build(
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
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) => {
|
||||||
for v in match (*total, current) {
|
for v in match (*total, current) {
|
||||||
(0, false) => &state.html.queue_loopinf,
|
(0, false) => &state.html.queue_loopinf,
|
||||||
@ -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,
|
|
||||||
),
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user