perf(lib): lowercase each shared path once at scan time

Answering a search lowercased every shared path again for every query:
with a large share and the steady stream of searches the server forwards,
that is an allocation per file per search. The lowercase key is now
computed once when the share is scanned.
This commit is contained in:
Michel de Graaf
2026-09-07 16:25:42 +02:00
parent 833542377b
commit a9c68a29ca
+19 -4
View File
@@ -13,6 +13,9 @@ use std::path::{Path, PathBuf};
pub struct SharedFile { pub struct SharedFile {
/// Backslash-separated path exposed to peers, e.g. `music\album\song.mp3`. /// Backslash-separated path exposed to peers, e.g. `music\album\song.mp3`.
pub virtual_path: String, pub virtual_path: String,
/// `virtual_path` lowercased once at scan time, so answering a search does
/// not lowercase every shared path again.
search_key: String,
/// The real filesystem path used to serve the bytes. /// The real filesystem path used to serve the bytes.
pub real_path: PathBuf, pub real_path: PathBuf,
pub size: u64, pub size: u64,
@@ -106,10 +109,7 @@ impl Shares {
} }
self.files self.files
.iter() .iter()
.filter(|f| { .filter(|f| terms.iter().all(|t| f.search_key.contains(t.as_str())))
let haystack = f.virtual_path.to_lowercase();
terms.iter().all(|t| haystack.contains(t.as_str()))
})
.collect() .collect()
} }
@@ -200,6 +200,7 @@ fn scan_root(
} }
let virtual_path = virtual_path_for(root_name, root, &path); let virtual_path = virtual_path_for(root_name, root, &path);
files.push(SharedFile { files.push(SharedFile {
search_key: virtual_path.to_lowercase(),
virtual_path, virtual_path,
real_path: path, real_path: path,
size: meta.len(), size: meta.len(),
@@ -278,6 +279,20 @@ mod tests {
let _ = std::fs::remove_dir_all(root); let _ = std::fs::remove_dir_all(root);
} }
#[test]
fn a_mixed_case_path_matches_a_lowercase_query() {
let root = std::env::temp_dir()
.join(format!("soulseek-shares-case-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join("Live Set")).unwrap();
std::fs::write(root.join("Live Set").join("Opening TRACK.flac"), b"x")
.unwrap();
let shares = Shares::scan(&root).unwrap();
assert_eq!(shares.search("live opening track").len(), 1);
let _ = std::fs::remove_dir_all(root);
}
#[test] #[test]
fn search_is_case_insensitive_and_multi_term_and() { fn search_is_case_insensitive_and_multi_term_and() {
let root = temp_tree(); let root = temp_tree();