fix: two medium bugs from the final adversarial review
- Retry was broken for the common case: retry_selected_download removed the failed download only from the UI list, not the client's DownloadStore. Since Client::download re-derives the same md5 token from the unchanged filename, retrying a still-Failed download left a duplicate-token entry; the token-migration path then deleted both, misrouting completion so the retried file downloaded to disk but the UI showed 'Queued' forever. Add Client::remove_download / DownloadStore::remove_by_file (status-agnostic) and clear the stale entry before re-issuing. Regression-tested. - Chat-room member selection could point out of range: RoomEvent::UserLeft shrank the active room's member list without clamping user_selected, so the highlight silently shifted to the wrong user (or vanished, making 'b'/'m' no-ops, and blanking the member pane). Clamp user_selected after every applied room event. Regression-tested.
This commit is contained in:
@@ -84,6 +84,17 @@ impl DownloadStore {
|
||||
true
|
||||
}
|
||||
|
||||
/// Remove every download matching `username`/`filename` regardless of
|
||||
/// status. Used before retrying a failed download so the stale entry (whose
|
||||
/// md5-derived token collides with the retry's) can't shadow the fresh one.
|
||||
/// Returns whether anything was removed.
|
||||
pub fn remove_by_file(&mut self, username: &str, filename: &str) -> bool {
|
||||
let before = self.downloads.len();
|
||||
self.downloads
|
||||
.retain(|d| !(d.username == username && d.filename == filename));
|
||||
self.downloads.len() != before
|
||||
}
|
||||
|
||||
pub fn pause_by_file(&mut self, username: &str, filename: &str) -> bool {
|
||||
let Some(download) = self.get_by_file_mut(username, filename) else {
|
||||
return false;
|
||||
@@ -272,6 +283,28 @@ mod tests {
|
||||
assert!(store.get_by_token(456).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_by_file_removes_regardless_of_status() {
|
||||
let mut store = DownloadStore::new();
|
||||
// A failed download (the retry case) plus a same-name duplicate that a
|
||||
// token-migration could have left behind — both must go.
|
||||
let mut failed = make_download(1, DownloadStatus::Failed(None));
|
||||
failed.filename = "song.mp3".to_string();
|
||||
store.add(failed);
|
||||
let mut dup = make_download(2, DownloadStatus::Queued);
|
||||
dup.filename = "song.mp3".to_string();
|
||||
store.add(dup);
|
||||
let mut other = make_download(3, DownloadStatus::Queued);
|
||||
other.filename = "other.mp3".to_string();
|
||||
store.add(other);
|
||||
|
||||
assert!(store.remove_by_file("peer", "song.mp3"));
|
||||
assert!(store.get_by_token(1).is_none());
|
||||
assert!(store.get_by_token(2).is_none());
|
||||
assert!(store.get_by_token(3).is_some(), "other file untouched");
|
||||
assert!(!store.remove_by_file("peer", "song.mp3"), "idempotent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_failed_tokens_notifies_and_lists_matching() {
|
||||
let mut store = DownloadStore::new();
|
||||
|
||||
Reference in New Issue
Block a user