mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-12-14 11:56:16 +01:00
small improvements idk i forgot i had a git repo for this project
This commit is contained in:
@@ -8,5 +8,6 @@ edition = "2021"
|
||||
[dependencies]
|
||||
awedio = "0.2.0"
|
||||
base64 = "0.21.2"
|
||||
rand = "0.8.5"
|
||||
rc-u8-reader = "2.0.16"
|
||||
tokio = "1.29.1"
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::{
|
||||
fs::{self, File},
|
||||
io::{BufReader, Write},
|
||||
path::PathBuf,
|
||||
sync::{mpsc, Arc},
|
||||
time::Instant,
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{load::ToFromBytes, server::Command};
|
||||
@@ -18,16 +18,14 @@ use super::{
|
||||
};
|
||||
|
||||
pub struct Database {
|
||||
/// the path to the file used to save/load the data
|
||||
db_file: PathBuf,
|
||||
/// the path to the file used to save/load the data. empty if database is in client mode.
|
||||
pub db_file: PathBuf,
|
||||
/// the path to the directory containing the actual music and cover image files
|
||||
pub lib_directory: PathBuf,
|
||||
artists: HashMap<ArtistId, Artist>,
|
||||
albums: HashMap<AlbumId, Album>,
|
||||
songs: HashMap<SongId, Song>,
|
||||
covers: HashMap<CoverId, DatabaseLocation>,
|
||||
// TODO! make sure this works out for the server AND clients
|
||||
// cover_cache: HashMap<CoverId, Vec<u8>>,
|
||||
covers: HashMap<CoverId, Cover>,
|
||||
// These will be used for autosave once that gets implemented
|
||||
db_data_file_change_first: Option<Instant>,
|
||||
db_data_file_change_last: Option<Instant>,
|
||||
@@ -132,6 +130,21 @@ impl Database {
|
||||
}
|
||||
self.panic("database.artists all keys used - no more capacity for new artists!");
|
||||
}
|
||||
/// adds a cover to the database.
|
||||
/// assigns a new id, which it then returns.
|
||||
pub fn add_cover_new(&mut self, cover: Cover) -> AlbumId {
|
||||
self.add_cover_new_nomagic(cover)
|
||||
}
|
||||
/// used internally
|
||||
fn add_cover_new_nomagic(&mut self, cover: Cover) -> AlbumId {
|
||||
for key in 0.. {
|
||||
if !self.covers.contains_key(&key) {
|
||||
self.covers.insert(key, cover);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
self.panic("database.artists all keys used - no more capacity for new artists!");
|
||||
}
|
||||
/// updates an existing song in the database with the new value.
|
||||
/// uses song.id to find the correct song.
|
||||
/// if the id doesn't exist in the db, Err(()) is returned.
|
||||
@@ -193,7 +206,13 @@ impl Database {
|
||||
Command::Pause => self.playing = false,
|
||||
Command::Stop => self.playing = false,
|
||||
Command::NextSong => {
|
||||
self.queue.advance_index();
|
||||
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);
|
||||
}
|
||||
}
|
||||
Command::Save => {
|
||||
if let Err(e) = self.save_database(None) {
|
||||
@@ -208,18 +227,37 @@ impl Database {
|
||||
}
|
||||
Command::QueueAdd(mut index, new_data) => {
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||
v.add_to_end(new_data);
|
||||
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) {
|
||||
let mut actions = Vec::new();
|
||||
q.init(index, &mut actions);
|
||||
Queue::handle_actions(self, actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::QueueInsert(mut index, pos, new_data) => {
|
||||
Command::QueueInsert(mut index, pos, mut new_data) => {
|
||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||
index.push(pos);
|
||||
let mut actions = Vec::new();
|
||||
new_data.init(index, &mut actions);
|
||||
v.insert(new_data, pos);
|
||||
Queue::handle_actions(self, actions);
|
||||
}
|
||||
}
|
||||
Command::QueueRemove(index) => {
|
||||
self.queue.remove_by_index(&index, 0);
|
||||
}
|
||||
Command::QueueGoto(index) => self.queue.set_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::AddSong(song) => {
|
||||
self.add_song_new(song);
|
||||
}
|
||||
@@ -229,6 +267,7 @@ impl Database {
|
||||
Command::AddArtist(artist) => {
|
||||
self.add_artist_new(artist);
|
||||
}
|
||||
Command::AddCover(cover) => _ = self.add_cover_new(cover),
|
||||
Command::ModifySong(song) => {
|
||||
_ = self.update_song(song);
|
||||
}
|
||||
@@ -302,6 +341,7 @@ impl Database {
|
||||
command_sender: None,
|
||||
})
|
||||
}
|
||||
/// saves the database's contents. save path can be overridden
|
||||
pub fn save_database(&self, path: Option<PathBuf>) -> Result<PathBuf, std::io::Error> {
|
||||
let path = if let Some(p) = path {
|
||||
p
|
||||
@@ -386,4 +426,75 @@ impl Database {
|
||||
pub fn artists(&self) -> &HashMap<ArtistId, Artist> {
|
||||
&self.artists
|
||||
}
|
||||
pub fn covers(&self) -> &HashMap<CoverId, Cover> {
|
||||
&self.covers
|
||||
}
|
||||
/// you should probably use a Command to do this...
|
||||
pub fn songs_mut(&mut self) -> &mut HashMap<SongId, Song> {
|
||||
&mut self.songs
|
||||
}
|
||||
/// you should probably use a Command to do this...
|
||||
pub fn albums_mut(&mut self) -> &mut HashMap<AlbumId, Album> {
|
||||
&mut self.albums
|
||||
}
|
||||
/// you should probably use a Command to do this...
|
||||
pub fn artists_mut(&mut self) -> &mut HashMap<ArtistId, Artist> {
|
||||
&mut self.artists
|
||||
}
|
||||
/// you should probably use a Command to do this...
|
||||
pub fn covers_mut(&mut self) -> &mut HashMap<CoverId, Cover> {
|
||||
&mut self.covers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Cover {
|
||||
pub location: DatabaseLocation,
|
||||
pub data: Arc<Mutex<(bool, Option<(Instant, Vec<u8>)>)>>,
|
||||
}
|
||||
impl Cover {
|
||||
pub fn get_bytes<O>(
|
||||
&self,
|
||||
path: impl FnOnce(&DatabaseLocation) -> PathBuf,
|
||||
conv: impl FnOnce(&Vec<u8>) -> O,
|
||||
) -> Option<O> {
|
||||
let mut data = loop {
|
||||
let data = self.data.lock().unwrap();
|
||||
if data.0 {
|
||||
drop(data);
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
} else {
|
||||
break data;
|
||||
}
|
||||
};
|
||||
if let Some((accessed, data)) = &mut data.1 {
|
||||
*accessed = Instant::now();
|
||||
Some(conv(&data))
|
||||
} else {
|
||||
match std::fs::read(path(&self.location)) {
|
||||
Ok(bytes) => {
|
||||
data.1 = Some((Instant::now(), bytes));
|
||||
Some(conv(&data.1.as_ref().unwrap().1))
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToFromBytes for Cover {
|
||||
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
self.location.to_bytes(s)
|
||||
}
|
||||
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
|
||||
where
|
||||
T: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
location: ToFromBytes::from_bytes(s)?,
|
||||
data: Arc::new(Mutex::new((false, None))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
use crate::load::ToFromBytes;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::SongId;
|
||||
use rand::{
|
||||
seq::{IteratorRandom, SliceRandom},
|
||||
Rng,
|
||||
};
|
||||
|
||||
use crate::{load::ToFromBytes, server::Command};
|
||||
|
||||
use super::{database::Database, SongId};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Queue {
|
||||
@@ -11,6 +18,14 @@ pub struct Queue {
|
||||
pub enum QueueContent {
|
||||
Song(SongId),
|
||||
Folder(usize, Vec<Queue>, String),
|
||||
Loop(usize, usize, Box<Queue>),
|
||||
Random(VecDeque<Queue>),
|
||||
Shuffle(usize, Vec<usize>, Vec<Queue>, usize),
|
||||
}
|
||||
|
||||
pub enum QueueAction {
|
||||
AddRandomSong(Vec<usize>),
|
||||
SetShuffle(Vec<usize>, Vec<usize>, usize),
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
@@ -20,27 +35,53 @@ impl Queue {
|
||||
pub fn content(&self) -> &QueueContent {
|
||||
&self.content
|
||||
}
|
||||
pub fn content_mut(&mut self) -> &mut QueueContent {
|
||||
&mut self.content
|
||||
}
|
||||
|
||||
pub fn add_to_end(&mut self, v: Self) -> bool {
|
||||
pub fn add_to_end(&mut self, v: Self) -> Option<usize> {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => false,
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Folder(_, vec, _) => {
|
||||
vec.push(v);
|
||||
true
|
||||
Some(vec.len() - 1)
|
||||
}
|
||||
QueueContent::Loop(..) => None,
|
||||
QueueContent::Random(q) => {
|
||||
q.push_back(v);
|
||||
Some(q.len() - 1)
|
||||
}
|
||||
QueueContent::Shuffle(_, map, elems, _) => {
|
||||
map.push(elems.len());
|
||||
elems.push(v);
|
||||
Some(map.len() - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn insert(&mut self, v: Self, index: usize) -> bool {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => false,
|
||||
QueueContent::Folder(_, vec, _) => {
|
||||
QueueContent::Folder(current, vec, _) => {
|
||||
if index <= vec.len() {
|
||||
if *current >= index {
|
||||
*current += 1;
|
||||
}
|
||||
vec.insert(index, v);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
QueueContent::Shuffle(_, map, elems, _) => {
|
||||
if index <= map.len() {
|
||||
map.insert(index, elems.len());
|
||||
elems.push(v);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(..) | QueueContent::Random(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +92,22 @@ 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::Loop(total, _done, inner) => {
|
||||
if *total == 0 {
|
||||
inner.len()
|
||||
} else {
|
||||
*total * inner.len()
|
||||
}
|
||||
}
|
||||
QueueContent::Shuffle(_, _, v, _) => v.iter().map(|v| v.len()).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
/// recursively descends the queue until the current active element is found, then returns it.
|
||||
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) {
|
||||
@@ -65,7 +116,9 @@ impl Queue {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Song(_) => Some(self),
|
||||
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)?),
|
||||
}
|
||||
}
|
||||
pub fn get_current_song(&self) -> Option<&SongId> {
|
||||
@@ -84,6 +137,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) {
|
||||
@@ -100,17 +154,107 @@ impl Queue {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Song(_) => None,
|
||||
QueueContent::Loop(total, current, inner) => {
|
||||
if let Some(v) = inner.get_next() {
|
||||
Some(v)
|
||||
} else if *total == 0 || current < total {
|
||||
inner.get_first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
QueueContent::Random(v) => v.get(v.len().saturating_sub(1))?.get_current(),
|
||||
QueueContent::Shuffle(i, map, elems, _) => elems.get(*map.get(*i + 1)?),
|
||||
}
|
||||
}
|
||||
pub fn get_first(&self) -> Option<&Self> {
|
||||
match &self.content {
|
||||
QueueContent::Song(..) => Some(self),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_index(&mut self) -> bool {
|
||||
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);
|
||||
o
|
||||
}
|
||||
pub fn init(&mut self, path: Vec<usize>, actions: &mut Vec<QueueAction>) {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(..) => {}
|
||||
QueueContent::Folder(_, v, _) => {
|
||||
if let Some(v) = v.first_mut() {
|
||||
v.init(path, actions);
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => inner.init(path, 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(current, map, elems, next) => {
|
||||
let mut new_map = (0..elems.len()).filter(|v| *v != *next).collect::<Vec<_>>();
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_actions(db: &mut Database, actions: Vec<QueueAction>) {
|
||||
for action in actions {
|
||||
match action {
|
||||
QueueAction::AddRandomSong(path) => {
|
||||
if !db.db_file.as_os_str().is_empty() {
|
||||
if let Some(song) = db.songs().keys().choose(&mut rand::thread_rng()) {
|
||||
db.apply_command(Command::QueueAdd(
|
||||
path,
|
||||
QueueContent::Song(*song).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
QueueAction::SetShuffle(path, shuf, next) => {
|
||||
if !db.db_file.as_os_str().is_empty() {
|
||||
db.apply_command(Command::QueueSetShuffle(path, shuf, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn advance_index_inner(&mut self, path: Vec<usize>, actions: &mut Vec<QueueAction>) -> bool {
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => false,
|
||||
QueueContent::Folder(index, contents, _) => {
|
||||
if let Some(c) = contents.get_mut(*index) {
|
||||
// inner value could advance index, do nothing.
|
||||
if c.advance_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 {
|
||||
@@ -118,6 +262,7 @@ impl Queue {
|
||||
// can advance
|
||||
*index += 1;
|
||||
if contents[*index].enabled {
|
||||
contents[*index].init(path, actions);
|
||||
break true;
|
||||
}
|
||||
} else {
|
||||
@@ -132,22 +277,113 @@ impl Queue {
|
||||
false
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(total, current, inner) => {
|
||||
let mut p = path.clone();
|
||||
p.push(0);
|
||||
if inner.advance_index_inner(p, actions) {
|
||||
true
|
||||
} else {
|
||||
*current += 1;
|
||||
if *total == 0 || *current < *total {
|
||||
inner.init(path, actions);
|
||||
true
|
||||
} else {
|
||||
*current = 0;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
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(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
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_index(&mut self, index: &Vec<usize>, depth: usize) {
|
||||
let i = index.get(depth).map(|v| *v).unwrap_or(0);
|
||||
pub fn set_index_db(db: &mut Database, index: &Vec<usize>) {
|
||||
let mut actions = vec![];
|
||||
db.queue.set_index_inner(index, 0, vec![], &mut actions);
|
||||
Self::handle_actions(db, actions);
|
||||
}
|
||||
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
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
build_index.push(i);
|
||||
match &mut self.content {
|
||||
QueueContent::Song(_) => {}
|
||||
QueueContent::Folder(idx, contents, _) => {
|
||||
*idx = i;
|
||||
for (i2, c) in contents.iter_mut().enumerate() {
|
||||
if i2 != i {
|
||||
c.set_index(&vec![], 0)
|
||||
}
|
||||
if i != *idx {
|
||||
*idx = i;
|
||||
}
|
||||
if let Some(c) = contents.get_mut(i) {
|
||||
c.set_index(index, depth + 1);
|
||||
c.init(build_index.clone(), actions);
|
||||
c.set_index_inner(index, depth + 1, build_index, actions);
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => {
|
||||
inner.init(build_index.clone(), actions);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +400,12 @@ impl Queue {
|
||||
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(_, map, elems, _) => map
|
||||
.get(*i)
|
||||
.and_then(|i| elems.get(*i))
|
||||
.and_then(|elem| elem.get_item_at_index(index, depth + 1)),
|
||||
}
|
||||
} else {
|
||||
Some(self)
|
||||
@@ -180,6 +422,14 @@ impl Queue {
|
||||
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::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)
|
||||
@@ -210,6 +460,32 @@ impl Queue {
|
||||
}
|
||||
}
|
||||
}
|
||||
QueueContent::Loop(_, _, inner) => {
|
||||
if depth + 1 < index.len() {
|
||||
inner.remove_by_index(index, depth + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -264,6 +540,23 @@ impl ToFromBytes for QueueContent {
|
||||
contents.to_bytes(s)?;
|
||||
name.to_bytes(s)?;
|
||||
}
|
||||
Self::Loop(total, current, inner) => {
|
||||
s.write_all(&[0b11000000])?;
|
||||
total.to_bytes(s)?;
|
||||
current.to_bytes(s)?;
|
||||
inner.to_bytes(s)?;
|
||||
}
|
||||
Self::Random(q) => {
|
||||
s.write_all(&[0b00110000])?;
|
||||
q.to_bytes(s)?;
|
||||
}
|
||||
Self::Shuffle(current, map, elems, next) => {
|
||||
s.write_all(&[0b00001100])?;
|
||||
current.to_bytes(s)?;
|
||||
map.to_bytes(s)?;
|
||||
elems.to_bytes(s)?;
|
||||
next.to_bytes(s)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -273,14 +566,26 @@ impl ToFromBytes for QueueContent {
|
||||
{
|
||||
let mut switch_on = [0];
|
||||
s.read_exact(&mut switch_on)?;
|
||||
Ok(if switch_on[0].count_ones() > 4 {
|
||||
Self::Song(ToFromBytes::from_bytes(s)?)
|
||||
} else {
|
||||
Self::Folder(
|
||||
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)?,
|
||||
)
|
||||
),
|
||||
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(
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
ToFromBytes::from_bytes(s)?,
|
||||
),
|
||||
_ => Self::Folder(0, vec![], "<invalid byte received>".to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, VecDeque},
|
||||
io::{Read, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
@@ -81,6 +81,32 @@ where
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
impl<C> ToFromBytes for VecDeque<C>
|
||||
where
|
||||
C: ToFromBytes,
|
||||
{
|
||||
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
self.len().to_bytes(s)?;
|
||||
for elem in self {
|
||||
elem.to_bytes(s)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
|
||||
where
|
||||
T: Read,
|
||||
{
|
||||
let len = ToFromBytes::from_bytes(s)?;
|
||||
let mut buf = VecDeque::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
buf.push_back(ToFromBytes::from_bytes(s)?);
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
impl<A> ToFromBytes for Option<A>
|
||||
where
|
||||
A: ToFromBytes,
|
||||
|
||||
@@ -67,7 +67,11 @@ impl Player {
|
||||
if let Some((source, _notif)) = &mut self.source {
|
||||
source.set_paused(true);
|
||||
}
|
||||
self.current_song_id = SongOpt::New(None);
|
||||
if let SongOpt::Some(id) | SongOpt::New(Some(id)) = self.current_song_id {
|
||||
self.current_song_id = SongOpt::New(Some(id));
|
||||
} else {
|
||||
self.current_song_id = SongOpt::New(None);
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, db: &mut Database) {
|
||||
if db.playing && self.source.is_none() {
|
||||
@@ -76,7 +80,10 @@ impl Player {
|
||||
self.current_song_id = SongOpt::New(Some(*song));
|
||||
} else {
|
||||
// db.playing, but no song in queue...
|
||||
db.apply_command(Command::Stop);
|
||||
}
|
||||
} else if !db.playing && self.source.is_some() {
|
||||
self.current_song_id = SongOpt::New(None);
|
||||
} else if let Some((_source, notif)) = &mut self.source {
|
||||
if let Ok(()) = notif.try_recv() {
|
||||
// song has finished playing
|
||||
@@ -103,34 +110,35 @@ impl Player {
|
||||
// new current song
|
||||
if let SongOpt::New(song_opt) = &self.current_song_id {
|
||||
// stop playback
|
||||
eprintln!("[play] stopping playback");
|
||||
// eprintln!("[play] stopping playback");
|
||||
self.manager.clear();
|
||||
if let Some(song_id) = song_opt {
|
||||
if db.playing {
|
||||
// start playback again
|
||||
if let Some(song) = db.get_song(song_id) {
|
||||
eprintln!("[play] starting playback...");
|
||||
// add our song
|
||||
let ext = match &song.location.rel_path.extension() {
|
||||
Some(s) => s.to_str().unwrap_or(""),
|
||||
None => "",
|
||||
};
|
||||
let (sound, notif) = Self::sound_from_bytes(
|
||||
ext,
|
||||
song.cached_data_now(db).expect("no cached data"),
|
||||
)
|
||||
.unwrap()
|
||||
.pausable()
|
||||
.with_async_completion_notifier();
|
||||
// add it
|
||||
let (sound, controller) = sound.controllable();
|
||||
self.source = Some((controller, notif));
|
||||
// and play it
|
||||
self.manager.play(Box::new(sound));
|
||||
eprintln!("[play] started playback");
|
||||
} else {
|
||||
panic!("invalid song ID: current_song_id not found in DB!");
|
||||
// start playback again
|
||||
if let Some(song) = db.get_song(song_id) {
|
||||
// eprintln!("[play] starting playback...");
|
||||
// add our song
|
||||
let ext = match &song.location.rel_path.extension() {
|
||||
Some(s) => s.to_str().unwrap_or(""),
|
||||
None => "",
|
||||
};
|
||||
if let Some(bytes) = song.cached_data_now(db) {
|
||||
match Self::sound_from_bytes(ext, bytes) {
|
||||
Ok(v) => {
|
||||
let (sound, notif) = v.pausable().with_async_completion_notifier();
|
||||
// add it
|
||||
let (sound, controller) = sound.controllable();
|
||||
self.source = Some((controller, notif));
|
||||
// and play it
|
||||
self.manager.play(Box::new(sound));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[player] Can't play, skipping! {e}");
|
||||
db.apply_command(Command::NextSong);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("invalid song ID: current_song_id not found in DB!");
|
||||
}
|
||||
self.current_song_id = SongOpt::Some(*song_id);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
pub mod get;
|
||||
|
||||
use std::{
|
||||
eprintln,
|
||||
io::Write,
|
||||
io::{BufRead, BufReader, Read, Write},
|
||||
net::{SocketAddr, TcpListener},
|
||||
path::PathBuf,
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
@@ -12,12 +14,13 @@ use crate::{
|
||||
data::{
|
||||
album::Album,
|
||||
artist::Artist,
|
||||
database::{Database, UpdateEndpoint},
|
||||
database::{Cover, Database, UpdateEndpoint},
|
||||
queue::Queue,
|
||||
song::Song,
|
||||
},
|
||||
load::ToFromBytes,
|
||||
player::Player,
|
||||
server::get::handle_one_connection_as_get,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -33,12 +36,14 @@ pub enum Command {
|
||||
QueueInsert(Vec<usize>, usize, Queue),
|
||||
QueueRemove(Vec<usize>),
|
||||
QueueGoto(Vec<usize>),
|
||||
QueueSetShuffle(Vec<usize>, Vec<usize>, usize),
|
||||
/// .id field is ignored!
|
||||
AddSong(Song),
|
||||
/// .id field is ignored!
|
||||
AddAlbum(Album),
|
||||
/// .id field is ignored!
|
||||
AddArtist(Artist),
|
||||
AddCover(Cover),
|
||||
ModifySong(Song),
|
||||
ModifyAlbum(Album),
|
||||
ModifyArtist(Artist),
|
||||
@@ -93,33 +98,41 @@ pub fn run_server(
|
||||
Ok(v) => {
|
||||
let command_sender = command_sender.clone();
|
||||
let db = Arc::clone(&database);
|
||||
// each connection gets its own thread, but they will be idle most of the time (waiting for data on the tcp stream)
|
||||
thread::spawn(move || loop {
|
||||
if let Ok((mut connection, con_addr)) = v.accept() {
|
||||
eprintln!("[info] TCP connection accepted from {con_addr}.");
|
||||
if let Ok((connection, con_addr)) = v.accept() {
|
||||
let command_sender = command_sender.clone();
|
||||
let db = Arc::clone(&db);
|
||||
thread::spawn(move || {
|
||||
// sync database
|
||||
let mut db = db.lock().unwrap();
|
||||
db.init_connection(&mut connection)?;
|
||||
// keep the client in sync:
|
||||
// the db will send all updates to the client once it is added to update_endpoints
|
||||
db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new(
|
||||
// try_clone is used here to split a TcpStream into Writer and Reader
|
||||
connection.try_clone().unwrap(),
|
||||
)));
|
||||
// drop the mutex lock
|
||||
drop(db);
|
||||
// read updates from the tcp stream and send them to the database, exit on EOF or Err
|
||||
loop {
|
||||
if let Ok(command) = Command::from_bytes(&mut connection) {
|
||||
command_sender.send(command).unwrap();
|
||||
} else {
|
||||
break;
|
||||
eprintln!("[info] TCP connection accepted from {con_addr}.");
|
||||
// each connection first has to send one line to tell us what it wants
|
||||
let mut connection = BufReader::new(connection);
|
||||
let mut line = String::new();
|
||||
if connection.read_line(&mut line).is_ok() {
|
||||
// based on that line, we adjust behavior
|
||||
match line.as_str().trim() {
|
||||
// sends all updates to this connection and reads commands from it
|
||||
"main" => {
|
||||
let connection = connection.into_inner();
|
||||
_ = handle_one_connection_as_main(
|
||||
db,
|
||||
&mut connection.try_clone().unwrap(),
|
||||
connection,
|
||||
&command_sender,
|
||||
)
|
||||
}
|
||||
// reads commands from the connection, but (unlike main) doesn't send any updates
|
||||
"control" => handle_one_connection_as_control(
|
||||
&mut connection,
|
||||
&command_sender,
|
||||
),
|
||||
"get" => _ = handle_one_connection_as_get(db, &mut connection),
|
||||
_ => {
|
||||
_ = connection
|
||||
.into_inner()
|
||||
.shutdown(std::net::Shutdown::Both)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok::<(), std::io::Error>(())
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -141,24 +154,39 @@ pub fn run_server(
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Connection: Sized + Send + 'static {
|
||||
type SendError: Send;
|
||||
fn send_command(&mut self, command: Command) -> Result<(), Self::SendError>;
|
||||
fn receive_updates(&mut self) -> Result<Vec<Command>, Self::SendError>;
|
||||
fn receive_update_blocking(&mut self) -> Result<Command, Self::SendError>;
|
||||
fn move_to_thread<F: FnMut(&mut Self, Command) -> bool + Send + 'static>(
|
||||
mut self,
|
||||
mut handler: F,
|
||||
) -> JoinHandle<Result<Self, Self::SendError>> {
|
||||
std::thread::spawn(move || loop {
|
||||
let update = self.receive_update_blocking()?;
|
||||
if handler(&mut self, update) {
|
||||
return Ok(self);
|
||||
}
|
||||
})
|
||||
pub fn handle_one_connection_as_main(
|
||||
db: Arc<Mutex<Database>>,
|
||||
connection: &mut impl Read,
|
||||
mut send_to: (impl Write + Sync + Send + 'static),
|
||||
command_sender: &mpsc::Sender<Command>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
// sync database
|
||||
let mut db = db.lock().unwrap();
|
||||
db.init_connection(&mut send_to)?;
|
||||
// keep the client in sync:
|
||||
// the db will send all updates to the client once it is added to update_endpoints
|
||||
db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new(
|
||||
// try_clone is used here to split a TcpStream into Writer and Reader
|
||||
send_to,
|
||||
)));
|
||||
// drop the mutex lock
|
||||
drop(db);
|
||||
handle_one_connection_as_control(connection, command_sender);
|
||||
Ok(())
|
||||
}
|
||||
pub fn handle_one_connection_as_control(
|
||||
connection: &mut impl Read,
|
||||
command_sender: &mpsc::Sender<Command>,
|
||||
) {
|
||||
// read updates from the tcp stream and send them to the database, exit on EOF or Err
|
||||
loop {
|
||||
if let Ok(command) = Command::from_bytes(connection) {
|
||||
command_sender.send(command).unwrap();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromBytes for Command {
|
||||
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||
where
|
||||
@@ -200,6 +228,12 @@ impl ToFromBytes for Command {
|
||||
s.write_all(&[0b00011011])?;
|
||||
index.to_bytes(s)?;
|
||||
}
|
||||
Self::QueueSetShuffle(path, map, next) => {
|
||||
s.write_all(&[0b10011011])?;
|
||||
path.to_bytes(s)?;
|
||||
map.to_bytes(s)?;
|
||||
next.to_bytes(s)?;
|
||||
}
|
||||
Self::AddSong(song) => {
|
||||
s.write_all(&[0b01010000])?;
|
||||
song.to_bytes(s)?;
|
||||
@@ -212,6 +246,10 @@ impl ToFromBytes for Command {
|
||||
s.write_all(&[0b01011100])?;
|
||||
artist.to_bytes(s)?;
|
||||
}
|
||||
Self::AddCover(cover) => {
|
||||
s.write_all(&[0b01011101])?;
|
||||
cover.to_bytes(s)?;
|
||||
}
|
||||
Self::ModifySong(song) => {
|
||||
s.write_all(&[0b10010000])?;
|
||||
song.to_bytes(s)?;
|
||||
@@ -259,12 +297,18 @@ 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)?,
|
||||
),
|
||||
0b01010000 => Self::AddSong(ToFromBytes::from_bytes(s)?),
|
||||
0b01010011 => Self::AddAlbum(ToFromBytes::from_bytes(s)?),
|
||||
0b01011100 => Self::AddArtist(ToFromBytes::from_bytes(s)?),
|
||||
0b10010000 => Self::AddSong(ToFromBytes::from_bytes(s)?),
|
||||
0b10010011 => Self::AddAlbum(ToFromBytes::from_bytes(s)?),
|
||||
0b10011100 => Self::AddArtist(ToFromBytes::from_bytes(s)?),
|
||||
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
|
||||
0b00110001 => Self::SetLibraryDirectory(ToFromBytes::from_bytes(s)?),
|
||||
_ => {
|
||||
eprintln!("unexpected byte when reading command; stopping playback.");
|
||||
|
||||
Reference in New Issue
Block a user