From a9c68a29ca8f3f6565fa04fcf9118421f9c15bdd Mon Sep 17 00:00:00 2001 From: Michel de Graaf Date: Mon, 7 Sep 2026 16:25:42 +0200 Subject: [PATCH] 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. --- soulseek-rs-lib/src/shares/mod.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/soulseek-rs-lib/src/shares/mod.rs b/soulseek-rs-lib/src/shares/mod.rs index 49fc9ad..341ec89 100644 --- a/soulseek-rs-lib/src/shares/mod.rs +++ b/soulseek-rs-lib/src/shares/mod.rs @@ -13,6 +13,9 @@ use std::path::{Path, PathBuf}; pub struct SharedFile { /// Backslash-separated path exposed to peers, e.g. `music\album\song.mp3`. 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. pub real_path: PathBuf, pub size: u64, @@ -106,10 +109,7 @@ impl Shares { } self.files .iter() - .filter(|f| { - let haystack = f.virtual_path.to_lowercase(); - terms.iter().all(|t| haystack.contains(t.as_str())) - }) + .filter(|f| terms.iter().all(|t| f.search_key.contains(t.as_str()))) .collect() } @@ -200,6 +200,7 @@ fn scan_root( } let virtual_path = virtual_path_for(root_name, root, &path); files.push(SharedFile { + search_key: virtual_path.to_lowercase(), virtual_path, real_path: path, size: meta.len(), @@ -278,6 +279,20 @@ mod tests { 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] fn search_is_case_insensitive_and_multi_term_and() { let root = temp_tree();