mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-12-14 11:56:16 +01:00
.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?),
|
||||
_ => {
|
||||
|
||||
Reference in New Issue
Block a user