mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-12-14 20:06:16 +01:00
include shuffle functionality in folders, remove shuffle and random elements, change ToFromBytes impls
This commit is contained in:
@@ -19,6 +19,7 @@ pub struct CacheManager {
|
||||
/// Amount of bytes. If free system memory is greater than this number, consider caching more songs.
|
||||
pub max_avail_mem: Arc<AtomicU64>,
|
||||
pub songs_to_cache: Arc<AtomicU32>,
|
||||
#[allow(unused)]
|
||||
thread: Arc<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
@@ -82,15 +83,9 @@ impl CacheManager {
|
||||
} else {
|
||||
let mut queue = db.queue.clone();
|
||||
|
||||
let mut actions = vec![];
|
||||
|
||||
let queue_current_song = queue.get_current_song().copied();
|
||||
queue.advance_index_inner(vec![], &mut actions);
|
||||
let queue_next_song = if actions.is_empty() {
|
||||
queue.get_current_song().copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
queue.advance_index_inner();
|
||||
let queue_next_song = queue.get_current_song().copied();
|
||||
|
||||
let mut ids_to_cache = queue_current_song
|
||||
.into_iter()
|
||||
@@ -98,10 +93,7 @@ impl CacheManager {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for _ in 2..songs_to_cache {
|
||||
queue.advance_index_inner(vec![], &mut actions);
|
||||
if !actions.is_empty() {
|
||||
break;
|
||||
}
|
||||
queue.advance_index_inner();
|
||||
if let Some(id) = queue.get_current_song() {
|
||||
if !ids_to_cache.contains(id) {
|
||||
ids_to_cache.push(*id);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use rand::prelude::SliceRandom;
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap},
|
||||
fs::{self, File},
|
||||
@@ -8,13 +9,14 @@ use std::{
|
||||
};
|
||||
|
||||
use colorize::AnsiColor;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::{load::ToFromBytes, server::Command};
|
||||
|
||||
use super::{
|
||||
album::Album,
|
||||
artist::Artist,
|
||||
queue::{Queue, QueueContent, ShuffleState},
|
||||
queue::{Queue, QueueContent, QueueFolder},
|
||||
song::Song,
|
||||
AlbumId, ArtistId, CoverId, DatabaseLocation, SongId,
|
||||
};
|
||||
@@ -515,8 +517,13 @@ impl Database {
|
||||
t.clear();
|
||||
}
|
||||
}
|
||||
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
|
||||
self.broadcast_update(&command);
|
||||
// some commands shouldn't be broadcast. these will broadcast a different command in their specific implementation.
|
||||
match &command {
|
||||
// Will broadcast `QueueSetShuffle`
|
||||
Command::QueueShuffle(_) => (),
|
||||
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
|
||||
_ => self.broadcast_update(&command),
|
||||
}
|
||||
match command {
|
||||
Command::Resume => self.playing = true,
|
||||
Command::Pause => self.playing = false,
|
||||
@@ -525,9 +532,7 @@ impl Database {
|
||||
if !Queue::advance_index_db(self) {
|
||||
// end of queue
|
||||
self.apply_command(Command::Pause);
|
||||
let mut actions = Vec::new();
|
||||
self.queue.init(vec![], &mut actions);
|
||||
Queue::handle_actions(self, actions);
|
||||
self.queue.init();
|
||||
}
|
||||
}
|
||||
Command::Save => {
|
||||
@@ -537,51 +542,67 @@ impl Database {
|
||||
}
|
||||
Command::SyncDatabase(a, b, c) => self.sync(a, b, c),
|
||||
Command::QueueUpdate(index, new_data) => {
|
||||
let mut actions = vec![];
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) {
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||
*v = new_data;
|
||||
}
|
||||
Queue::handle_actions(self, actions);
|
||||
}
|
||||
Command::QueueAdd(index, new_data) => {
|
||||
let mut actions = vec![];
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) {
|
||||
v.add_to_end(new_data, index, &mut actions);
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||
v.add_to_end(new_data);
|
||||
}
|
||||
Queue::handle_actions(self, actions);
|
||||
}
|
||||
Command::QueueInsert(index, pos, new_data) => {
|
||||
let mut actions = vec![];
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0, &mut actions) {
|
||||
v.insert(new_data, pos, index, &mut actions);
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||
v.insert(new_data, pos);
|
||||
}
|
||||
Queue::handle_actions(self, actions);
|
||||
}
|
||||
Command::QueueRemove(index) => {
|
||||
self.queue.remove_by_index(&index, 0);
|
||||
}
|
||||
Command::QueueGoto(index) => Queue::set_index_db(self, &index),
|
||||
Command::QueueSetShuffle(path, order) => {
|
||||
let mut actions = vec![];
|
||||
if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0, &mut actions) {
|
||||
if let QueueContent::Shuffle { inner, state } = elem.content_mut() {
|
||||
if let QueueContent::Folder(_, v, _) = inner.content_mut() {
|
||||
let mut o = std::mem::replace(v, vec![])
|
||||
.into_iter()
|
||||
.map(|v| Some(v))
|
||||
.collect::<Vec<_>>();
|
||||
for &i in order.iter() {
|
||||
if let Some(a) = o.get_mut(i).and_then(Option::take) {
|
||||
v.push(a);
|
||||
} else {
|
||||
eprintln!("[{}] Can't properly apply requested order to Queue/Shuffle: no element at index {i}. Index may be out of bounds or used twice. Len: {}, Order: {order:?}.", "WARN".yellow(), v.len());
|
||||
}
|
||||
Command::QueueShuffle(path) => {
|
||||
if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) {
|
||||
if let QueueContent::Folder(QueueFolder {
|
||||
index: _,
|
||||
content,
|
||||
name: _,
|
||||
order: _,
|
||||
}) = elem.content_mut()
|
||||
{
|
||||
let mut ord: Vec<usize> = (0..content.len()).collect();
|
||||
ord.shuffle(&mut thread_rng());
|
||||
self.apply_command(Command::QueueSetShuffle(path, ord));
|
||||
} else {
|
||||
eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!");
|
||||
}
|
||||
} else {
|
||||
eprintln!("(QueueShuffle) No QueueElement at {path:?}");
|
||||
}
|
||||
}
|
||||
Command::QueueSetShuffle(path, ord) => {
|
||||
if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) {
|
||||
if let QueueContent::Folder(QueueFolder {
|
||||
index,
|
||||
content,
|
||||
name: _,
|
||||
order,
|
||||
}) = elem.content_mut()
|
||||
{
|
||||
if ord.len() == content.len() {
|
||||
if let Some(ni) = ord.iter().position(|v| *v == *index) {
|
||||
*index = ni;
|
||||
}
|
||||
*order = Some(ord);
|
||||
} else {
|
||||
eprintln!(
|
||||
"[warn] can't QueueSetShuffle - length of new ord ({}) is not the same as length of content ({})!",
|
||||
ord.len(),
|
||||
content.len()
|
||||
);
|
||||
}
|
||||
*state = ShuffleState::Shuffled;
|
||||
} else {
|
||||
eprintln!(
|
||||
"[warn] can't QueueSetShuffle - element at path {path:?} isn't Shuffle"
|
||||
"[warn] can't QueueSetShuffle - element at path {path:?} isn't a folder"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -590,7 +611,22 @@ impl Database {
|
||||
"WARN".yellow()
|
||||
);
|
||||
}
|
||||
Queue::handle_actions(self, actions);
|
||||
}
|
||||
Command::QueueUnshuffle(path) => {
|
||||
if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) {
|
||||
if let QueueContent::Folder(QueueFolder {
|
||||
index,
|
||||
content: _,
|
||||
name: _,
|
||||
order,
|
||||
}) = elem.content_mut()
|
||||
{
|
||||
if let Some(ni) = order.as_ref().and_then(|v| v.get(*index).copied()) {
|
||||
*index = ni;
|
||||
}
|
||||
*order = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::AddSong(song) => {
|
||||
self.add_song_new(song);
|
||||
@@ -745,7 +781,7 @@ impl Database {
|
||||
songs: HashMap::new(),
|
||||
covers: HashMap::new(),
|
||||
custom_files: None,
|
||||
queue: QueueContent::Folder(0, vec![], String::new()).into(),
|
||||
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
||||
update_endpoints: vec![],
|
||||
playing: false,
|
||||
command_sender: None,
|
||||
@@ -765,7 +801,7 @@ impl Database {
|
||||
songs: HashMap::new(),
|
||||
covers: HashMap::new(),
|
||||
custom_files: None,
|
||||
queue: QueueContent::Folder(0, vec![], String::new()).into(),
|
||||
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
||||
update_endpoints: vec![],
|
||||
playing: false,
|
||||
command_sender: None,
|
||||
@@ -790,7 +826,7 @@ impl Database {
|
||||
songs: ToFromBytes::from_bytes(&mut file)?,
|
||||
covers: ToFromBytes::from_bytes(&mut file)?,
|
||||
custom_files: None,
|
||||
queue: QueueContent::Folder(0, vec![], String::new()).into(),
|
||||
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
||||
update_endpoints: vec![],
|
||||
playing: false,
|
||||
command_sender: None,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::{collections::VecDeque, ops::AddAssign};
|
||||
use std::ops::AddAssign;
|
||||
|
||||
use rand::seq::{IteratorRandom, SliceRandom};
|
||||
|
||||
use crate::{load::ToFromBytes, server::Command};
|
||||
use crate::load::ToFromBytes;
|
||||
|
||||
use super::{database::Database, SongId};
|
||||
|
||||
@@ -14,25 +12,15 @@ pub struct Queue {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum QueueContent {
|
||||
Song(SongId),
|
||||
Folder(usize, Vec<Queue>, String),
|
||||
Folder(QueueFolder),
|
||||
Loop(usize, usize, Box<Queue>),
|
||||
Random(VecDeque<Queue>),
|
||||
Shuffle {
|
||||
inner: Box<Queue>,
|
||||
state: ShuffleState,
|
||||
},
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ShuffleState {
|
||||
NotShuffled,
|
||||
Modified,
|
||||
Shuffled,
|
||||
}
|
||||
|
||||
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),
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct QueueFolder {
|
||||
pub index: usize,
|
||||
pub content: Vec<Queue>,
|
||||
pub name: String,
|
||||
pub order: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
@@ -46,72 +34,18 @@ impl Queue {
|
||||
&mut self.content
|
||||
}
|
||||
|
||||
pub fn add_to_end(
|
||||
&mut self,
|
||||
v: Vec<Self>,
|
||||
mut path: Vec<usize>,
|
||||
actions: &mut Vec<QueueAction>,
|
||||
) -> Option<usize> {
|
||||
pub fn add_to_end(&mut self, v: Vec<Self>) -> Option<usize> {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Folder(_, vec, _) => {
|
||||
let len = vec.len();
|
||||
for (i, mut v) in v.into_iter().enumerate() {
|
||||
path.push(len + i);
|
||||
v.init(path.clone(), actions);
|
||||
vec.push(v);
|
||||
path.pop();
|
||||
}
|
||||
Some(len)
|
||||
}
|
||||
QueueContent::Folder(folder) => folder.add_to_end(v),
|
||||
QueueContent::Loop(..) => None,
|
||||
QueueContent::Random(q) => {
|
||||
// insert new elements
|
||||
let len = q.len();
|
||||
for (i, mut v) in v.into_iter().enumerate() {
|
||||
path.push(len + i);
|
||||
v.init(path.clone(), actions);
|
||||
q.push_back(v);
|
||||
path.pop();
|
||||
}
|
||||
Some(len)
|
||||
}
|
||||
QueueContent::Shuffle { .. } => None,
|
||||
}
|
||||
}
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
v: Vec<Self>,
|
||||
index: usize,
|
||||
mut path: Vec<usize>,
|
||||
actions: &mut Vec<QueueAction>,
|
||||
) -> bool {
|
||||
pub fn insert(&mut self, v: Vec<Self>, index: usize) -> bool {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => false,
|
||||
QueueContent::Folder(current, vec, _) => {
|
||||
if index <= vec.len() {
|
||||
if *current >= index {
|
||||
*current += v.len();
|
||||
}
|
||||
// remove the elements starting at the insertion point
|
||||
let end = vec.split_off(index);
|
||||
// insert new elements
|
||||
for (i, mut v) in v.into_iter().enumerate() {
|
||||
path.push(index + i);
|
||||
v.init(path.clone(), actions);
|
||||
vec.push(v);
|
||||
path.pop();
|
||||
}
|
||||
// re-add previously removed elements
|
||||
vec.extend(end);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(..) | QueueContent::Random(..) | QueueContent::Shuffle { .. } => {
|
||||
false
|
||||
}
|
||||
QueueContent::Folder(folder) => folder.insert(v, index),
|
||||
QueueContent::Loop(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,16 +55,14 @@ impl Queue {
|
||||
}
|
||||
match &self.content {
|
||||
QueueContent::Song(_) => 1,
|
||||
QueueContent::Folder(_, v, _) => v.iter().map(|v| v.len()).sum(),
|
||||
QueueContent::Random(v) => v.iter().map(|v| v.len()).sum(),
|
||||
QueueContent::Folder(folder) => folder.len(),
|
||||
QueueContent::Loop(total, _done, inner) => {
|
||||
if *total == 0 {
|
||||
inner.len()
|
||||
} else {
|
||||
*total * inner.len()
|
||||
total.saturating_mul(inner.len())
|
||||
}
|
||||
}
|
||||
QueueContent::Shuffle { inner, state: _ } => inner.len(),
|
||||
}
|
||||
}
|
||||
pub fn duration_total(&self, db: &Database) -> QueueDuration {
|
||||
@@ -150,9 +82,14 @@ impl Queue {
|
||||
QueueContent::Song(v) => {
|
||||
dur.millis += db.get_song(v).map(|s| s.duration_millis).unwrap_or(0)
|
||||
}
|
||||
QueueContent::Folder(c, inner, _) => {
|
||||
for (i, inner) in inner.iter().enumerate() {
|
||||
if dur.include_past || i >= *c {
|
||||
QueueContent::Folder(QueueFolder {
|
||||
index,
|
||||
content,
|
||||
name: _,
|
||||
order: _,
|
||||
}) => {
|
||||
for (i, inner) in content.iter().enumerate() {
|
||||
if dur.include_past || i >= *index {
|
||||
inner.add_duration(dur, db);
|
||||
}
|
||||
}
|
||||
@@ -175,15 +112,6 @@ impl Queue {
|
||||
}
|
||||
}
|
||||
}
|
||||
QueueContent::Random(q) => {
|
||||
if let Some(el) = q.iter().next() {
|
||||
dur.random_known_millis += el.duration_total(db).millis;
|
||||
}
|
||||
dur.random_counter += 1;
|
||||
}
|
||||
QueueContent::Shuffle { inner, state: _ } => {
|
||||
inner.add_duration(dur, db);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,17 +120,8 @@ impl Queue {
|
||||
pub fn get_current(&self) -> Option<&Self> {
|
||||
match &self.content {
|
||||
QueueContent::Song(_) => Some(self),
|
||||
QueueContent::Folder(i, v, _) => {
|
||||
let i = *i;
|
||||
if let Some(v) = v.get(i) {
|
||||
v.get_current()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Folder(folder) => folder.get_current_immut()?.get_current(),
|
||||
QueueContent::Loop(_, _, inner) => inner.get_current(),
|
||||
QueueContent::Random(v) => v.get(v.len().saturating_sub(2))?.get_current(),
|
||||
QueueContent::Shuffle { inner, state: _ } => inner.get_current(),
|
||||
}
|
||||
}
|
||||
pub fn get_current_song(&self) -> Option<&SongId> {
|
||||
@@ -222,22 +141,7 @@ impl Queue {
|
||||
pub fn get_next(&self) -> Option<&Self> {
|
||||
match &self.content {
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Folder(i, vec, _) => {
|
||||
let i = *i;
|
||||
if let Some(v) = vec.get(i) {
|
||||
if let Some(v) = v.get_next() {
|
||||
Some(v)
|
||||
} else {
|
||||
if let Some(v) = vec.get(i + 1) {
|
||||
v.get_current()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Folder(folder) => folder.get_next(),
|
||||
QueueContent::Loop(total, current, inner) => {
|
||||
if let Some(v) = inner.get_next() {
|
||||
Some(v)
|
||||
@@ -247,156 +151,43 @@ impl Queue {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Random(v) => v.get(v.len().saturating_sub(1))?.get_current(),
|
||||
QueueContent::Shuffle { inner, state: _ } => inner.get_next(),
|
||||
}
|
||||
}
|
||||
pub fn get_first(&self) -> Option<&Self> {
|
||||
match &self.content {
|
||||
QueueContent::Song(..) => Some(self),
|
||||
QueueContent::Folder(_, v, _) => v.first(),
|
||||
QueueContent::Folder(folder) => folder.get_first(),
|
||||
QueueContent::Loop(_, _, q) => q.get_first(),
|
||||
QueueContent::Random(q) => q.front(),
|
||||
QueueContent::Shuffle { inner, state: _ } => inner.get_first(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_index_db(db: &mut Database) -> bool {
|
||||
let mut actions = vec![];
|
||||
let o = db.queue.advance_index_inner(vec![], &mut actions);
|
||||
Self::handle_actions(db, actions);
|
||||
let o = db.queue.advance_index_inner();
|
||||
o
|
||||
}
|
||||
pub fn init(&mut self, path: Vec<usize>, actions: &mut Vec<QueueAction>) {
|
||||
pub fn init(&mut self) {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(..) => {}
|
||||
QueueContent::Folder(i, v, _) => {
|
||||
*i = 0;
|
||||
if let Some(v) = v.first_mut() {
|
||||
v.init(
|
||||
{
|
||||
let mut p = path.clone();
|
||||
p.push(0);
|
||||
p
|
||||
},
|
||||
actions,
|
||||
);
|
||||
QueueContent::Folder(folder) => {
|
||||
folder.index = 0;
|
||||
for v in &mut folder.content {
|
||||
v.init();
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => inner.init(
|
||||
{
|
||||
let mut p = path.clone();
|
||||
p.push(0);
|
||||
p
|
||||
},
|
||||
actions,
|
||||
),
|
||||
QueueContent::Random(q) => {
|
||||
if q.len() == 0 {
|
||||
actions.push(QueueAction::AddRandomSong(path.clone()));
|
||||
actions.push(QueueAction::AddRandomSong(path.clone()));
|
||||
}
|
||||
if let Some(q) = q.get_mut(q.len().saturating_sub(2)) {
|
||||
q.init(path, actions)
|
||||
}
|
||||
}
|
||||
QueueContent::Shuffle { inner, state } => {
|
||||
let mut p = path.clone();
|
||||
p.push(0);
|
||||
if inner.len() == 0 {
|
||||
*state = ShuffleState::NotShuffled;
|
||||
} else if matches!(state, ShuffleState::NotShuffled | ShuffleState::Modified) {
|
||||
actions.push(QueueAction::SetShuffle(
|
||||
path,
|
||||
matches!(state, ShuffleState::Modified),
|
||||
));
|
||||
*state = ShuffleState::Shuffled;
|
||||
}
|
||||
inner.init(p, actions);
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => inner.init(),
|
||||
}
|
||||
}
|
||||
pub fn handle_actions(db: &mut Database, actions: Vec<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 {
|
||||
pub fn advance_index_inner(&mut self) -> bool {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => false,
|
||||
QueueContent::Folder(index, contents, _) => {
|
||||
if let Some(c) = contents.get_mut(*index) {
|
||||
let mut p = path.clone();
|
||||
p.push(*index);
|
||||
if c.advance_index_inner(p, actions) {
|
||||
// inner value could advance index, do nothing.
|
||||
true
|
||||
} else {
|
||||
loop {
|
||||
if *index + 1 < contents.len() {
|
||||
// can advance
|
||||
*index += 1;
|
||||
if contents[*index].enabled {
|
||||
contents[*index].init(path, actions);
|
||||
break true;
|
||||
}
|
||||
} else {
|
||||
// can't advance: index would be out of bounds
|
||||
*index = 0;
|
||||
break false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*index = 0;
|
||||
false
|
||||
}
|
||||
}
|
||||
QueueContent::Folder(folder) => folder.advance_index_inner(),
|
||||
QueueContent::Loop(total, current, inner) => {
|
||||
path.push(0);
|
||||
if inner.advance_index_inner(path.clone(), actions) {
|
||||
if inner.advance_index_inner() {
|
||||
true
|
||||
} else {
|
||||
*current += 1;
|
||||
if *total == 0 || *current < *total {
|
||||
inner.init(path, actions);
|
||||
inner.init();
|
||||
true
|
||||
} else {
|
||||
*current = 0;
|
||||
@@ -404,56 +195,18 @@ impl Queue {
|
||||
}
|
||||
}
|
||||
}
|
||||
QueueContent::Random(q) => {
|
||||
let i = q.len().saturating_sub(2);
|
||||
let mut p = path.clone();
|
||||
p.push(i);
|
||||
if q.get_mut(i)
|
||||
.is_some_and(|inner| inner.advance_index_inner(p, actions))
|
||||
{
|
||||
true
|
||||
} else {
|
||||
if q.len() >= 2 {
|
||||
q.pop_front();
|
||||
}
|
||||
// only sub 1 here because this is before the next random song is added
|
||||
let i2 = q.len().saturating_sub(1);
|
||||
if let Some(q) = q.get_mut(i2) {
|
||||
let mut p = path.clone();
|
||||
p.push(i2);
|
||||
q.init(p, actions);
|
||||
}
|
||||
actions.push(QueueAction::AddRandomSong(path));
|
||||
false
|
||||
}
|
||||
}
|
||||
QueueContent::Shuffle { inner, state } => {
|
||||
let mut p = path.clone();
|
||||
p.push(0);
|
||||
if !inner.advance_index_inner(p, actions) {
|
||||
// end of inner Folder element, reshuffle for next time
|
||||
*state = ShuffleState::Shuffled;
|
||||
actions.push(QueueAction::SetShuffle(path, false));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_index_db(db: &mut Database, index: &Vec<usize>) {
|
||||
let mut actions = vec![];
|
||||
db.queue.reset_index();
|
||||
db.queue.set_index_inner(index, 0, vec![], &mut actions);
|
||||
Self::handle_actions(db, actions);
|
||||
db.queue.set_index_inner(index, 0, vec![]);
|
||||
}
|
||||
pub fn set_index_inner(
|
||||
&mut self,
|
||||
index: &Vec<usize>,
|
||||
depth: usize,
|
||||
mut build_index: Vec<usize>,
|
||||
actions: &mut Vec<QueueAction>,
|
||||
) {
|
||||
let i = if let Some(i) = index.get(depth) {
|
||||
*i
|
||||
@@ -463,32 +216,25 @@ impl Queue {
|
||||
build_index.push(i);
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => {}
|
||||
QueueContent::Folder(idx, contents, _) => {
|
||||
if i != *idx {
|
||||
*idx = i;
|
||||
}
|
||||
if let Some(c) = contents.get_mut(i) {
|
||||
c.init(build_index.clone(), actions);
|
||||
c.set_index_inner(index, depth + 1, build_index, actions);
|
||||
QueueContent::Folder(folder) => {
|
||||
folder.index = i;
|
||||
if let Some(c) = folder.get_current_mut() {
|
||||
c.init();
|
||||
c.set_index_inner(index, depth + 1, build_index);
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => {
|
||||
inner.init(build_index.clone(), actions);
|
||||
inner.set_index_inner(index, depth + 1, build_index, actions)
|
||||
}
|
||||
QueueContent::Random(_) => {}
|
||||
QueueContent::Shuffle { inner, state: _ } => {
|
||||
inner.init(build_index.clone(), actions);
|
||||
inner.set_index_inner(index, depth + 1, build_index, actions)
|
||||
inner.init();
|
||||
inner.set_index_inner(index, depth + 1, build_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn reset_index(&mut self) {
|
||||
match self.content_mut() {
|
||||
QueueContent::Song(_) => {}
|
||||
QueueContent::Folder(i, v, _) => {
|
||||
*i = 0;
|
||||
for v in v {
|
||||
QueueContent::Folder(folder) => {
|
||||
folder.index = 0;
|
||||
for v in &mut folder.content {
|
||||
v.reset_index();
|
||||
}
|
||||
}
|
||||
@@ -496,10 +242,6 @@ impl Queue {
|
||||
*done = 0;
|
||||
i.reset_index();
|
||||
}
|
||||
QueueContent::Random(_) => {}
|
||||
QueueContent::Shuffle { inner, state: _ } => {
|
||||
inner.reset_index();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,61 +249,31 @@ impl Queue {
|
||||
if let Some(i) = index.get(depth) {
|
||||
match &self.content {
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Folder(_, v, _) => {
|
||||
if let Some(v) = v.get(*i) {
|
||||
QueueContent::Folder(folder) => {
|
||||
if let Some(v) = folder.get_at(*i) {
|
||||
v.get_item_at_index(index, depth + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => inner.get_item_at_index(index, depth + 1),
|
||||
QueueContent::Random(vec) => vec.get(*i)?.get_item_at_index(index, depth + 1),
|
||||
QueueContent::Shuffle { inner, state: _ } => {
|
||||
inner.get_item_at_index(index, depth + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
pub fn get_item_at_index_mut(
|
||||
&mut self,
|
||||
index: &Vec<usize>,
|
||||
depth: usize,
|
||||
actions: &mut Vec<QueueAction>,
|
||||
) -> Option<&mut Self> {
|
||||
pub fn get_item_at_index_mut(&mut self, index: &Vec<usize>, depth: usize) -> Option<&mut Self> {
|
||||
if let Some(i) = index.get(depth) {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Folder(_, v, _) => {
|
||||
if let Some(v) = v.get_mut(*i) {
|
||||
v.get_item_at_index_mut(index, depth + 1, actions)
|
||||
QueueContent::Folder(folder) => {
|
||||
if let Some(v) = folder.get_mut_at(*i) {
|
||||
v.get_item_at_index_mut(index, depth + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => {
|
||||
inner.get_item_at_index_mut(index, depth + 1, actions)
|
||||
}
|
||||
QueueContent::Random(vec) => {
|
||||
vec.get_mut(*i)?
|
||||
.get_item_at_index_mut(index, depth + 1, actions)
|
||||
}
|
||||
QueueContent::Shuffle { inner, state } => {
|
||||
// if getting a mutable reference to the Folder that holds our songs,
|
||||
// it may have been modified
|
||||
if depth + 1 == index.len() && matches!(state, ShuffleState::Shuffled) {
|
||||
*state = ShuffleState::Modified;
|
||||
}
|
||||
if matches!(state, ShuffleState::NotShuffled | ShuffleState::Modified) {
|
||||
actions.push(QueueAction::SetShuffle(
|
||||
index[0..depth].to_vec(),
|
||||
matches!(state, ShuffleState::Modified),
|
||||
));
|
||||
*state = ShuffleState::Shuffled;
|
||||
}
|
||||
inner.get_item_at_index_mut(index, depth + 1, actions)
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => inner.get_item_at_index_mut(index, depth + 1),
|
||||
}
|
||||
} else {
|
||||
Some(self)
|
||||
@@ -572,21 +284,33 @@ impl Queue {
|
||||
if let Some(i) = index.get(depth) {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Folder(ci, v, _) => {
|
||||
QueueContent::Folder(folder) => {
|
||||
if depth + 1 < index.len() {
|
||||
if let Some(v) = v.get_mut(*i) {
|
||||
if let Some(v) = folder.get_mut_at(*i) {
|
||||
v.remove_by_index(index, depth + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
if *i < v.len() {
|
||||
if *i < folder.content.len() {
|
||||
// if current playback is past this point,
|
||||
// reduce the index by 1 so that it still points to the same element
|
||||
if *ci > *i {
|
||||
*ci -= 1;
|
||||
if folder.index > *i {
|
||||
folder.index -= 1;
|
||||
}
|
||||
Some(v.remove(*i))
|
||||
let idx = if let Some(order) = &mut folder.order {
|
||||
let idx = order.remove(*i);
|
||||
// compensate for removal of element from .content
|
||||
for o in order {
|
||||
if *o > idx {
|
||||
*o -= 1;
|
||||
}
|
||||
}
|
||||
idx
|
||||
} else {
|
||||
*i
|
||||
};
|
||||
Some(folder.content.remove(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -599,15 +323,148 @@ impl Queue {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Random(v) => v.remove(*i),
|
||||
QueueContent::Shuffle { inner, state: _ } => {
|
||||
inner.remove_by_index(index, depth + 1)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueueFolder {
|
||||
pub fn iter(&self) -> QueueFolderIter {
|
||||
QueueFolderIter {
|
||||
folder: self,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
pub fn add_to_end(&mut self, v: Vec<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 {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueueContent> for Queue {
|
||||
@@ -651,11 +508,9 @@ impl ToFromBytes for QueueContent {
|
||||
s.write_all(&[0b11111111])?;
|
||||
id.to_bytes(s)?;
|
||||
}
|
||||
Self::Folder(index, contents, name) => {
|
||||
Self::Folder(folder) => {
|
||||
s.write_all(&[0b00000000])?;
|
||||
index.to_bytes(s)?;
|
||||
contents.to_bytes(s)?;
|
||||
name.to_bytes(s)?;
|
||||
ToFromBytes::to_bytes(folder, s)?;
|
||||
}
|
||||
Self::Loop(total, current, inner) => {
|
||||
s.write_all(&[0b11000000])?;
|
||||
@@ -663,15 +518,6 @@ impl ToFromBytes for QueueContent {
|
||||
current.to_bytes(s)?;
|
||||
inner.to_bytes(s)?;
|
||||
}
|
||||
Self::Random(q) => {
|
||||
s.write_all(&[0b00110000])?;
|
||||
q.to_bytes(s)?;
|
||||
}
|
||||
Self::Shuffle { inner, state } => {
|
||||
s.write_all(&[0b00001100])?;
|
||||
inner.to_bytes(s)?;
|
||||
state.to_bytes(s)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -683,54 +529,43 @@ impl ToFromBytes for QueueContent {
|
||||
s.read_exact(&mut switch_on)?;
|
||||
Ok(match switch_on[0] {
|
||||
0b11111111 => Self::Song(ToFromBytes::from_bytes(s)?),
|
||||
0b00000000 => Self::Folder(
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
),
|
||||
0b00000000 => Self::Folder(ToFromBytes::from_bytes(s)?),
|
||||
0b11000000 => Self::Loop(
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
Box::new(ToFromBytes::from_bytes(s)?),
|
||||
),
|
||||
0b00110000 => Self::Random(ToFromBytes::from_bytes(s)?),
|
||||
0b00001100 => Self::Shuffle {
|
||||
inner: Box::new(ToFromBytes::from_bytes(s)?),
|
||||
state: ToFromBytes::from_bytes(s)?,
|
||||
},
|
||||
_ => Self::Folder(0, vec![], "<invalid byte received>".to_string()),
|
||||
_ => Self::Folder(QueueFolder {
|
||||
index: 0,
|
||||
content: vec![],
|
||||
name: "<invalid byte received>".to_string(),
|
||||
order: None,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ToFromBytes for ShuffleState {
|
||||
impl ToFromBytes for QueueFolder {
|
||||
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||
where
|
||||
T: std::io::Write,
|
||||
T: std::io::prelude::Write,
|
||||
{
|
||||
s.write_all(&[match self {
|
||||
Self::NotShuffled => 1,
|
||||
Self::Modified => 2,
|
||||
Self::Shuffled => 4,
|
||||
}])
|
||||
ToFromBytes::to_bytes(&self.index, s)?;
|
||||
ToFromBytes::to_bytes(&self.content, s)?;
|
||||
ToFromBytes::to_bytes(&self.name, s)?;
|
||||
ToFromBytes::to_bytes(&self.order, s)?;
|
||||
Ok(())
|
||||
}
|
||||
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
|
||||
where
|
||||
T: std::io::Read,
|
||||
T: std::io::prelude::Read,
|
||||
{
|
||||
let mut b = [0];
|
||||
s.read_exact(&mut b)?;
|
||||
Ok(match b[0] {
|
||||
1 => Self::NotShuffled,
|
||||
2 => Self::Modified,
|
||||
4 => Self::Shuffled,
|
||||
_ => {
|
||||
eprintln!(
|
||||
"[warn] received {} as ShuffleState, which is invalid. defaulting to Shuffled.",
|
||||
b[0]
|
||||
);
|
||||
Self::Shuffled
|
||||
}
|
||||
})
|
||||
let v = Self {
|
||||
index: ToFromBytes::from_bytes(s)?,
|
||||
content: ToFromBytes::from_bytes(s)?,
|
||||
name: ToFromBytes::from_bytes(s)?,
|
||||
order: ToFromBytes::from_bytes(s)?,
|
||||
};
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ where
|
||||
match self {
|
||||
None => s.write_all(&[0b11001100]),
|
||||
Some(v) => {
|
||||
s.write_all(&[0b00111010])?;
|
||||
s.write_all(&[0b00110011])?;
|
||||
v.to_bytes(s)
|
||||
}
|
||||
}
|
||||
@@ -129,10 +129,11 @@ where
|
||||
{
|
||||
let mut b = [0u8];
|
||||
s.read_exact(&mut b)?;
|
||||
match b[0] {
|
||||
0b00111010 => Ok(Some(ToFromBytes::from_bytes(s)?)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
Ok(if (b[0] ^ 0b11001100).count_ones() > 4 {
|
||||
Some(ToFromBytes::from_bytes(s)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<K, V> ToFromBytes for HashMap<K, V>
|
||||
|
||||
@@ -40,7 +40,11 @@ pub enum Command {
|
||||
QueueInsert(Vec<usize>, usize, Vec<Queue>),
|
||||
QueueRemove(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>),
|
||||
QueueUnshuffle(Vec<usize>),
|
||||
|
||||
/// .id field is ignored!
|
||||
AddSong(Song),
|
||||
@@ -256,47 +260,57 @@ pub fn handle_one_connection_as_control(
|
||||
}
|
||||
}
|
||||
|
||||
const BYTE_RESUME: u8 = 0b11000000;
|
||||
const BYTE_PAUSE: u8 = 0b00110000;
|
||||
const BYTE_STOP: u8 = 0b11110000;
|
||||
const BYTE_NEXT_SONG: u8 = 0b11110010;
|
||||
// 01_***_*** => Simple commands
|
||||
// 01_00*_*** => Playback
|
||||
// 01_010_*** => Other
|
||||
// 01_100_*** => Errors
|
||||
// 10_***_*** => Complicated commands
|
||||
// 10_00*_*** => Queue
|
||||
// 10_010_*** => Misc
|
||||
// 10_100_*** => Library
|
||||
|
||||
const BYTE_SYNC_DATABASE: u8 = 0b01011000;
|
||||
const BYTE_QUEUE_UPDATE: u8 = 0b00011100;
|
||||
const BYTE_QUEUE_ADD: u8 = 0b00011010;
|
||||
const BYTE_QUEUE_INSERT: u8 = 0b00011110;
|
||||
const BYTE_QUEUE_REMOVE: u8 = 0b00011001;
|
||||
const BYTE_QUEUE_GOTO: u8 = 0b00011011;
|
||||
const BYTE_QUEUE_SET_SHUFFLE: u8 = 0b10011011;
|
||||
const BYTE_RESUME: u8 = 0b01_000_000;
|
||||
const BYTE_PAUSE: u8 = 0b01_000_001;
|
||||
const BYTE_STOP: u8 = 0b01_000_010;
|
||||
const BYTE_NEXT_SONG: u8 = 0b01_000_100;
|
||||
|
||||
const BYTE_ADD_SONG: u8 = 0b01010000;
|
||||
const BYTE_ADD_ALBUM: u8 = 0b01010011;
|
||||
const BYTE_ADD_ARTIST: u8 = 0b01011100;
|
||||
const BYTE_ADD_COVER: u8 = 0b01011101;
|
||||
const BYTE_MODIFY_SONG: u8 = 0b10010000;
|
||||
const BYTE_MODIFY_ALBUM: u8 = 0b10010011;
|
||||
const BYTE_MODIFY_ARTIST: u8 = 0b10011100;
|
||||
const BYTE_REMOVE_SONG: u8 = 0b11010000;
|
||||
const BYTE_REMOVE_ALBUM: u8 = 0b11010011;
|
||||
const BYTE_REMOVE_ARTIST: u8 = 0b11011100;
|
||||
const BYTE_TAG_SONG_FLAG_SET: u8 = 0b11100000;
|
||||
const BYTE_TAG_SONG_FLAG_UNSET: u8 = 0b11100001;
|
||||
const BYTE_TAG_ALBUM_FLAG_SET: u8 = 0b11100010;
|
||||
const BYTE_TAG_ALBUM_FLAG_UNSET: u8 = 0b11100011;
|
||||
const BYTE_TAG_ARTIST_FLAG_SET: u8 = 0b11100110;
|
||||
const BYTE_TAG_ARTIST_FLAG_UNSET: u8 = 0b11100111;
|
||||
const BYTE_TAG_SONG_PROPERTY_SET: u8 = 0b11101001;
|
||||
const BYTE_TAG_SONG_PROPERTY_UNSET: u8 = 0b11101010;
|
||||
const BYTE_TAG_ALBUM_PROPERTY_SET: u8 = 0b11101011;
|
||||
const BYTE_TAG_ALBUM_PROPERTY_UNSET: u8 = 0b11101100;
|
||||
const BYTE_TAG_ARTIST_PROPERTY_SET: u8 = 0b11101110;
|
||||
const BYTE_TAG_ARTIST_PROPERTY_UNSET: u8 = 0b11101111;
|
||||
const BYTE_INIT_COMPLETE: u8 = 0b01_010_000;
|
||||
const BYTE_SET_SONG_DURATION: u8 = 0b01_010_001;
|
||||
const BYTE_SAVE: u8 = 0b01_010_010;
|
||||
const BYTE_ERRORINFO: u8 = 0b01_100_010;
|
||||
|
||||
const BYTE_SET_SONG_DURATION: u8 = 0b11111000;
|
||||
const BYTE_QUEUE_UPDATE: u8 = 0b10_000_000;
|
||||
const BYTE_QUEUE_ADD: u8 = 0b10_000_001;
|
||||
const BYTE_QUEUE_INSERT: u8 = 0b10_000_010;
|
||||
const BYTE_QUEUE_REMOVE: u8 = 0b10_000_100;
|
||||
const BYTE_QUEUE_GOTO: u8 = 0b10_001_000;
|
||||
const BYTE_QUEUE_ACTION: u8 = 0b10_100;
|
||||
const SUBBYTE_ACTION_SHUFFLE: u8 = 0b01_000_001;
|
||||
const SUBBYTE_ACTION_SET_SHUFFLE: u8 = 0b01_000_010;
|
||||
const SUBBYTE_ACTION_UNSHUFFLE: u8 = 0b01_000_100;
|
||||
|
||||
const BYTE_INIT_COMPLETE: u8 = 0b00110001;
|
||||
const BYTE_SAVE: u8 = 0b11110011;
|
||||
const BYTE_ERRORINFO: u8 = 0b11011011;
|
||||
const BYTE_SYNC_DATABASE: u8 = 0b10_010_100;
|
||||
|
||||
const BYTE_LIB_ADD: u8 = 0b10_100_000;
|
||||
const BYTE_LIB_MODIFY: u8 = 0b10_100_001;
|
||||
const BYTE_LIB_REMOVE: u8 = 0b10_100_010;
|
||||
const BYTE_LIB_TAG: u8 = 0b10_100_100;
|
||||
const SUBBYTE_SONG: u8 = 0b10_001_000;
|
||||
const SUBBYTE_ALBUM: u8 = 0b10_001_001;
|
||||
const SUBBYTE_ARTIST: u8 = 0b10_001_010;
|
||||
const SUBBYTE_COVER: u8 = 0b10_001_100;
|
||||
const SUBBYTE_TAG_SONG_FLAG_SET: u8 = 0b10_001_000;
|
||||
const SUBBYTE_TAG_SONG_FLAG_UNSET: u8 = 0b10_001_001;
|
||||
const SUBBYTE_TAG_ALBUM_FLAG_SET: u8 = 0b10_001_010;
|
||||
const SUBBYTE_TAG_ALBUM_FLAG_UNSET: u8 = 0b10_001_100;
|
||||
const SUBBYTE_TAG_ARTIST_FLAG_SET: u8 = 0b10_010_000;
|
||||
const SUBBYTE_TAG_ARTIST_FLAG_UNSET: u8 = 0b10_010_001;
|
||||
const SUBBYTE_TAG_SONG_PROPERTY_SET: u8 = 0b10_010_010;
|
||||
const SUBBYTE_TAG_SONG_PROPERTY_UNSET: u8 = 0b10_010_100;
|
||||
const SUBBYTE_TAG_ALBUM_PROPERTY_SET: u8 = 0b10_100_000;
|
||||
const SUBBYTE_TAG_ALBUM_PROPERTY_UNSET: u8 = 0b10_100_001;
|
||||
const SUBBYTE_TAG_ARTIST_PROPERTY_SET: u8 = 0b10_100_010;
|
||||
const SUBBYTE_TAG_ARTIST_PROPERTY_UNSET: u8 = 0b10_100_100;
|
||||
|
||||
impl ToFromBytes for Command {
|
||||
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||
@@ -338,111 +352,144 @@ impl ToFromBytes for Command {
|
||||
s.write_all(&[BYTE_QUEUE_GOTO])?;
|
||||
index.to_bytes(s)?;
|
||||
}
|
||||
Self::QueueShuffle(path) => {
|
||||
s.write_all(&[BYTE_QUEUE_ACTION])?;
|
||||
s.write_all(&[SUBBYTE_ACTION_SHUFFLE])?;
|
||||
path.to_bytes(s)?;
|
||||
}
|
||||
Self::QueueSetShuffle(path, map) => {
|
||||
s.write_all(&[BYTE_QUEUE_SET_SHUFFLE])?;
|
||||
s.write_all(&[BYTE_QUEUE_ACTION])?;
|
||||
s.write_all(&[SUBBYTE_ACTION_SET_SHUFFLE])?;
|
||||
path.to_bytes(s)?;
|
||||
map.to_bytes(s)?;
|
||||
}
|
||||
Self::QueueUnshuffle(path) => {
|
||||
s.write_all(&[BYTE_QUEUE_ACTION])?;
|
||||
s.write_all(&[SUBBYTE_ACTION_UNSHUFFLE])?;
|
||||
path.to_bytes(s)?;
|
||||
}
|
||||
Self::AddSong(song) => {
|
||||
s.write_all(&[BYTE_ADD_SONG])?;
|
||||
s.write_all(&[BYTE_LIB_ADD])?;
|
||||
s.write_all(&[SUBBYTE_SONG])?;
|
||||
song.to_bytes(s)?;
|
||||
}
|
||||
Self::AddAlbum(album) => {
|
||||
s.write_all(&[BYTE_ADD_ALBUM])?;
|
||||
s.write_all(&[BYTE_LIB_ADD])?;
|
||||
s.write_all(&[SUBBYTE_ALBUM])?;
|
||||
album.to_bytes(s)?;
|
||||
}
|
||||
Self::AddArtist(artist) => {
|
||||
s.write_all(&[BYTE_ADD_ARTIST])?;
|
||||
s.write_all(&[BYTE_LIB_ADD])?;
|
||||
s.write_all(&[SUBBYTE_ARTIST])?;
|
||||
artist.to_bytes(s)?;
|
||||
}
|
||||
Self::AddCover(cover) => {
|
||||
s.write_all(&[BYTE_ADD_COVER])?;
|
||||
s.write_all(&[BYTE_LIB_ADD])?;
|
||||
s.write_all(&[SUBBYTE_COVER])?;
|
||||
cover.to_bytes(s)?;
|
||||
}
|
||||
Self::ModifySong(song) => {
|
||||
s.write_all(&[BYTE_MODIFY_SONG])?;
|
||||
s.write_all(&[BYTE_LIB_MODIFY])?;
|
||||
s.write_all(&[SUBBYTE_SONG])?;
|
||||
song.to_bytes(s)?;
|
||||
}
|
||||
Self::ModifyAlbum(album) => {
|
||||
s.write_all(&[BYTE_MODIFY_ALBUM])?;
|
||||
s.write_all(&[BYTE_LIB_MODIFY])?;
|
||||
s.write_all(&[SUBBYTE_ALBUM])?;
|
||||
album.to_bytes(s)?;
|
||||
}
|
||||
Self::ModifyArtist(artist) => {
|
||||
s.write_all(&[BYTE_MODIFY_ARTIST])?;
|
||||
s.write_all(&[BYTE_LIB_MODIFY])?;
|
||||
s.write_all(&[SUBBYTE_ARTIST])?;
|
||||
artist.to_bytes(s)?;
|
||||
}
|
||||
Self::RemoveSong(song) => {
|
||||
s.write_all(&[BYTE_REMOVE_SONG])?;
|
||||
s.write_all(&[BYTE_LIB_REMOVE])?;
|
||||
s.write_all(&[SUBBYTE_SONG])?;
|
||||
song.to_bytes(s)?;
|
||||
}
|
||||
Self::RemoveAlbum(album) => {
|
||||
s.write_all(&[BYTE_REMOVE_ALBUM])?;
|
||||
s.write_all(&[BYTE_LIB_REMOVE])?;
|
||||
s.write_all(&[SUBBYTE_ALBUM])?;
|
||||
album.to_bytes(s)?;
|
||||
}
|
||||
Self::RemoveArtist(artist) => {
|
||||
s.write_all(&[BYTE_REMOVE_ARTIST])?;
|
||||
s.write_all(&[BYTE_LIB_REMOVE])?;
|
||||
s.write_all(&[SUBBYTE_ARTIST])?;
|
||||
artist.to_bytes(s)?;
|
||||
}
|
||||
Self::TagSongFlagSet(id, tag) => {
|
||||
s.write_all(&[BYTE_TAG_SONG_FLAG_SET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_SONG_FLAG_SET])?;
|
||||
id.to_bytes(s)?;
|
||||
tag.to_bytes(s)?;
|
||||
}
|
||||
Self::TagSongFlagUnset(id, tag) => {
|
||||
s.write_all(&[BYTE_TAG_SONG_FLAG_UNSET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_SONG_FLAG_UNSET])?;
|
||||
id.to_bytes(s)?;
|
||||
tag.to_bytes(s)?;
|
||||
}
|
||||
Self::TagAlbumFlagSet(id, tag) => {
|
||||
s.write_all(&[BYTE_TAG_ALBUM_FLAG_SET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ALBUM_FLAG_SET])?;
|
||||
id.to_bytes(s)?;
|
||||
tag.to_bytes(s)?;
|
||||
}
|
||||
Self::TagAlbumFlagUnset(id, tag) => {
|
||||
s.write_all(&[BYTE_TAG_ALBUM_FLAG_UNSET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ALBUM_FLAG_UNSET])?;
|
||||
id.to_bytes(s)?;
|
||||
tag.to_bytes(s)?;
|
||||
}
|
||||
Self::TagArtistFlagSet(id, tag) => {
|
||||
s.write_all(&[BYTE_TAG_ARTIST_FLAG_SET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ARTIST_FLAG_SET])?;
|
||||
id.to_bytes(s)?;
|
||||
tag.to_bytes(s)?;
|
||||
}
|
||||
Self::TagArtistFlagUnset(id, tag) => {
|
||||
s.write_all(&[BYTE_TAG_ARTIST_FLAG_UNSET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ARTIST_FLAG_UNSET])?;
|
||||
id.to_bytes(s)?;
|
||||
tag.to_bytes(s)?;
|
||||
}
|
||||
Self::TagSongPropertySet(id, key, val) => {
|
||||
s.write_all(&[BYTE_TAG_SONG_PROPERTY_SET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_SONG_PROPERTY_SET])?;
|
||||
id.to_bytes(s)?;
|
||||
key.to_bytes(s)?;
|
||||
val.to_bytes(s)?;
|
||||
}
|
||||
Self::TagSongPropertyUnset(id, key) => {
|
||||
s.write_all(&[BYTE_TAG_SONG_PROPERTY_UNSET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_SONG_PROPERTY_UNSET])?;
|
||||
id.to_bytes(s)?;
|
||||
key.to_bytes(s)?;
|
||||
}
|
||||
Self::TagAlbumPropertySet(id, key, val) => {
|
||||
s.write_all(&[BYTE_TAG_ALBUM_PROPERTY_SET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ALBUM_PROPERTY_SET])?;
|
||||
id.to_bytes(s)?;
|
||||
key.to_bytes(s)?;
|
||||
val.to_bytes(s)?;
|
||||
}
|
||||
Self::TagAlbumPropertyUnset(id, key) => {
|
||||
s.write_all(&[BYTE_TAG_ALBUM_PROPERTY_UNSET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ALBUM_PROPERTY_UNSET])?;
|
||||
id.to_bytes(s)?;
|
||||
key.to_bytes(s)?;
|
||||
}
|
||||
Self::TagArtistPropertySet(id, key, val) => {
|
||||
s.write_all(&[BYTE_TAG_ARTIST_PROPERTY_SET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ARTIST_PROPERTY_SET])?;
|
||||
id.to_bytes(s)?;
|
||||
key.to_bytes(s)?;
|
||||
val.to_bytes(s)?;
|
||||
}
|
||||
Self::TagArtistPropertyUnset(id, key) => {
|
||||
s.write_all(&[BYTE_TAG_ARTIST_PROPERTY_UNSET])?;
|
||||
s.write_all(&[BYTE_LIB_TAG])?;
|
||||
s.write_all(&[SUBBYTE_TAG_ARTIST_PROPERTY_UNSET])?;
|
||||
id.to_bytes(s)?;
|
||||
key.to_bytes(s)?;
|
||||
}
|
||||
@@ -467,14 +514,12 @@ impl ToFromBytes for Command {
|
||||
where
|
||||
T: std::io::Read,
|
||||
{
|
||||
let mut kind = [0];
|
||||
s.read_exact(&mut kind)?;
|
||||
macro_rules! from_bytes {
|
||||
() => {
|
||||
ToFromBytes::from_bytes(s)?
|
||||
};
|
||||
}
|
||||
Ok(match kind[0] {
|
||||
Ok(match s.read_byte()? {
|
||||
BYTE_RESUME => Self::Resume,
|
||||
BYTE_PAUSE => Self::Pause,
|
||||
BYTE_STOP => Self::Stop,
|
||||
@@ -485,42 +530,93 @@ impl ToFromBytes for Command {
|
||||
BYTE_QUEUE_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()),
|
||||
BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()),
|
||||
BYTE_QUEUE_GOTO => Self::QueueGoto(from_bytes!()),
|
||||
BYTE_QUEUE_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()),
|
||||
BYTE_ADD_SONG => Self::AddSong(from_bytes!()),
|
||||
BYTE_ADD_ALBUM => Self::AddAlbum(from_bytes!()),
|
||||
BYTE_ADD_ARTIST => Self::AddArtist(from_bytes!()),
|
||||
BYTE_MODIFY_SONG => Self::ModifySong(from_bytes!()),
|
||||
BYTE_MODIFY_ALBUM => Self::ModifyAlbum(from_bytes!()),
|
||||
BYTE_MODIFY_ARTIST => Self::ModifyArtist(from_bytes!()),
|
||||
BYTE_REMOVE_SONG => Self::RemoveSong(from_bytes!()),
|
||||
BYTE_REMOVE_ALBUM => Self::RemoveAlbum(from_bytes!()),
|
||||
BYTE_REMOVE_ARTIST => Self::RemoveArtist(from_bytes!()),
|
||||
BYTE_TAG_SONG_FLAG_SET => Self::TagSongFlagSet(from_bytes!(), from_bytes!()),
|
||||
BYTE_TAG_SONG_FLAG_UNSET => Self::TagSongFlagUnset(from_bytes!(), from_bytes!()),
|
||||
BYTE_TAG_ALBUM_FLAG_SET => Self::TagAlbumFlagSet(from_bytes!(), from_bytes!()),
|
||||
BYTE_TAG_ALBUM_FLAG_UNSET => Self::TagAlbumFlagUnset(from_bytes!(), from_bytes!()),
|
||||
BYTE_TAG_ARTIST_FLAG_SET => Self::TagArtistFlagSet(from_bytes!(), from_bytes!()),
|
||||
BYTE_TAG_ARTIST_FLAG_UNSET => Self::TagArtistFlagUnset(from_bytes!(), from_bytes!()),
|
||||
BYTE_TAG_SONG_PROPERTY_SET => {
|
||||
Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||
}
|
||||
BYTE_TAG_SONG_PROPERTY_UNSET => {
|
||||
Self::TagSongPropertyUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
BYTE_TAG_ALBUM_PROPERTY_SET => {
|
||||
Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||
}
|
||||
BYTE_TAG_ALBUM_PROPERTY_UNSET => {
|
||||
Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
BYTE_TAG_ARTIST_PROPERTY_SET => {
|
||||
Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||
}
|
||||
BYTE_TAG_ARTIST_PROPERTY_UNSET => {
|
||||
Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
BYTE_QUEUE_ACTION => match s.read_byte()? {
|
||||
SUBBYTE_ACTION_SHUFFLE => Self::QueueShuffle(from_bytes!()),
|
||||
SUBBYTE_ACTION_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()),
|
||||
SUBBYTE_ACTION_UNSHUFFLE => Self::QueueUnshuffle(from_bytes!()),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"[{}] unexpected byte when reading command:queueAction; stopping playback.",
|
||||
"WARN".yellow()
|
||||
);
|
||||
Self::Stop
|
||||
}
|
||||
},
|
||||
BYTE_LIB_ADD => match s.read_byte()? {
|
||||
SUBBYTE_SONG => Self::AddSong(from_bytes!()),
|
||||
SUBBYTE_ALBUM => Self::AddAlbum(from_bytes!()),
|
||||
SUBBYTE_ARTIST => Self::AddArtist(from_bytes!()),
|
||||
SUBBYTE_COVER => Self::AddCover(from_bytes!()),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"[{}] unexpected byte when reading command:libAdd; stopping playback.",
|
||||
"WARN".yellow()
|
||||
);
|
||||
Self::Stop
|
||||
}
|
||||
},
|
||||
BYTE_LIB_MODIFY => match s.read_byte()? {
|
||||
SUBBYTE_SONG => Self::ModifySong(from_bytes!()),
|
||||
SUBBYTE_ALBUM => Self::ModifyAlbum(from_bytes!()),
|
||||
SUBBYTE_ARTIST => Self::ModifyArtist(from_bytes!()),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"[{}] unexpected byte when reading command:libModify; stopping playback.",
|
||||
"WARN".yellow()
|
||||
);
|
||||
Self::Stop
|
||||
}
|
||||
},
|
||||
BYTE_LIB_REMOVE => match s.read_byte()? {
|
||||
SUBBYTE_SONG => Self::RemoveSong(from_bytes!()),
|
||||
SUBBYTE_ALBUM => Self::RemoveAlbum(from_bytes!()),
|
||||
SUBBYTE_ARTIST => Self::RemoveArtist(from_bytes!()),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"[{}] unexpected byte when reading command:libRemove; stopping playback.",
|
||||
"WARN".yellow()
|
||||
);
|
||||
Self::Stop
|
||||
}
|
||||
},
|
||||
BYTE_LIB_TAG => match s.read_byte()? {
|
||||
SUBBYTE_TAG_SONG_FLAG_SET => Self::TagSongFlagSet(from_bytes!(), from_bytes!()),
|
||||
SUBBYTE_TAG_SONG_FLAG_UNSET => Self::TagSongFlagUnset(from_bytes!(), from_bytes!()),
|
||||
SUBBYTE_TAG_ALBUM_FLAG_SET => Self::TagAlbumFlagSet(from_bytes!(), from_bytes!()),
|
||||
SUBBYTE_TAG_ALBUM_FLAG_UNSET => {
|
||||
Self::TagAlbumFlagUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_ARTIST_FLAG_SET => Self::TagArtistFlagSet(from_bytes!(), from_bytes!()),
|
||||
SUBBYTE_TAG_ARTIST_FLAG_UNSET => {
|
||||
Self::TagArtistFlagUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_SONG_PROPERTY_SET => {
|
||||
Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_SONG_PROPERTY_UNSET => {
|
||||
Self::TagSongPropertyUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_ALBUM_PROPERTY_SET => {
|
||||
Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_ALBUM_PROPERTY_UNSET => {
|
||||
Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_ARTIST_PROPERTY_SET => {
|
||||
Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!())
|
||||
}
|
||||
SUBBYTE_TAG_ARTIST_PROPERTY_UNSET => {
|
||||
Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!())
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"[{}] unexpected byte when reading command:libTag; stopping playback.",
|
||||
"WARN".yellow()
|
||||
);
|
||||
Self::Stop
|
||||
}
|
||||
},
|
||||
BYTE_SET_SONG_DURATION => Self::SetSongDuration(from_bytes!(), from_bytes!()),
|
||||
BYTE_ADD_COVER => Self::AddCover(from_bytes!()),
|
||||
BYTE_INIT_COMPLETE => Self::InitComplete,
|
||||
BYTE_SAVE => Self::Save,
|
||||
BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()),
|
||||
@@ -534,3 +630,14 @@ impl ToFromBytes for Command {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
trait ReadByte {
|
||||
fn read_byte(&mut self) -> std::io::Result<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])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user