From c18ca38a087454284305d7fc226f523d62bc9085 Mon Sep 17 00:00:00 2001 From: serverjonas Date: Tue, 9 Jun 2026 14:37:51 +0200 Subject: [PATCH] add fuzzy search --- daemon/Cargo.lock | 7 +++ daemon/Cargo.toml | 1 + daemon/src/protocol.rs | 1 + daemon/src/socket.rs | 36 +++++++++++--- daemon/src/state.rs | 107 +++++++++++++++++++++++++++++++++++------ 5 files changed, 129 insertions(+), 23 deletions(-) diff --git a/daemon/Cargo.lock b/daemon/Cargo.lock index 61724e2..1544f0a 100644 --- a/daemon/Cargo.lock +++ b/daemon/Cargo.lock @@ -862,6 +862,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "strsim", ] [[package]] @@ -876,6 +877,12 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "symphonia" version = "0.5.5" diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index 50b9ad8..bcba8f2 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -9,3 +9,4 @@ rodio = "0.19" serde = { version = "1", features = ["derive"] } serde_json = "1" dirs = "5" +strsim = "0.11" diff --git a/daemon/src/protocol.rs b/daemon/src/protocol.rs index f59c52c..a661f53 100644 --- a/daemon/src/protocol.rs +++ b/daemon/src/protocol.rs @@ -6,4 +6,5 @@ pub enum Command { Add(String), Del(String), Init(String), + Search(String), } diff --git a/daemon/src/socket.rs b/daemon/src/socket.rs index c549977..4f85049 100644 --- a/daemon/src/socket.rs +++ b/daemon/src/socket.rs @@ -22,21 +22,41 @@ pub fn start_socket(state: Arc>) { let msg = String::from_utf8_lossy(&buf[..size]); if let Ok(cmd) = serde_json::from_str::(&msg) { - handle(cmd, state); + let response = handle(cmd, state); + let _ = stream.write_all(response.as_bytes()); + } else { + let _ = stream.write_all(b"ERROR: Invalid JSON"); } - - let _ = stream.write_all(b"OK"); }); } } -fn handle(cmd: Command, state: Arc>) { +fn handle(cmd: Command, state: Arc>) -> String { let mut state = state.lock().unwrap(); match cmd { - Command::Play(song) => state.play(song), - Command::Add(song) => state.add(song), - Command::Del(song) => state.delete(song), - Command::Init(song) => state.init(song), + Command::Play(query) => { + state.play(&query); + format!("OK: Playing {}", query) + } + Command::Add(query) => { + state.add(&query); + format!("OK: Added {}", query) + } + Command::Del(query) => { + state.delete(&query); + format!("OK: Deleted {}", query) + } + Command::Init(song) => { + state.init(song.clone()); + format!("OK: Downloading {}", song) + } + Command::Search(query) => { + if let Some(song) = state.search(&query) { + serde_json::to_string(&song).unwrap_or_else(|_| "ERROR: Serialization failed".to_string()) + } else { + "ERROR: Song not found".to_string() + } + } } } diff --git a/daemon/src/state.rs b/daemon/src/state.rs index 012689b..3eb037b 100644 --- a/daemon/src/state.rs +++ b/daemon/src/state.rs @@ -1,6 +1,8 @@ use std::process::Command as SysCommand; +use strsim::jaro_winkler; +use serde::{Serialize, Deserialize}; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Song { pub name: String, pub path: String, @@ -19,24 +21,29 @@ impl DaemonState { } } - pub fn play(&mut self, name: String) { - let path = crate::paths::song_path(&name); - - self.queue.insert(0, Song { name, path }); + pub fn play(&mut self, query: &str) { + if let Some(song) = self.search(query) { + self.queue.insert(0, song); + } else { + eprintln!("Song nicht gefunden: {}", query); + } } - pub fn add(&mut self, name: String) { - let path = crate::paths::song_path(&name); - - self.queue.push(Song { name, path }); + pub fn add(&mut self, query: &str) { + if let Some(song) = self.search(query) { + self.queue.push(song); + } else { + eprintln!("Song nicht gefunden: {}", query); + } } - pub fn delete(&mut self, name: String) { - let path = crate::paths::song_path(&name); - - let _ = std::fs::remove_file(&path); - - self.queue.retain(|s| s.name != name); + pub fn delete(&mut self, query: &str) { + if let Some(song) = self.search(query) { + let _ = std::fs::remove_file(&song.path); + self.queue.retain(|s| s.name != song.name); + } else { + eprintln!("Song nicht gefunden: {}", query); + } } pub fn init(&mut self, name: String) { @@ -63,4 +70,74 @@ impl DaemonState { path, }); } + + pub fn search(&self, query: &str) -> Option { + use std::fs; + + let songs_dir = crate::paths::songs_dir(); + let query_lower = query.to_lowercase(); + + // Alle .mp3 Dateien aus songs_dir lesen + let entries = match fs::read_dir(&songs_dir) { + Ok(entries) => entries, + Err(_) => return None, + }; + + let mut best_match: Option<(String, f64)> = None; + const MIN_SCORE: f64 = 0.65; // Threshold - nicht zu aggressiv + + for entry in entries { + if let Ok(entry) = entry { + if let Ok(metadata) = entry.metadata() { + if metadata.is_file() { + if let Some(filename) = entry.file_name().to_str() { + // .mp3 entfernen + let song_name = filename.strip_suffix(".mp3") + .or_else(|| filename.strip_suffix(".webm")) + .unwrap_or(filename); + + let song_name_lower = song_name.to_lowercase(); + + // Exakte Übereinstimmung (case-insensitive) + if song_name_lower == query_lower { + return Some(Song { + name: song_name.to_string(), + path: entry.path().to_string_lossy().to_string(), + }); + } + + // Fuzzy match mit Jaro-Winkler + let score = jaro_winkler(&song_name_lower, &query_lower); + + if score > MIN_SCORE { + if best_match.is_none() || score > best_match.as_ref().unwrap().1 { + best_match = Some(( + entry.path().to_string_lossy().to_string(), + score + )); + } + } + } + } + } + } + } + + // Wenn fuzzy match gefunden, returne es + if let Some((path, _)) = best_match { + if let Some(filename) = std::path::Path::new(&path) + .file_name() + .and_then(|f| f.to_str()) { + let song_name = filename.strip_suffix(".mp3") + .or_else(|| filename.strip_suffix(".webm")) + .unwrap_or(filename); + return Some(Song { + name: song_name.to_string(), + path, + }); + } + } + + None + } }