update dependencies #1

This commit is contained in:
Mark 2024-05-12 15:23:31 +02:00
parent 81ec0d6668
commit a316f6282e
3 changed files with 235 additions and 11 deletions

View File

@ -6,9 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
musicdb-lib = { path = "../musicdb-lib" }
clap = { version = "4.4.6", features = ["derive"] } clap = { version = "4.4.6", features = ["derive"] }
directories = "5.0.1" directories = "5.0.1"
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
regex = "1.9.3" regex = "1.9.3"
speedy2d = { version = "1.12.0", optional = true } speedy2d = { version = "1.12.0", optional = true }
toml = "0.7.6" toml = "0.7.6"

View File

@ -1,8 +1,7 @@
use std::{ use std::{
fs, fs,
io::BufRead, io::{BufRead, BufReader, Read, Write},
io::{BufReader, Read, Write}, path::{Path, PathBuf},
path::Path,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
@ -12,6 +11,7 @@ pub struct Client<T: Write + Read>(BufReader<T>);
impl<T: Write + Read> Client<T> { impl<T: Write + Read> Client<T> {
pub fn new(mut con: BufReader<T>) -> std::io::Result<Self> { pub fn new(mut con: BufReader<T>) -> std::io::Result<Self> {
writeln!(con.get_mut(), "get")?; writeln!(con.get_mut(), "get")?;
con.get_mut().flush()?;
Ok(Self(con)) Ok(Self(con))
} }
pub fn cover_bytes(&mut self, id: CoverId) -> Result<Result<Vec<u8>, String>, std::io::Error> { pub fn cover_bytes(&mut self, id: CoverId) -> Result<Result<Vec<u8>, String>, std::io::Error> {
@ -20,9 +20,9 @@ impl<T: Write + Read> Client<T> {
"{}", "{}",
con_get_encode_string(&format!("cover-bytes\n{id}")) con_get_encode_string(&format!("cover-bytes\n{id}"))
)?; )?;
self.0.get_mut().flush()?;
let mut response = String::new(); let mut response = String::new();
self.0.read_line(&mut response)?; self.0.read_line(&mut response)?;
let response = con_get_decode_line(&response);
if response.starts_with("len: ") { if response.starts_with("len: ") {
if let Ok(len) = response[4..].trim().parse() { if let Ok(len) = response[4..].trim().parse() {
let mut bytes = vec![0; len]; let mut bytes = vec![0; len];
@ -41,9 +41,9 @@ impl<T: Write + Read> Client<T> {
"{}", "{}",
con_get_encode_string(&format!("song-file\n{id}",)) con_get_encode_string(&format!("song-file\n{id}",))
)?; )?;
self.0.get_mut().flush()?;
let mut response = String::new(); let mut response = String::new();
self.0.read_line(&mut response)?; self.0.read_line(&mut response)?;
let response = con_get_decode_line(&response);
if response.starts_with("len: ") { if response.starts_with("len: ") {
if let Ok(len) = response[4..].trim().parse() { if let Ok(len) = response[4..].trim().parse() {
let mut bytes = vec![0; len]; let mut bytes = vec![0; len];
@ -62,9 +62,9 @@ impl<T: Write + Read> Client<T> {
"{}", "{}",
con_get_encode_string(&format!("custom-file\n{path}",)) con_get_encode_string(&format!("custom-file\n{path}",))
)?; )?;
self.0.get_mut().flush()?;
let mut response = String::new(); let mut response = String::new();
self.0.read_line(&mut response)?; self.0.read_line(&mut response)?;
let response = con_get_decode_line(&response);
if response.starts_with("len: ") { if response.starts_with("len: ") {
if let Ok(len) = response[4..].trim().parse() { if let Ok(len) = response[4..].trim().parse() {
let mut bytes = vec![0; len]; let mut bytes = vec![0; len];
@ -77,15 +77,91 @@ impl<T: Write + Read> Client<T> {
Ok(Err(response)) Ok(Err(response))
} }
} }
pub fn song_file_by_path(
&mut self,
path: &str,
) -> Result<Result<Vec<u8>, String>, std::io::Error> {
writeln!(
self.0.get_mut(),
"{}",
con_get_encode_string(&format!("song-file-by-path\n{path}",))
)?;
self.0.get_mut().flush()?;
let mut response = String::new();
self.0.read_line(&mut 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))
}
}
/// tell the server to search for files that are not in its song database.
///
/// ## `extensions`:
/// If `None`, the server uses a default set of music-related extensions (`[".mp3", ...]`).
/// If `Some([])`, allow all extensions, even ones like `.jpg` and files without extensions.
/// If `Some(...)`, only allow the specified extensions. Note: These are actually suffixes, for example `mp3` would allow a file named `test_mp3`, while `.mp3` would only allow `test.mp3`.
/// Because of this, you usually want to include the `.` before the extension, and double extensions like `.tar.gz` are also supported.
pub fn find_unused_song_files(
&mut self,
extensions: Option<&[&str]>,
) -> Result<Result<Vec<(String, bool)>, String>, std::io::Error> {
let mut str = "find-unused-song-files".to_owned();
if let Some(extensions) = extensions {
if extensions.is_empty() {
str.push_str("\nextensions");
} else {
str.push_str("\nextensions=");
for (i, ext) in extensions.iter().enumerate() {
if i > 0 {
str.push(':');
}
str.push_str(ext);
}
}
}
writeln!(self.0.get_mut(), "{}", con_get_encode_string(&str))?;
self.0.get_mut().flush()?;
let mut response = String::new();
self.0.read_line(&mut response)?;
let len_line = response.trim();
if len_line.starts_with("len: ") {
if let Ok(len) = len_line[4..].trim().parse() {
let mut out = Vec::with_capacity(len);
for _ in 0..len {
let mut line = String::new();
self.0.read_line(&mut line)?;
let line = line.trim_end_matches(['\n', '\r']);
if line.starts_with('#') {
out.push((line[1..].to_owned(), false))
} else if line.starts_with('!') {
out.push((line[1..].to_owned(), true))
} else {
return Ok(Err(format!("bad line-format: {line}")));
}
}
Ok(Ok(out))
} else {
Ok(Err(format!("bad len in len-line: {len_line}")))
}
} else {
Ok(Err(format!("bad len-line: {len_line}")))
}
}
} }
pub fn handle_one_connection_as_get( pub fn handle_one_connection_as_get(
db: Arc<Mutex<Database>>, db: Arc<Mutex<Database>>,
connection: &mut BufReader<impl Read + Write>, connection: &mut BufReader<impl Read + Write>,
) -> Result<(), std::io::Error> { ) -> Result<(), std::io::Error> {
let mut line = String::new();
loop { loop {
line.clear(); let mut line = String::new();
if connection.read_line(&mut line).is_ok() { if connection.read_line(&mut line).is_ok() {
if line.is_empty() { if line.is_empty() {
return Ok(()); return Ok(());
@ -145,7 +221,12 @@ pub fn handle_one_connection_as_get(
parent = None; parent = None;
} }
if let Some(parent) = parent { if let Some(parent) = parent {
fs::read(parent.join(path)).ok() let path = parent.join(path);
if path.starts_with(parent) {
fs::read(path).ok()
} else {
None
}
} else { } else {
None None
} }
@ -156,6 +237,78 @@ pub fn handle_one_connection_as_get(
writeln!(connection.get_mut(), "no data")?; writeln!(connection.get_mut(), "no data")?;
} }
} }
"song-file-by-path" => {
if let Some(bytes) = request.next().and_then(|path| {
let db = db.lock().unwrap();
let mut parent = Some(db.lib_directory.clone());
// check for malicious paths [TODO: Improve]
if Path::new(path).is_absolute() {
parent = None;
}
if let Some(parent) = parent {
let path = parent.join(path);
if path.starts_with(parent) {
fs::read(path).ok()
} else {
None
}
} else {
None
}
}) {
writeln!(connection.get_mut(), "len: {}", bytes.len())?;
connection.get_mut().write_all(&bytes)?;
} else {
writeln!(connection.get_mut(), "no data")?;
}
}
"find-unused-song-files" => {
// configure search
let mut extensions = None;
loop {
if let Some(line) = request.next() {
if let Some((key, value)) = line.split_once("=") {
match key.trim() {
"extensions" => {
extensions = Some(Some(
value
.split(':')
.map(|v| v.trim().to_owned())
.collect::<Vec<_>>(),
))
}
_ => (),
}
} else {
match line.trim() {
"extensions" => extensions = Some(None),
_ => (),
}
}
} else {
break;
}
}
// search
let lib_dir = db.lock().unwrap().lib_directory.clone();
let unused = find_unused_song_files(
&db,
&lib_dir,
&FindUnusedSongFilesConfig {
extensions: extensions
.unwrap_or_else(|| Some(vec![".mp3".to_owned()])),
},
);
writeln!(connection.get_mut(), "len: {}", unused.len())?;
for path in unused {
if let Some(path) = path.to_str().filter(|v| !v.contains('\n')) {
writeln!(connection.get_mut(), "#{path}")?;
} else {
let path = path.to_string_lossy().replace('\n', "");
writeln!(connection.get_mut(), "!{path}")?;
}
}
}
_ => {} _ => {}
} }
} }
@ -195,3 +348,74 @@ pub fn con_get_encode_string(line: &str) -> String {
} }
o o
} }
fn find_unused_song_files(
db: &Arc<Mutex<Database>>,
path: &impl AsRef<Path>,
cfg: &FindUnusedSongFilesConfig,
) -> Vec<PathBuf> {
let mut files = vec![];
find_unused_song_files_internal(db, path, &"", cfg, &mut files, &mut vec![], true);
files
}
struct FindUnusedSongFilesConfig {
extensions: Option<Vec<String>>,
}
fn find_unused_song_files_internal(
db: &Arc<Mutex<Database>>,
path: &impl AsRef<Path>,
rel_path: &impl AsRef<Path>,
cfg: &FindUnusedSongFilesConfig,
unused_files: &mut Vec<PathBuf>,
files_buf: &mut Vec<PathBuf>,
is_final: bool,
) {
if let Ok(rd) = std::fs::read_dir(path.as_ref()) {
for entry in rd {
if let Ok(entry) = entry {
if let Ok(file_type) = entry.file_type() {
let path = entry.path();
let rel_path = rel_path.as_ref().join(entry.file_name());
if file_type.is_dir() {
find_unused_song_files_internal(
db,
&path,
&rel_path,
cfg,
unused_files,
files_buf,
false,
);
} else if file_type.is_file() {
if match &cfg.extensions {
None => true,
Some(exts) => {
if let Some(name) = path.file_name().and_then(|v| v.to_str()) {
exts.iter().any(|ext| name.ends_with(ext))
} else {
false
}
}
} {
files_buf.push(rel_path);
}
}
}
}
}
}
if (is_final && files_buf.len() > 0) || files_buf.len() > 50 {
let db = db.lock().unwrap();
for song in db.songs().values() {
if let Some(i) = files_buf
.iter()
.position(|path| path == &song.location.rel_path)
{
files_buf.remove(i);
}
}
unused_files.extend(std::mem::replace(files_buf, vec![]).into_iter());
}
}

View File

@ -6,11 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
musicdb-lib = { path = "../musicdb-lib", features = ["playback"] }
axum = { version = "0.6.19", features = ["headers"] } axum = { version = "0.6.19", features = ["headers"] }
clap = { version = "4.4.6", features = ["derive"] } clap = { version = "4.4.6", features = ["derive"] }
futures = "0.3.28" futures = "0.3.28"
headers = "0.3.8" headers = "0.3.8"
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib", features = ["playback"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] } tokio = { version = "1.0", features = ["full"] }