mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16:56:59 +01:00
feat(gui,fuse): delete photos from the gallery and notice remote deletions
The gallery could show photos and nothing else: a photo deleted on the phone stayed on the page for minutes, and a photo you wanted gone had to be deleted on another device. Both are now handled here. Deleting needs a uid-addressed request, because `Request::Delete` takes a mountpoint path and the photos volume is not in the FUSE mount. `TrashNodes` trashes through the SDK and answers per-uid outcomes, so a partial failure is reportable; the daemon then drops those rows with `Db::photos_delete`, album membership included, rather than leaving the gallery to wait for the next timeline refresh. The GUI removes the tiles immediately with an Undo toast and puts back anything the server refused — the photos are in Proton's trash and stay recoverable there either way. Selection is Ctrl/Shift+click, the Select toggle or Delete in the lightbox. Remote deletions arrive on their own now. The Drive volume and the photos volume have separate event streams, so the photos volume gets its own poller and its own cursor. Trash and delete events remove rows; anything else only marks the timeline stale, because there is no inode space to converge on this volume. With the timeline TTL down to 60 s and an explicit Refresh that waits for the server instead of only clearing the freshness stamp, a photo deleted elsewhere leaves the grid in about ten seconds.
This commit is contained in:
1 parent
5a8e73657c
commit
c6bdba024f
13 files changed
+724
-10
No files matched your search
@@ -102,6 +102,31 @@ impl Db {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Forget photos that have been trashed, so the gallery does not show them
|
||||
/// until the next timeline refresh would have caught up.
|
||||
///
|
||||
/// Album membership goes with them: a trashed photo is gone from every album
|
||||
/// that held it, and leaving the rows behind would keep it on the album
|
||||
/// pages and in the album covers.
|
||||
pub fn photos_delete(&self, uids: &[String]) -> Result<usize> {
|
||||
if uids.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
let mut conn = self.conn.lock();
|
||||
let tx = conn.transaction()?;
|
||||
let mut removed = 0;
|
||||
{
|
||||
let mut photos = tx.prepare("DELETE FROM photos WHERE uid = ?1")?;
|
||||
let mut albums = tx.prepare("DELETE FROM album_photos WHERE uid = ?1")?;
|
||||
for uid in uids {
|
||||
removed += photos.execute([uid])?;
|
||||
albums.execute([uid])?;
|
||||
}
|
||||
}
|
||||
tx.commit()?;
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
/// One page of the persisted timeline, newest first. `kind`, when set,
|
||||
/// restricts the page to one tab (Photos / Videos / Raw); `range`, when set,
|
||||
/// restricts it to a `[from, to)` capture-time window (epoch seconds) — the
|
||||
|
||||
@@ -3919,3 +3919,54 @@ fn an_idle_worker_does_not_wait_on_an_op_another_worker_has() {
|
||||
"an op somebody else is draining is not work this worker is waiting for"
|
||||
);
|
||||
}
|
||||
|
||||
/// Trashing a photo from the gallery must not leave it behind on an album page:
|
||||
/// the row goes, its album membership goes with it, and nothing else moves.
|
||||
#[test]
|
||||
fn photos_delete_removes_the_photo_and_its_album_membership() {
|
||||
let db = Db::open_in_memory().unwrap();
|
||||
db.photos_replace(&[
|
||||
("p1".into(), 300, None, None, None),
|
||||
("p2".into(), 200, None, None, None),
|
||||
("p3".into(), 100, None, None, None),
|
||||
])
|
||||
.unwrap();
|
||||
db.albums_replace(&[StoredAlbum {
|
||||
uid: "a1".into(),
|
||||
name: "Trip".into(),
|
||||
photo_count: 3,
|
||||
cover_uid: Some("p1".into()),
|
||||
last_activity: Some(500),
|
||||
shared: false,
|
||||
}])
|
||||
.unwrap();
|
||||
db.album_photos_replace(
|
||||
"a1",
|
||||
&[
|
||||
("p1".into(), 300, None, None),
|
||||
("p2".into(), 200, None, None),
|
||||
("p3".into(), 100, None, None),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(db.photos_delete(&["p2".into()]).unwrap(), 1);
|
||||
|
||||
let page = db.photos_page(0, 10, None, None, false).unwrap();
|
||||
assert_eq!(
|
||||
page.iter().map(|p| p.uid.as_str()).collect::<Vec<_>>(),
|
||||
["p1", "p3"],
|
||||
"only the trashed photo leaves the timeline"
|
||||
);
|
||||
assert_eq!(db.album_photos_count("a1").unwrap(), 2);
|
||||
let album = db.album_photos_page("a1", 0, 10).unwrap();
|
||||
assert_eq!(
|
||||
album.iter().map(|p| p.uid.as_str()).collect::<Vec<_>>(),
|
||||
["p1", "p3"]
|
||||
);
|
||||
|
||||
// A uid we no longer hold is not an error: the event poller and the trash
|
||||
// reply can both report the same deletion.
|
||||
assert_eq!(db.photos_delete(&["p2".into()]).unwrap(), 0);
|
||||
assert_eq!(db.photos_delete(&[]).unwrap(), 0);
|
||||
}
|
||||
Reference in new issue
Block a user