diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 01:32:06 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 01:32:06 +0100 |
| commit | 382bb2af15e7076f8dcbbe6cc4a6b135709ef7b9 (patch) | |
| tree | 5e03e559d05ff2510781d5b760f5737e4ff03580 /src/commands/autocomplete.rs | |
| parent | cron: validate expressions and allocate ids atomically (diff) | |
| download | senator-sperma-382bb2af15e7076f8dcbbe6cc4a6b135709ef7b9.tar.gz senator-sperma-382bb2af15e7076f8dcbbe6cc4a6b135709ef7b9.zip | |
autocomplete: key results by extension filter and reject the placeholder
Diffstat (limited to '')
| -rw-r--r-- | src/commands/autocomplete.rs | 24 |
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. |
| 23 | const MOCK_SEARCHING: &str = "\u{231B}..."; | 23 | pub(crate) const MOCK_SEARCHING: &str = "\u{231B}..."; |
| 24 | 24 | ||
| 25 | fn truncate_choice(s: &str) -> String { | 25 | fn 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 | ||
| 101 | fn 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 | |||
| 101 | fn spawn_search( | 107 | fn 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); |
