aboutsummaryrefslogtreecommitdiffstats
path: root/src/commands/autocomplete.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/commands/autocomplete.rs')
-rw-r--r--src/commands/autocomplete.rs24
1 files changed, 18 insertions, 6 deletions
diff --git a/src/commands/autocomplete.rs b/src/commands/autocomplete.rs
index d05a48b..4cefd67 100644
--- a/src/commands/autocomplete.rs
+++ b/src/commands/autocomplete.rs
@@ -20,7 +20,7 @@ const AC_SEARCH_TIMEOUT: Duration = Duration::from_secs(2);
20 20
21/// Value used for the mock "searching" placeholder — recognisable by the 21/// Value used for the mock "searching" placeholder — recognisable by the
22/// command handler as a no-op. 22/// command handler as a no-op.
23const MOCK_SEARCHING: &str = "\u{231B}..."; 23pub(crate) const MOCK_SEARCHING: &str = "\u{231B}...";
24 24
25fn truncate_choice(s: &str) -> String { 25fn truncate_choice(s: &str) -> String {
26 if s.chars().count() <= MAX_CHOICE_LEN { 26 if s.chars().count() <= MAX_CHOICE_LEN {
@@ -98,6 +98,12 @@ type SearchCache = Arc<
98 tokio::sync::Mutex<HashMap<String, (Vec<copyparty::SearchHit>, std::time::Instant)>>, 98 tokio::sync::Mutex<HashMap<String, (Vec<copyparty::SearchHit>, std::time::Instant)>>,
99>; 99>;
100 100
101fn search_cache_key(partial: &str, extensions: &[&str]) -> String {
102 // The extension filter changes the result set, so it must be part of the
103 // key; otherwise `/music` and `/upload` poison each other's cache.
104 format!("{partial}\u{1f}{}", extensions.join(","))
105}
106
101fn spawn_search( 107fn spawn_search(
102 cache: SearchCache, 108 cache: SearchCache,
103 client: copyparty::CopypartyClient, 109 client: copyparty::CopypartyClient,
@@ -114,10 +120,10 @@ fn spawn_search(
114 Ok(hits) => { 120 Ok(hits) => {
115 debug!(%query, count = hits.len(), "background search completed"); 121 debug!(%query, count = hits.len(), "background search completed");
116 // Always fill the shared search cache 122 // Always fill the shared search cache
117 cache 123 cache.lock().await.insert(
118 .lock() 124 search_cache_key(&query, &exts),
119 .await 125 (hits.clone(), std::time::Instant::now()),
120 .insert(query, (hits.clone(), std::time::Instant::now())); 126 );
121 // Send to foreground (ignores error if receiver was dropped) 127 // Send to foreground (ignores error if receiver was dropped)
122 let _ = tx.send(hits); 128 let _ = tx.send(hits);
123 } 129 }
@@ -143,8 +149,14 @@ async fn timed_search(
143 partial: &str, 149 partial: &str,
144 extensions: &[&str], 150 extensions: &[&str],
145) -> Vec<serenity::AutocompleteChoice> { 151) -> Vec<serenity::AutocompleteChoice> {
152 // The placeholder is a UI hint, not a real query.
153 if partial == MOCK_SEARCHING {
154 return vec![];
155 }
156
146 // 1. Shared search cache hit? 157 // 1. Shared search cache hit?
147 if let Some(hits) = ctx.data().ac_search_cache_lookup(partial).await 158 let cache_key = search_cache_key(partial, extensions);
159 if let Some(hits) = ctx.data().ac_search_cache_lookup(&cache_key).await
148 && !hits.is_empty() { 160 && !hits.is_empty() {
149 debug!(%partial, count = hits.len(), "autocomplete cache hit"); 161 debug!(%partial, count = hits.len(), "autocomplete cache hit");
150 let pairs = build_choices(&hits); 162 let pairs = build_choices(&hits);