feat(protocol): away status, excluded phrases, queue-place requests
Three features the client half-had. SetStatus (28) was only ever sent as "online" at login, so there was no way to go away; excluded search phrases (160) were parsed and logged into nowhere, so every client kept spending searches on queries the server was never going to answer; and a queued download could answer a peer asking about its place but could never ask about its own. Each is now driven end to end: the away status seen by another user, a refused query on a stub server that announces phrases (soulfind keeps its filters in a database the suite cannot seed), and a peer's answer landing on the download as its queue position.
This commit is contained in:
@@ -368,6 +368,8 @@ pub enum ClientOperation {
|
||||
CantConnectToPeer {
|
||||
token: u32,
|
||||
},
|
||||
/// Phrases the server refuses to search for (code 160).
|
||||
ExcludedSearchPhrases(Vec<String>),
|
||||
/// A parent candidate told us how deep it sits. `link` says which dial
|
||||
/// to that user is talking.
|
||||
ParentBranchLevel {
|
||||
@@ -469,6 +471,9 @@ pub struct ClientContext {
|
||||
privileged_users: HashSet<String>,
|
||||
/// Seconds of our own privileges left (code 92), once we have asked.
|
||||
own_privileges: Option<u32>,
|
||||
/// Phrases the server will not search for (code 160). A query carrying one
|
||||
/// is refused locally rather than spent on the server.
|
||||
excluded_search_phrases: Vec<String>,
|
||||
/// Peers waiting for one of our upload slots.
|
||||
upload_queue: Vec<QueuedUpload>,
|
||||
/// Arrival counter for the queue's first-come tie-break.
|
||||
@@ -575,6 +580,7 @@ impl ClientContext {
|
||||
wishlist_interval: None,
|
||||
privileged_users: HashSet::new(),
|
||||
own_privileges: None,
|
||||
excluded_search_phrases: Vec::new(),
|
||||
upload_queue: Vec::new(),
|
||||
upload_seq: 0,
|
||||
upload_slots: DEFAULT_UPLOAD_SLOTS,
|
||||
@@ -725,6 +731,31 @@ impl ClientContext {
|
||||
self.user_interests.get(username).cloned()
|
||||
}
|
||||
|
||||
/// Record the phrases the server refuses to search for (code 160).
|
||||
pub fn set_excluded_search_phrases(&mut self, phrases: Vec<String>) {
|
||||
self.excluded_search_phrases = phrases;
|
||||
}
|
||||
|
||||
/// The phrases the server refuses to search for.
|
||||
#[must_use]
|
||||
pub fn excluded_search_phrases(&self) -> Vec<String> {
|
||||
self.excluded_search_phrases.clone()
|
||||
}
|
||||
|
||||
/// The first excluded phrase `query` contains, if any. Matching is
|
||||
/// case-insensitive and on substrings, which is how the server applies its
|
||||
/// own filter.
|
||||
#[must_use]
|
||||
pub fn excluded_phrase_in(&self, query: &str) -> Option<String> {
|
||||
let haystack = query.to_lowercase();
|
||||
self.excluded_search_phrases
|
||||
.iter()
|
||||
.find(|phrase| {
|
||||
!phrase.is_empty() && haystack.contains(&phrase.to_lowercase())
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Forget any interests cached for `username`, so a poll after a fresh
|
||||
/// request cannot mistake the previous answer for this one.
|
||||
pub fn invalidate_user_interests(&mut self, username: &str) {
|
||||
|
||||
Reference in New Issue
Block a user