add fuzzy search

This commit is contained in:
2026-06-09 14:37:51 +02:00
parent 6551d063e6
commit c18ca38a08
5 changed files with 129 additions and 23 deletions
+92 -15
View File
@@ -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<Song> {
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
}
}