feat(photos): port the C# album capabilities

Ports upstream a36cc423. add_photos_to_album now uses the timeline
transfer driver, generalised over a PhotoTransferTarget. A photo on
another volume, from an album shared with us, is copied into the album
instead of failing. A photo whose group the server reports incomplete
is sent once more with the related photos it named. Outcomes stay in
input order.

New set_album_cover, and delete_album_saving_photos, which saves the
photos that exist only in the album (code 200302, now
AlbumContainsPhotosNotInTimeline) before deleting it.
remove_photos_from_album fails a photo on another volume without
sending it. AlbumUpdateRequest leaves out a missing cover or name, as
C# does.

Pins upstream to b68d64bd. The search commit e861ef99 is not ported:
its engine source is not public.
This commit is contained in:
nvrl committed 2026-10-25 20:48:22 +01:00
1 parent c30b107382
commit 3da18b9309
6 files changed
+551 -363

No files matched your search

+3 -2
View File
@@ -7,8 +7,8 @@ gitignored, so this file is the only durable record of what we last reviewed.
- **Upstream**: https://github.com/ProtonDriveApps/sdk
- **Reconciled subtree**: `client/cs/src` (was `client/cs/sdk/src` before the
mid-2026 projects reorg; the script watches both)
- **Pinned**: `8ffae007`
- **Date**: 2026-10-15
- **Pinned**: `b68d64bd`
- **Date**: 2026-10-25
> **Note**: upstream force-pushes — the SHAs in rows before 2026-07-14 no longer
> exist in its history. Triage by commit subject/date when a pin goes stale.
@@ -43,3 +43,4 @@ gitignored, so this file is the only durable record of what we last reviewed.
| 2026-10-06 | `f28a93ce` → `df3a717b` | `0aabea59` | ported: **recently accessed report** (`ProtonDriveClient::report_recently_accessed` → `POST recently-accessed-items`, `ProtonPhotosClient::report_recently_accessed` → `POST photos/recently-accessed-items`; batches of 50, missing `AccessTime` defaults to now, epoch seconds; `RecentlyAccessedReportItem` takes `Option<i64>` rather than `DateTimeOffset`). Also ported the DTO half of `f257c20e`: `LinkDto` / `ActiveRevisionDto` read `ThirdParty` and `Sdk` (default `false`). not ported: rest of `f257c20e` (`DecryptionErrorEvent` / `VerificationErrorEvent` with `Recency` and `CreatedBy` — telemetry plumbing for C#'s interop metric callback, we emit no such events; same reasoning as `29e58378`). Live validation pending: the recently-accessed endpoints. |
| 2026-10-15 | `df3a717b` → `8ffae007` | `af391cbb` | ported: **folder-aware available names** (`af391cbb`, breaking upstream and here): `get_available_name` takes an `AvailableNameTarget` (`File` / `Folder`). A file keeps its extension last (`name (1).ext`, as before); a folder appends to the whole name (`name (1)`), so a folder named `v1.2` no longer resolves to `v1 (1).2`. `alternate_names` split into `alternate_file_names` / `alternate_folder_names` (C# `AlternateFileNameGenerator.GetFileNames` / `GetFolderNames`). no-op: `c6b45188` (C# transfer buffers optionally backed by an unlinked `mkstemp` file instead of `RecyclableMemoryStream`, behind a compile-time flag) — memory plumbing only; our blocks are `Vec<u8>` bounded by the client-wide in-flight block semaphore, and the verifier prefix it now reads from the stream is the same `min(prefix, len)` bytes our `verification_token` already zero-pads. **Downstream:** no `get_available_name` caller in `proton-drive-linux` or `proton-stream`. |
| 2026-10-25 | — (no C# delta) | — | **not an upstream port of C#** — two ports from the **TypeScript** SDK, plus one change of ours. **Folder size** (TS `getFolderSize`, `243f11f4`): `ProtonDriveClient::get_folder_size` → `GET volumes/{vid}/folders/{lid}/calculate-descendents-size`, returning `FolderSize { size, descendants }`. Live-verified, which settled what the TS docs leave open: folders count as descendants, and the size is encrypted bytes on storage (two single-block files of 301 000 plaintext bytes report 301 112). **Copy** (TS `NodesManagement.copyNode`): `ProtonDriveClient::copy_node(uid, new_parent, name)` → `POST volumes/{vid}/links/{lid}/copy`, a server-side copy of a file with its content or a folder with its subtree. The crypto is the move's (`build_move_parts`: passphrase rewrapped to the new parent, name encrypted and signed by the My Files address, signature fields only for an anonymously signed node); `CopyPhotoResponse` became the shared `CopyLinkResponse`. Diverged deliberately: the returned uid takes the **target** parent's volume, where TS uses the source's, which only holds within one volume; no `copyNodes` batch, which in TS is a sequential loop over `copyNode`. Live-verified: file content, rename-on-copy, folder subtree, and `AlreadyExists` for a taken name. **Ours**: reads are retried after a dropped connection — `send_retrying` reads the body inside its loop and takes a `Replay` mode; a `GET`, a block download and the new `ApiHttpClient::post_query` (link details, thumbnail URLs, photo duplicates, `checkAvailableHashes`) are sent again when the connection drops mid-request or mid-body, while writes keep the old rule. |
| 2026-10-25 | `8ffae007` → `b68d64bd` | `a36cc423` | ported: **album capabilities** (`a36cc423`), the C# album write API, against the TS-sourced album writes we already had. `add_photos_to_album` now goes through the timeline transfer driver, renamed `transfer_photos` over a `PhotoTransferTarget` with a `PhotoTransferKind` (C# `TransferPhotosToTargetAsync` / `PhotoTransferTarget`): same-volume photos through `add-multiple` (the per-link answers share `TransferPhotosResponse`, so `AddPhotosToAlbumResponse` is gone), photos on **another volume** copied into the album through `POST volumes/{vid}/links/{lid}/copy` instead of failing, and a photo whose group the server reports incomplete re-sent once with the related photos it named; a related photo that cannot be re-encrypted is skipped rather than failing its main photo. The call still answers in input order. `add-multiple` entries never carry a passphrase signature (C# `ToAlbumPhotoLinkItem`). New `ProtonPhotosClient::set_album_cover` (the cover half of `UpdateAlbumAsync`; same volume only, refused before sending), and `AlbumUpdateRequest` now omits a `None` cover or link like C#. New `delete_album_saving_photos` (`DeleteAlbumAsync` with `saveToTimeline`): on `ResponseCode::AlbumContainsPhotosNotInTimeline` (200302, new) it saves the photos `Details.ChildLinkIDs` names, then deletes again; the first photo that cannot be saved fails the call. `remove_photos_from_album` fails a photo on another volume without sending it. Kept additive: `delete_album(uid, delete_photos)` and `rename_album` keep their signatures (`proton-drive-linux` calls both) rather than taking C#'s `(force, saveToTimeline)` and `(name?, cover?)`. Live-verified on the test account: add, adding twice (`Photo already exists in the album.`), cover, remove, delete keeping photos. Not live-verified: the cross-volume copy and the 200302 save, which need a second account sharing an album. **not ported: experimental search** (`e861ef99`): the engine is a Rust crate under `incubating/search` whose source is not in the public tree — only its C# bindings' wiring (`Search.targets`, `ProtonDriveSearchClient`) and a `BinaryKeyValueStore` for its index are. The rest of that commit is cosmetic (`await foreach` over a local, `SqliteConnectionStringBuilder`). |