add fuzzy search
This commit is contained in:
+92
-15
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user