feat(daemon): make every window a view of one shared session
Two TUIs on one daemon behaved as two separate windows that happened to share a login: each showed only the transfers it had queued and the searches it had run, and neither said it was attached to anything. The status bar now names the daemon, and both lists are views of the session: transfers and searches appear in every window, dismissing one dismisses it everywhere, and a window opened later starts from what is already running rather than blank. message read likewise starts from the conversation the daemon collected instead of only what arrives while it runs. Search state needed the daemon to say when a query went out. The window a search collects for belongs to whichever client started it, so without that every other window showed a running search as finished the moment it appeared — which is what it did. The summary carries a file count too, so a window has the right number before it has pulled the results across. Adds Client::forget_search, which the library had no way to do: a resident client otherwise holds every result set it has ever seen, and a window that dismissed a search had no way to say so. A review of the first cut found five defects, all fixed here and all covered: syncing ran locally too, where it cost a full copy of every result set per frame and deleted the completed-download history restored from disk; a transfer could be listed twice by racing its own registration; clearing all searches did not reach the session, so every row came back; a search dismissed elsewhere never left; and a wishlist search reported as finished the instant it went out.
This commit is contained in:
@@ -23,6 +23,23 @@ use std::sync::atomic::AtomicBool;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::time::Duration;
|
||||
|
||||
/// What a session knows about one search it has run.
|
||||
///
|
||||
/// `started_secs_ago` is what lets a second window tell a search that is still
|
||||
/// collecting from one that has finished: the collecting window is the
|
||||
/// client's, so without the daemon saying when a search began, every window
|
||||
/// but the one that started it would show the result as final immediately.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SessionSearch {
|
||||
pub query: String,
|
||||
/// Files found so far, across every peer that has answered.
|
||||
pub files: usize,
|
||||
/// How long ago the search went out, or `None` when the session cannot
|
||||
/// say — which is the local case, where the window that ran it already
|
||||
/// knows.
|
||||
pub started_secs_ago: Option<u64>,
|
||||
}
|
||||
|
||||
/// Everything the command layer and the TUI ask of a logged-in session.
|
||||
///
|
||||
/// Two rules keep the remote implementation honest:
|
||||
@@ -36,6 +53,11 @@ use std::time::Duration;
|
||||
/// local mirror.
|
||||
pub trait SessionApi: Send + Sync {
|
||||
fn username(&self) -> String;
|
||||
/// The daemon this session is borrowed from, for the UI to name. `None`
|
||||
/// when the session belongs to this process.
|
||||
fn daemon_endpoint(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn listen_port(&self) -> Option<u16>;
|
||||
fn session_loss(&self) -> Option<SessionLoss>;
|
||||
|
||||
@@ -56,6 +78,14 @@ pub trait SessionApi: Send + Sync {
|
||||
cancel: Option<Arc<AtomicBool>>,
|
||||
) -> Result<Vec<SearchResult>>;
|
||||
fn get_search_results(&self, key: &str) -> Vec<SearchResult>;
|
||||
/// Every search the session knows about.
|
||||
///
|
||||
/// Daemon-side this is shared, so a second client sees what the first has
|
||||
/// been searching for rather than starting from a blank list.
|
||||
fn all_searches(&self) -> Vec<SessionSearch>;
|
||||
/// Drop a search from the session, so every window loses it rather than
|
||||
/// only the one that dismissed it.
|
||||
fn forget_search(&self, query: &str) -> bool;
|
||||
fn get_search_results_count(&self, key: &str) -> usize;
|
||||
fn try_get_search_results(&self, key: &str) -> Option<Vec<SearchResult>>;
|
||||
fn start_wishlist_search(&self, query: &str) -> Result<()>;
|
||||
@@ -152,6 +182,23 @@ impl SessionApi for Client {
|
||||
Self::get_search_results(self, key)
|
||||
}
|
||||
|
||||
fn forget_search(&self, query: &str) -> bool {
|
||||
Self::forget_search(self, query)
|
||||
}
|
||||
|
||||
fn all_searches(&self) -> Vec<SessionSearch> {
|
||||
Self::get_all_searches(self)
|
||||
.into_iter()
|
||||
.map(|(query, search)| SessionSearch {
|
||||
query,
|
||||
files: search.results.iter().map(|r| r.files.len()).sum(),
|
||||
// A local window started every search it can see, so it
|
||||
// already knows which are still running.
|
||||
started_secs_ago: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_search_results_count(&self, key: &str) -> usize {
|
||||
Self::get_search_results_count(self, key)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user