This commit is contained in:
Mark
2023-09-07 21:53:05 +02:00
parent 0e5e33367d
commit ac16628c31
16 changed files with 1173 additions and 623 deletions

View File

@@ -1,7 +1,7 @@
use std::{
collections::HashMap,
fs::{self, File},
io::{BufReader, Write},
io::{BufReader, Read, Write},
path::PathBuf,
sync::{mpsc, Arc, Mutex},
time::{Duration, Instant},
@@ -36,7 +36,11 @@ pub struct Database {
/// true if a song is/should be playing
pub playing: bool,
pub command_sender: Option<mpsc::Sender<Command>>,
pub remote_server_as_song_file_source:
Option<Arc<Mutex<crate::server::get::Client<Box<dyn ClientIo>>>>>,
}
pub trait ClientIo: Read + Write + Send {}
impl<T: Read + Write + Send> ClientIo for T {}
// for custom server implementations, this enum should allow you to deal with updates from any context (writers such as tcp streams, sync/async mpsc senders, or via closure as a fallback)
pub enum UpdateEndpoint {
Bytes(Box<dyn Write + Sync + Send>),
@@ -53,6 +57,9 @@ impl Database {
// exit
panic!("DatabasePanic: {msg}");
}
pub fn is_client(&self) -> bool {
self.db_file.as_os_str().is_empty()
}
pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf {
self.lib_directory.join(&location.rel_path)
}
@@ -177,6 +184,28 @@ impl Database {
self.songs.insert(song.id, song)
}
pub fn remove_song(&mut self, song: SongId) -> Option<Song> {
if let Some(removed) = self.songs.remove(&song) {
Some(removed)
} else {
None
}
}
pub fn remove_album(&mut self, song: SongId) -> Option<Song> {
if let Some(removed) = self.songs.remove(&song) {
Some(removed)
} else {
None
}
}
pub fn remove_artist(&mut self, song: SongId) -> Option<Song> {
if let Some(removed) = self.songs.remove(&song) {
Some(removed)
} else {
None
}
}
pub fn init_connection<T: Write>(&self, con: &mut T) -> Result<(), std::io::Error> {
// TODO! this is slow because it clones everything - there has to be a better way...
Command::SyncDatabase(
@@ -277,6 +306,15 @@ impl Database {
Command::ModifyArtist(artist) => {
_ = self.update_artist(artist);
}
Command::RemoveSong(song) => {
_ = self.remove_song(song);
}
Command::RemoveAlbum(album) => {
_ = self.remove_album(album);
}
Command::RemoveArtist(artist) => {
_ = self.remove_artist(artist);
}
Command::SetLibraryDirectory(new_dir) => {
self.lib_directory = new_dir;
}
@@ -303,6 +341,7 @@ impl Database {
update_endpoints: vec![],
playing: false,
command_sender: None,
remote_server_as_song_file_source: None,
}
}
pub fn new_empty(path: PathBuf, lib_dir: PathBuf) -> Self {
@@ -319,6 +358,7 @@ impl Database {
update_endpoints: vec![],
playing: false,
command_sender: None,
remote_server_as_song_file_source: None,
}
}
pub fn load_database(path: PathBuf) -> Result<Self, std::io::Error> {
@@ -339,6 +379,7 @@ impl Database {
update_endpoints: vec![],
playing: false,
command_sender: None,
remote_server_as_song_file_source: None,
})
}
/// saves the database's contents. save path can be overridden

View File

@@ -229,7 +229,7 @@ impl Queue {
for action in actions {
match action {
QueueAction::AddRandomSong(path) => {
if !db.db_file.as_os_str().is_empty() {
if !db.is_client() {
if let Some(song) = db.songs().keys().choose(&mut rand::thread_rng()) {
db.apply_command(Command::QueueAdd(
path,
@@ -239,7 +239,7 @@ impl Queue {
}
}
QueueAction::SetShuffle(path, shuf, next) => {
if !db.db_file.as_os_str().is_empty() {
if !db.is_client() {
db.apply_command(Command::QueueSetShuffle(path, shuf, next));
}
}

View File

@@ -1,7 +1,7 @@
use std::{
fmt::Display,
io::{Read, Write},
path::Path,
path::{Path, PathBuf},
sync::{Arc, Mutex},
thread::JoinHandle,
};
@@ -9,7 +9,8 @@ use std::{
use crate::load::ToFromBytes;
use super::{
database::Database, AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId,
database::{ClientIo, Database},
AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId,
};
#[derive(Clone, Debug)]
@@ -67,11 +68,13 @@ impl Song {
Some(Err(_)) | Some(Ok(_)) => false,
};
if start_thread {
let path = db.get_path(&self.location);
let src = if let Some(dlcon) = &db.remote_server_as_song_file_source {
Err((self.id, Arc::clone(dlcon)))
} else {
Ok(db.get_path(&self.location))
};
*cd = Some(Err(std::thread::spawn(move || {
eprintln!("[info] thread started");
let data = Self::load_data(&path)?;
eprintln!("[info] thread stopping after loading {path:?}");
let data = Self::load_data(src)?;
Some(Arc::new(data))
})));
true
@@ -97,7 +100,12 @@ impl Song {
let mut cd = self.cached_data.lock().unwrap();
*cd = match cd.take() {
None => {
if let Some(v) = Self::load_data(db.get_path(&self.location)) {
let src = if let Some(dlcon) = &db.remote_server_as_song_file_source {
Err((self.id, Arc::clone(dlcon)))
} else {
Ok(db.get_path(&self.location))
};
if let Some(v) = Self::load_data(src) {
Some(Ok(Arc::new(v)))
} else {
None
@@ -113,16 +121,43 @@ impl Song {
drop(cd);
self.cached_data()
}
fn load_data<P: AsRef<Path>>(path: P) -> Option<Vec<u8>> {
eprintln!("[info] loading song from {:?}", path.as_ref());
match std::fs::read(&path) {
Ok(v) => {
eprintln!("[info] loaded song from {:?}", path.as_ref());
Some(v)
fn load_data(
src: Result<
PathBuf,
(
SongId,
Arc<Mutex<crate::server::get::Client<Box<dyn ClientIo>>>>,
),
>,
) -> Option<Vec<u8>> {
match src {
Ok(path) => {
eprintln!("[info] loading song from {:?}", path);
match std::fs::read(&path) {
Ok(v) => {
eprintln!("[info] loaded song from {:?}", path);
Some(v)
}
Err(e) => {
eprintln!("[info] error loading {:?}: {e:?}", path);
None
}
}
}
Err(e) => {
eprintln!("[info] error loading {:?}: {e:?}", path.as_ref());
None
Err((id, dlcon)) => {
eprintln!("[info] loading song {id}");
match dlcon
.lock()
.unwrap()
.song_file(id, true)
.expect("problem with downloader connection...")
{
Ok(data) => Some(data),
Err(e) => {
eprintln!("[info] error loading song {id}: {e}");
None
}
}
}
}
}

View File

@@ -4,7 +4,7 @@ use std::{
sync::{Arc, Mutex},
};
use crate::data::{database::Database, CoverId};
use crate::data::{database::Database, CoverId, SongId};
pub struct Client<T: Write + Read>(BufReader<T>);
impl<T: Write + Read> Client<T> {
@@ -33,6 +33,34 @@ impl<T: Write + Read> Client<T> {
Ok(Err(response))
}
}
pub fn song_file(
&mut self,
id: SongId,
blocking: bool,
) -> Result<Result<Vec<u8>, String>, std::io::Error> {
writeln!(
self.0.get_mut(),
"{}",
con_get_encode_string(&format!(
"song-file{}\n{id}",
if blocking { "-blocking" } else { "" }
))
)?;
let mut response = String::new();
self.0.read_line(&mut response)?;
let response = con_get_decode_line(&response);
if response.starts_with("len: ") {
if let Ok(len) = response[4..].trim().parse() {
let mut bytes = vec![0; len];
self.0.read_exact(&mut bytes)?;
Ok(Ok(bytes))
} else {
Ok(Err(response))
}
} else {
Ok(Err(response))
}
}
}
pub fn handle_one_connection_as_get(
@@ -72,6 +100,40 @@ pub fn handle_one_connection_as_get(
writeln!(connection.get_mut(), "no cover")?;
}
}
"song-file" => {
if let Some(bytes) =
request
.next()
.and_then(|id| id.parse().ok())
.and_then(|id| {
db.lock()
.unwrap()
.get_song(&id)
.and_then(|song| song.cached_data())
})
{
writeln!(connection.get_mut(), "len: {}", bytes.len())?;
connection.get_mut().write_all(&bytes)?;
} else {
writeln!(connection.get_mut(), "no data")?;
}
}
"song-file-blocking" => {
if let Some(bytes) =
request
.next()
.and_then(|id| id.parse().ok())
.and_then(|id| {
let db = db.lock().unwrap();
db.get_song(&id).and_then(|song| song.cached_data_now(&db))
})
{
writeln!(connection.get_mut(), "len: {}", bytes.len())?;
connection.get_mut().write_all(&bytes)?;
} else {
writeln!(connection.get_mut(), "no data")?;
}
}
_ => {}
}
}

View File

@@ -6,7 +6,7 @@ use std::{
net::{SocketAddr, TcpListener},
path::PathBuf,
sync::{mpsc, Arc, Mutex},
thread::{self, JoinHandle},
thread,
time::Duration,
};
@@ -17,6 +17,7 @@ use crate::{
database::{Cover, Database, UpdateEndpoint},
queue::Queue,
song::Song,
AlbumId, ArtistId, SongId,
},
load::ToFromBytes,
player::Player,
@@ -46,6 +47,9 @@ pub enum Command {
AddCover(Cover),
ModifySong(Song),
ModifyAlbum(Album),
RemoveSong(SongId),
RemoveAlbum(AlbumId),
RemoveArtist(ArtistId),
ModifyArtist(Artist),
SetLibraryDirectory(PathBuf),
}
@@ -262,6 +266,18 @@ impl ToFromBytes for Command {
s.write_all(&[0b10011100])?;
artist.to_bytes(s)?;
}
Self::RemoveSong(song) => {
s.write_all(&[0b11010000])?;
song.to_bytes(s)?;
}
Self::RemoveAlbum(album) => {
s.write_all(&[0b11010011])?;
album.to_bytes(s)?;
}
Self::RemoveArtist(artist) => {
s.write_all(&[0b11011100])?;
artist.to_bytes(s)?;
}
Self::SetLibraryDirectory(path) => {
s.write_all(&[0b00110001])?;
path.to_bytes(s)?;
@@ -308,6 +324,9 @@ impl ToFromBytes for Command {
0b10010000 => Self::ModifySong(ToFromBytes::from_bytes(s)?),
0b10010011 => Self::ModifyAlbum(ToFromBytes::from_bytes(s)?),
0b10011100 => Self::ModifyArtist(ToFromBytes::from_bytes(s)?),
0b11010000 => Self::RemoveSong(ToFromBytes::from_bytes(s)?),
0b11010011 => Self::RemoveAlbum(ToFromBytes::from_bytes(s)?),
0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
0b00110001 => Self::SetLibraryDirectory(ToFromBytes::from_bytes(s)?),
_ => {