small improvements idk i forgot i had a git repo for this project

This commit is contained in:
Mark
2023-08-24 16:15:01 +02:00
parent 0ae0126f04
commit 9fbe67012e
17 changed files with 1894 additions and 455 deletions

View File

@@ -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"

View File

@@ -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))),
})
}
}

View File

@@ -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()),
})
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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.");