feat(core,fuse,gui,cli): album management

New requests CreateAlbum, RenameAlbum, DeleteAlbum, AddToAlbum and
RemoveFromAlbum, backed by proton-drive-rs 0.6.6. The Albums view gains a
New Album button and a card menu with Rename and Delete; photos gain Add
to Album (menu and selection bar) and Remove from Album inside an album.
CLI: pdfs new-album, rename-album, delete-album, add-to-album,
remove-from-album.

Also fixes the album view state: the timeline filters are hidden on the
album grid, the header counts albums, Back keeps the grid and its scroll
position, and the timeline is only reloaded when an album replaced it.
This commit is contained in:
nvrl committed 2026-09-23 03:25:16 +02:00
1 parent 29e5769dec
commit 43d1afe705
8 files changed
+738 -18

No files matched your search

+37
View File
@@ -94,6 +94,25 @@ pub enum Request {
offset: usize,
limit: usize,
},
/// Create an album named `name`. Replies with [`Response::AlbumCreated`].
CreateAlbum { name: String },
/// Rename the album `uid`. Replies with [`Response::Ok`].
RenameAlbum { uid: String, name: String },
/// Delete the album `uid`. Its photos stay in the timeline; with
/// `delete_photos` set, photos that exist only in the album (saved from a
/// shared album) are deleted with it, which the server otherwise refuses.
/// Replies with [`Response::Ok`].
DeleteAlbum {
uid: String,
#[serde(default)]
delete_photos: bool,
},
/// Add timeline photos to the album `uid`. Replies with
/// [`Response::AlbumChanged`].
AddToAlbum { uid: String, photos: Vec<String> },
/// Take photos out of the album `uid`. They stay in the timeline: this is
/// not a trash. Replies with [`Response::AlbumChanged`].
RemoveFromAlbum { uid: String, photos: Vec<String> },
/// Fetch thumbnails for the given photo uids, downloading the ones not
/// already cached (one batched round-trip) and replying with their on-disk
/// paths. Keep the batch small — it is served on demand, as tiles scroll in.
@@ -1745,6 +1764,15 @@ pub enum Response {
trashed: Vec<String>,
failed: Vec<TrashFailure>,
},
/// The album a [`Request::CreateAlbum`] made.
AlbumCreated { uid: String },
/// Outcome of a [`Request::AddToAlbum`] or [`Request::RemoveFromAlbum`],
/// per photo like [`Response::Trashed`]: `changed` photos were added or
/// removed, `failed` ones were not.
AlbumChanged {
changed: Vec<String>,
failed: Vec<TrashFailure>,
},
/// A [`Request::FileThumbs`] generation is no longer current. This is
/// retryable after reserving a fresh generation and must never be interpreted
/// as a permanent "no thumbnail" verdict.
@@ -2360,6 +2388,15 @@ mod tests {
));
}
#[test]
fn delete_album_keeps_photos_unless_asked() {
let request: Request = serde_json::from_str(r#"{"DeleteAlbum":{"uid":"v~a"}}"#).unwrap();
assert!(matches!(
request,
Request::DeleteAlbum { uid, delete_photos: false } if uid == "v~a"
));
}
/// The trash requests carry uids rather than paths; they must survive the same
/// line-delimited round-trip, since a mangled uid would restore or destroy the
/// wrong node.