From 36da1ab1894fe71336965cb5f1c9a90197ad1ad7 Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 24 Sep 2023 21:29:32 +0200 Subject: [PATCH] queue: shuffle is now usable --- musicdb-client/src/gui_queue.rs | 61 ++++--- musicdb-lib/src/data/database.rs | 46 +++-- musicdb-lib/src/data/queue.rs | 296 ++++++++++++++++++------------- musicdb-lib/src/data/song.rs | 2 +- musicdb-lib/src/server/mod.rs | 13 +- musicdb-server/src/web.rs | 9 +- 6 files changed, 254 insertions(+), 173 deletions(-) diff --git a/musicdb-client/src/gui_queue.rs b/musicdb-client/src/gui_queue.rs index 6e85749..06fff98 100755 --- a/musicdb-client/src/gui_queue.rs +++ b/musicdb-client/src/gui_queue.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use musicdb_lib::{ data::{ database::Database, - queue::{Queue, QueueContent}, + queue::{Queue, QueueContent, ShuffleState}, song::Song, AlbumId, ArtistId, }, @@ -116,7 +116,7 @@ impl QueueViewer { GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))) .w_mouse(), vec![], - QueueContent::Shuffle(0, vec![], vec![], 0).into(), + QueueContent::Shuffle { inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()), state: ShuffleState::NotShuffled }.into(), false, ) .alwayscopy(), @@ -237,9 +237,9 @@ fn queue_gui( } QueueContent::Loop(_, _, inner) => { let mut p = path.clone(); + p.push(0); let mut p1 = path.clone(); let p2 = p1.pop().unwrap_or(0) + 1; - p.push(0); target.push(( GuiElem::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)), line_height * 0.8, @@ -262,7 +262,7 @@ fn queue_gui( } QueueContent::Random(q) => { target.push(( - GuiElem::new(QueueRandom::new(cfg, path.clone(), queue.clone(), current)), + GuiElem::new(QueueRandom::new(cfg.clone(), path.clone(), queue.clone(), current)), line_height, )); for (i, inner) in q.iter().enumerate() { @@ -280,29 +280,37 @@ fn queue_gui( false, ); } - } - QueueContent::Shuffle(c, map, elems, _) => { + let mut p1 = path.clone(); + let p2 = p1.pop().unwrap_or(0) + 1; target.push(( - GuiElem::new(QueueShuffle::new(cfg, path.clone(), queue.clone(), current)), + GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))), + line_height * 0.4, + )); + } + QueueContent::Shuffle { inner, state: _ } => { + target.push(( + GuiElem::new(QueueShuffle::new(cfg.clone(), path.clone(), queue.clone(), current)), line_height * 0.8, )); - for (i, inner) in map.iter().enumerate() { - if let Some(inner) = elems.get(*inner) { - let mut p = path.clone(); - p.push(i); - queue_gui( - inner, - db, - depth + depth_inc_by, - depth_inc_by, - line_height, - target, - p, - current && i == *c, - false, - ); - } - } + let mut p = path.clone(); + p.push(0); + queue_gui( + inner, + db, + depth + depth_inc_by, + depth_inc_by, + line_height, + target, + p, + current, + true, + ); + let mut p1 = path.clone(); + let p2 = p1.pop().unwrap_or(0) + 1; + target.push(( + GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))), + line_height * 0.4, + )); } } } @@ -1101,7 +1109,7 @@ impl QueueShuffle { children: vec![GuiElem::new(Label::new( GuiElemCfg::default(), match queue.content() { - QueueContent::Shuffle(..) => { + QueueContent::Shuffle { .. } => { format!("shuffle") } _ => "[???]".to_string(), @@ -1200,7 +1208,8 @@ impl GuiElemTrait for QueueShuffle { } fn dragged(&mut self, dragged: Dragging) -> Vec { if !self.always_copy { - let p = self.path.clone(); + let mut p = self.path.clone(); + p.push(0); dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p, q)) } else { vec![] diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index c07c5a1..0622780 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -12,7 +12,7 @@ use crate::{load::ToFromBytes, server::Command}; use super::{ album::Album, artist::Artist, - queue::{Queue, QueueContent}, + queue::{Queue, QueueContent, ShuffleState}, song::Song, AlbumId, ArtistId, CoverId, DatabaseLocation, SongId, }; @@ -250,42 +250,68 @@ impl Database { } Command::SyncDatabase(a, b, c) => self.sync(a, b, c), Command::QueueUpdate(index, new_data) => { - if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { + let mut actions = vec![]; + if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { *v = new_data; } + Queue::handle_actions(self, actions); } Command::QueueAdd(mut index, new_data) => { - if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { + let mut actions = vec![]; + if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { if let Some(i) = v.add_to_end(new_data) { index.push(i); - if let Some(q) = self.queue.get_item_at_index_mut(&index, 0) { + if let Some(q) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { let mut actions = Vec::new(); q.init(index, &mut actions); Queue::handle_actions(self, actions); } } } + Queue::handle_actions(self, actions); } Command::QueueInsert(mut index, pos, mut new_data) => { - if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) { + let mut actions = vec![]; + if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) { index.push(pos); let mut actions = Vec::new(); new_data.init(index, &mut actions); v.insert(new_data, pos); Queue::handle_actions(self, actions); } + Queue::handle_actions(self, actions); } Command::QueueRemove(index) => { self.queue.remove_by_index(&index, 0); } Command::QueueGoto(index) => Queue::set_index_db(self, &index), - Command::QueueSetShuffle(path, map, next) => { - if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { - if let QueueContent::Shuffle(_, m, _, n) = elem.content_mut() { - *m = map; - *n = next; + Command::QueueSetShuffle(path, order) => { + let mut actions = vec![]; + if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0, &mut actions) { + if let QueueContent::Shuffle { inner, state } = elem.content_mut() { + if let QueueContent::Folder(_, v, _) = inner.content_mut() { + let mut o = std::mem::replace(v, vec![]) + .into_iter() + .map(|v| Some(v)) + .collect::>(); + for &i in order.iter() { + if let Some(a) = o.get_mut(i).and_then(Option::take) { + v.push(a); + } else { + eprintln!("[warn] 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:?}.", v.len()); + } + } + } + *state = ShuffleState::Shuffled; + } else { + eprintln!( + "[warn] can't QueueSetShuffle - element at path {path:?} isn't Shuffle" + ); } + } else { + eprintln!("[warn] can't QueueSetShuffle - no element at path {path:?}"); } + Queue::handle_actions(self, actions); } Command::AddSong(song) => { self.add_song_new(song); diff --git a/musicdb-lib/src/data/queue.rs b/musicdb-lib/src/data/queue.rs index 60b6671..2bd63fa 100755 --- a/musicdb-lib/src/data/queue.rs +++ b/musicdb-lib/src/data/queue.rs @@ -1,9 +1,6 @@ use std::collections::VecDeque; -use rand::{ - seq::{IteratorRandom, SliceRandom}, - Rng, -}; +use rand::seq::{IteratorRandom, SliceRandom}; use crate::{load::ToFromBytes, server::Command}; @@ -20,12 +17,21 @@ pub enum QueueContent { Folder(usize, Vec, String), Loop(usize, usize, Box), Random(VecDeque), - Shuffle(usize, Vec, Vec, usize), + Shuffle { + inner: Box, + state: ShuffleState, + }, +} +#[derive(Clone, Copy, Debug)] +pub enum ShuffleState { + NotShuffled, + Modified, + Shuffled, } pub enum QueueAction { AddRandomSong(Vec), - SetShuffle(Vec, Vec, usize), + SetShuffle(Vec, bool), } impl Queue { @@ -51,11 +57,7 @@ impl Queue { q.push_back(v); Some(q.len() - 1) } - QueueContent::Shuffle(_, map, elems, _) => { - map.push(elems.len()); - elems.push(v); - Some(map.len() - 1) - } + QueueContent::Shuffle { .. } => None, } } pub fn insert(&mut self, v: Self, index: usize) -> bool { @@ -72,16 +74,9 @@ impl Queue { false } } - QueueContent::Shuffle(_, map, elems, _) => { - if index <= map.len() { - map.insert(index, elems.len()); - elems.push(v); - true - } else { - false - } + QueueContent::Loop(..) | QueueContent::Random(..) | QueueContent::Shuffle { .. } => { + false } - QueueContent::Loop(..) | QueueContent::Random(..) => false, } } @@ -100,7 +95,7 @@ impl Queue { *total * inner.len() } } - QueueContent::Shuffle(_, _, v, _) => v.iter().map(|v| v.len()).sum(), + QueueContent::Shuffle { inner, state: _ } => inner.len(), } } @@ -118,7 +113,7 @@ impl Queue { } QueueContent::Loop(_, _, inner) => inner.get_current(), QueueContent::Random(v) => v.get(v.len().saturating_sub(2))?.get_current(), - QueueContent::Shuffle(i, map, elems, _) => elems.get(*map.get(*i)?), + QueueContent::Shuffle { inner, state: _ } => inner.get_current(), } } pub fn get_current_song(&self) -> Option<&SongId> { @@ -164,7 +159,7 @@ impl Queue { } } QueueContent::Random(v) => v.get(v.len().saturating_sub(1))?.get_current(), - QueueContent::Shuffle(i, map, elems, _) => elems.get(*map.get(*i + 1)?), + QueueContent::Shuffle { inner, state: _ } => inner.get_next(), } } pub fn get_first(&self) -> Option<&Self> { @@ -173,13 +168,7 @@ impl Queue { QueueContent::Folder(_, v, _) => v.first(), QueueContent::Loop(_, _, q) => q.get_first(), QueueContent::Random(q) => q.front(), - QueueContent::Shuffle(i, _, v, next) => { - if *i == 0 { - v.get(*i) - } else { - v.get(*next) - } - } + QueueContent::Shuffle { inner, state: _ } => inner.get_first(), } } @@ -192,12 +181,27 @@ impl Queue { pub fn init(&mut self, path: Vec, actions: &mut Vec) { match &mut self.content { QueueContent::Song(..) => {} - QueueContent::Folder(_, v, _) => { + QueueContent::Folder(i, v, _) => { + *i = 0; if let Some(v) = v.first_mut() { - v.init(path, actions); + v.init( + { + let mut p = path.clone(); + p.push(0); + p + }, + actions, + ); } } - QueueContent::Loop(_, _, inner) => inner.init(path, actions), + 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())); @@ -207,21 +211,17 @@ impl Queue { q.init(path, actions) } } - QueueContent::Shuffle(current, map, elems, next) => { - let mut new_map = (0..elems.len()).filter(|v| *v != *next).collect::>(); - new_map.shuffle(&mut rand::thread_rng()); - if let Some(first) = new_map.first_mut() { - let was_first = std::mem::replace(first, *next); - new_map.push(was_first); - } else if *next < elems.len() { - new_map.push(*next); + QueueContent::Shuffle { inner, state } => { + let mut p = path.clone(); + p.push(0); + if matches!(state, ShuffleState::NotShuffled | ShuffleState::Modified) { + actions.push(QueueAction::SetShuffle( + path, + matches!(state, ShuffleState::Modified), + )); + *state = ShuffleState::Shuffled; } - let new_next = if elems.is_empty() { - 0 - } else { - rand::thread_rng().gen_range(0..elems.len()) - }; - actions.push(QueueAction::SetShuffle(path, new_map, new_next)); + inner.init(p, actions); } } } @@ -238,15 +238,36 @@ impl Queue { } } } - QueueAction::SetShuffle(path, shuf, next) => { + QueueAction::SetShuffle(path, partial) => { if !db.is_client() { - db.apply_command(Command::QueueSetShuffle(path, shuf, next)); + let mut actions = vec![]; + if let Some(QueueContent::Shuffle { inner, state: _ }) = db + .queue + .get_item_at_index_mut(&path, 0, &mut actions) + .map(|v| v.content_mut()) + { + if let QueueContent::Folder(i, v, _) = inner.content_mut() { + let mut order = (0..v.len()).collect::>(); + if partial && *i + 1 < v.len() { + // shuffle only elements after the current one + order[*i + 1..].shuffle(&mut rand::thread_rng()); + } else { + order.shuffle(&mut rand::thread_rng()); + } + db.apply_command(Command::QueueSetShuffle(path, order)); + } + } + Queue::handle_actions(db, actions); } } } } } - fn advance_index_inner(&mut self, path: Vec, actions: &mut Vec) -> bool { + fn advance_index_inner( + &mut self, + mut path: Vec, + actions: &mut Vec, + ) -> bool { match &mut self.content { QueueContent::Song(_) => false, QueueContent::Folder(index, contents, _) => { @@ -278,9 +299,8 @@ impl Queue { } } QueueContent::Loop(total, current, inner) => { - let mut p = path.clone(); - p.push(0); - if inner.advance_index_inner(p, actions) { + path.push(0); + if inner.advance_index_inner(path.clone(), actions) { true } else { *current += 1; @@ -316,28 +336,15 @@ impl Queue { false } } - QueueContent::Shuffle(current, map, elems, _) => { - if map - .get(*current) - .and_then(|i| elems.get_mut(*i)) - .is_some_and(|q| { - let mut p = path.clone(); - p.push(*current); - q.advance_index_inner(p, actions) - }) - { - true + QueueContent::Shuffle { inner, state } => { + let mut p = path.clone(); + p.push(0); + if !inner.advance_index_inner(p, actions) { + *state = ShuffleState::Shuffled; + actions.push(QueueAction::SetShuffle(path, false)); + false } else { - *current += 1; - if *current < map.len() { - if let Some(elem) = map.get(*current).and_then(|i| elems.get_mut(*i)) { - elem.init(path, actions); - } - true - } else { - *current = 0; - false - } + true } } } @@ -345,6 +352,7 @@ impl Queue { pub fn set_index_db(db: &mut Database, index: &Vec) { let mut actions = vec![]; + db.queue.reset_index(); db.queue.set_index_inner(index, 0, vec![], &mut actions); Self::handle_actions(db, actions); } @@ -377,15 +385,29 @@ impl Queue { inner.set_index_inner(index, depth + 1, build_index, actions) } QueueContent::Random(_) => {} - QueueContent::Shuffle(current, map, elems, next) => { - if i != *current { - *current = i; - } - if let Some(c) = map.get(i).and_then(|i| elems.get_mut(*i)) { - c.init(build_index.clone(), actions); - c.set_index_inner(index, depth + 1, build_index, actions); + 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) { + match self.content_mut() { + QueueContent::Song(_) => {} + QueueContent::Folder(i, v, _) => { + *i = 0; + for v in v { + v.reset_index(); } } + QueueContent::Loop(_, done, i) => { + *done = 0; + i.reset_index(); + } + QueueContent::Random(_) => {} + QueueContent::Shuffle { inner, state: _ } => { + inner.reset_index(); + } } } @@ -402,34 +424,52 @@ impl Queue { } 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(_, map, elems, _) => map - .get(*i) - .and_then(|i| elems.get(*i)) - .and_then(|elem| elem.get_item_at_index(index, depth + 1)), + QueueContent::Shuffle { inner, state: _ } => { + inner.get_item_at_index(index, depth + 1) + } } } else { Some(self) } } - pub fn get_item_at_index_mut(&mut self, index: &Vec, depth: usize) -> Option<&mut Self> { + pub fn get_item_at_index_mut( + &mut self, + index: &Vec, + depth: usize, + actions: &mut Vec, + ) -> Option<&mut Self> { if let Some(i) = index.get(depth) { match &mut self.content { QueueContent::Song(_) => None, QueueContent::Folder(_, v, _) => { if let Some(v) = v.get_mut(*i) { - v.get_item_at_index_mut(index, depth + 1) + v.get_item_at_index_mut(index, depth + 1, actions) } else { None } } - QueueContent::Loop(_, _, inner) => inner.get_item_at_index_mut(index, depth + 1), - QueueContent::Random(vec) => { - vec.get_mut(*i)?.get_item_at_index_mut(index, depth + 1) + QueueContent::Loop(_, _, inner) => { + inner.get_item_at_index_mut(index, depth + 1, actions) + } + QueueContent::Random(vec) => { + vec.get_mut(*i)? + .get_item_at_index_mut(index, depth + 1, actions) + } + QueueContent::Shuffle { inner, state } => { + // if getting a mutable reference to the Folder that holds our songs, + // it may have been modified + if depth + 1 == index.len() && matches!(state, ShuffleState::Shuffled) { + *state = ShuffleState::Modified; + } + if matches!(state, ShuffleState::NotShuffled | ShuffleState::Modified) { + actions.push(QueueAction::SetShuffle( + index[0..depth].to_vec(), + matches!(state, ShuffleState::Modified), + )); + *state = ShuffleState::Shuffled; + } + inner.get_item_at_index_mut(index, depth + 1, actions) } - QueueContent::Shuffle(_, map, elems, _) => map - .get(*i) - .and_then(|i| elems.get_mut(*i)) - .and_then(|elem| elem.get_item_at_index_mut(index, depth + 1)), } } else { Some(self) @@ -468,23 +508,8 @@ impl Queue { } } QueueContent::Random(v) => v.remove(*i), - QueueContent::Shuffle(current, map, elems, next) => { - if *i < *current { - *current -= 1; - } - if *i < *next { - *next -= 1; - } - if *i < map.len() { - let elem = map.remove(*i); - if elem < elems.len() { - Some(elems.remove(elem)) - } else { - None - } - } else { - None - } + QueueContent::Shuffle { inner, state: _ } => { + inner.remove_by_index(index, depth + 1) } } } else { @@ -550,12 +575,10 @@ impl ToFromBytes for QueueContent { s.write_all(&[0b00110000])?; q.to_bytes(s)?; } - Self::Shuffle(current, map, elems, next) => { + Self::Shuffle { inner, state } => { s.write_all(&[0b00001100])?; - current.to_bytes(s)?; - map.to_bytes(s)?; - elems.to_bytes(s)?; - next.to_bytes(s)?; + inner.to_bytes(s)?; + state.to_bytes(s)?; } } Ok(()) @@ -579,13 +602,42 @@ impl ToFromBytes for QueueContent { Box::new(ToFromBytes::from_bytes(s)?), ), 0b00110000 => Self::Random(ToFromBytes::from_bytes(s)?), - 0b00001100 => Self::Shuffle( - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ), + 0b00001100 => Self::Shuffle { + inner: Box::new(ToFromBytes::from_bytes(s)?), + state: ToFromBytes::from_bytes(s)?, + }, _ => Self::Folder(0, vec![], "".to_string()), }) } } +impl ToFromBytes for ShuffleState { + fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> + where + T: std::io::Write, + { + s.write_all(&[match self { + Self::NotShuffled => 1, + Self::Modified => 2, + Self::Shuffled => 4, + }]) + } + fn from_bytes(s: &mut T) -> Result + where + T: std::io::Read, + { + let mut b = [0]; + s.read_exact(&mut b)?; + Ok(match b[0] { + 1 => Self::NotShuffled, + 2 => Self::Modified, + 4 => Self::Shuffled, + _ => { + eprintln!( + "[warn] received {} as ShuffleState, which is invalid. defaulting to Shuffled.", + b[0] + ); + Self::Shuffled + } + }) + } +} diff --git a/musicdb-lib/src/data/song.rs b/musicdb-lib/src/data/song.rs index 294a0d4..26e8c18 100755 --- a/musicdb-lib/src/data/song.rs +++ b/musicdb-lib/src/data/song.rs @@ -1,7 +1,7 @@ use std::{ fmt::Display, io::{Read, Write}, - path::{Path, PathBuf}, + path::PathBuf, sync::{Arc, Mutex}, thread::JoinHandle, }; diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index 2d6ad5d..f4ed84d 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -37,7 +37,7 @@ pub enum Command { QueueInsert(Vec, usize, Queue), QueueRemove(Vec), QueueGoto(Vec), - QueueSetShuffle(Vec, Vec, usize), + QueueSetShuffle(Vec, Vec), /// .id field is ignored! AddSong(Song), /// .id field is ignored! @@ -232,11 +232,10 @@ impl ToFromBytes for Command { s.write_all(&[0b00011011])?; index.to_bytes(s)?; } - Self::QueueSetShuffle(path, map, next) => { + Self::QueueSetShuffle(path, map) => { s.write_all(&[0b10011011])?; path.to_bytes(s)?; map.to_bytes(s)?; - next.to_bytes(s)?; } Self::AddSong(song) => { s.write_all(&[0b01010000])?; @@ -313,11 +312,9 @@ impl ToFromBytes for Command { ), 0b00011001 => Self::QueueRemove(ToFromBytes::from_bytes(s)?), 0b00011011 => Self::QueueGoto(ToFromBytes::from_bytes(s)?), - 0b10011011 => Self::QueueSetShuffle( - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ToFromBytes::from_bytes(s)?, - ), + 0b10011011 => { + Self::QueueSetShuffle(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?) + } 0b01010000 => Self::AddSong(ToFromBytes::from_bytes(s)?), 0b01010011 => Self::AddAlbum(ToFromBytes::from_bytes(s)?), 0b01011100 => Self::AddArtist(ToFromBytes::from_bytes(s)?), diff --git a/musicdb-server/src/web.rs b/musicdb-server/src/web.rs index 2fa9669..0b1cbf3 100755 --- a/musicdb-server/src/web.rs +++ b/musicdb-server/src/web.rs @@ -663,7 +663,7 @@ fn build_queue_content_build( } } } - QueueContent::Shuffle(cur, map, content, _) => { + QueueContent::Shuffle { inner, state: _ } => { for v in if current { &state.html.queue_shuffle_current } else { @@ -674,18 +674,15 @@ fn build_queue_content_build( HtmlPart::Insert(key) => match key.as_str() { "path" => html.push_str(&path), "content" => { - for (i, v) in map.iter().filter_map(|i| content.get(*i)).enumerate() - { build_queue_content_build( db, state, html, - &v, + &inner, format!("{path}-0"), - current && i == *cur, + current, true, ) - } } _ => {} },