feat(core,fuse): restore the whole trashed tree, and bump the SDK to 0.6.5

The trash is a flat list of uids, but a delete has a shape: a folder, and
often items inside it that were trashed separately beforehand. Restoring
one of those in isolation is not what it means — a folder came back
without the contents deleted with it, and a file came back inside a
folder that is still trashed, which is to say nowhere the user can see.

- Schema 29: `trash.parent_uid`, filled by the trash refresh from the
  node's parent, plus an index for the walk.
- `expand_restore` takes the connected piece of the trashed tree around
  what was asked for (every trashed descendant, every trashed ancestor)
  and orders it into waves, shallowest first: the server cannot put a
  node back under a parent that is still trashed. A uid the listing does
  not know about is restored on its own, because the listing is
  materialised in chunks and can be stale.
- `Core::restore` restores wave by wave, still streaming per-node
  outcomes, so one node the server refuses leaves the rest restored.

The SDK bump brings the 6cbf2f44 port: permanent API errors are no
longer retried on block transfers, expired block URLs are re-minted on
the whole-file and public-link download paths, and a server-supplied
Retry-After is capped at 120 s.
This commit is contained in:
nvrl committed 2026-09-19 14:31:40 +02:00
1 parent 470948c191
commit f78f663740
8 files changed
+350 -38

No files matched your search

+14
View File
@@ -254,6 +254,7 @@ fn trash_replace_lists_folders_first() {
is_dir: false,
size: 10,
mtime: 1,
parent_uid: Some("t2".into()),
},
StoredTrash {
uid: "t2".into(),
@@ -261,6 +262,7 @@ fn trash_replace_lists_folders_first() {
is_dir: true,
size: 0,
mtime: 2,
parent_uid: None,
},
])
.unwrap();
@@ -271,6 +273,18 @@ fn trash_replace_lists_folders_first() {
["Alpha", "zeta.txt"]
);
// The parent links come back with the rows: a restore reads the tree shape
// from them.
let mut parents = db.trash_parents().unwrap();
parents.sort();
assert_eq!(
parents,
[
("t1".to_string(), Some("t2".to_string())),
("t2".to_string(), None),
]
);
// A replace is a replace: emptying the trash on the server empties it here.
db.trash_replace(&[]).unwrap();
assert!(db.trash_list().unwrap().is_empty());