From e15f2d3111c07c9e1b812f0ba64c7329cc99f153 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 26 Jun 2026 14:15:54 +0200 Subject: [PATCH] enumeration returns `NodeUid`s (`enumerate_folder_children_node_uids` / `enumerate_trash_node_uids`), caller materializes via `enumerate_nodes`. noise dropped: BOM/deps/kt-enum-order/cs-account-refactor --- UPSTREAM_SYNC.md | 5 +- .../proton-drive-sdk/examples/live_login.rs | 3 +- crates/proton-drive-sdk/src/client.rs | 79 +++++++------------ crates/proton-drive-sdk/src/lib.rs | 3 +- 4 files changed, 37 insertions(+), 53 deletions(-) diff --git a/UPSTREAM_SYNC.md b/UPSTREAM_SYNC.md index b03cb36..7952634 100644 --- a/UPSTREAM_SYNC.md +++ b/UPSTREAM_SYNC.md @@ -6,8 +6,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/sdk/src` -- **Pinned**: `fb4173ab823a62c1c9f5a11d1b8320d4ce0ea4dc` -- **Date**: 2026-06-25 +- **Pinned**: `36430318919c30fac42ec8577036a6d6e9de916f` +- **Date**: 2026-06-26 ## Workflow @@ -22,3 +22,4 @@ gitignored, so this file is the only durable record of what we last reviewed. | date | from → to | ported | notes | |------|-----------|--------|-------| | 2026-06-25 | initial pin `fb4173ab` | — | baseline; delta reviewed, 0 behavioral changes outstanding | +| 2026-06-26 | `fb4173ab` → `36430318` | `36430318` | ported: enumeration returns `NodeUid`s (`enumerate_folder_children_node_uids` / `enumerate_trash_node_uids`), caller materializes via `enumerate_nodes`. noise dropped: BOM/deps/kt-enum-order/cs-account-refactor | diff --git a/crates/proton-drive-sdk/examples/live_login.rs b/crates/proton-drive-sdk/examples/live_login.rs index fc69a6e..ef577d2 100644 --- a/crates/proton-drive-sdk/examples/live_login.rs +++ b/crates/proton-drive-sdk/examples/live_login.rs @@ -55,7 +55,8 @@ async fn main() -> Result<(), Box> { ); eprintln!("[*] enumerating children ..."); - let children = client.enumerate_folder_children(&root.uid).await?; + let child_uids = client.enumerate_folder_children_node_uids(&root.uid).await?; + let children = client.enumerate_nodes(&child_uids).await?; eprintln!("[+] {} children:", children.len()); for c in &children { eprintln!(" - {:?} [{:?}] {}", c.name, c.kind, c.uid); diff --git a/crates/proton-drive-sdk/src/client.rs b/crates/proton-drive-sdk/src/client.rs index ebcb63f..6320fc6 100644 --- a/crates/proton-drive-sdk/src/client.rs +++ b/crates/proton-drive-sdk/src/client.rs @@ -227,12 +227,21 @@ impl ProtonDriveClient { Ok(Some(node)) } - /// Enumerate the (non-trashed) children of a folder. - pub async fn enumerate_folder_children(&self, folder_uid: &NodeUid) -> Result> { - let mut timer = self.telemetry.start("enumerate_folder_children"); - let folder_key = self.folder_node_key(folder_uid).await?; + /// Enumerate the [`NodeUid`]s of a folder's (non-trashed) children. + /// + /// Mirrors C# `FolderOperations.EnumerateChildrenAsync` (renamed to + /// `EnumerateFolderChildrenNodeUidsAsync` on the client): enumeration now + /// only lists uids — it does not fetch the folder key, link details, or + /// decrypt anything. Callers materialize the nodes they care about via + /// [`enumerate_nodes`](Self::enumerate_nodes), avoiding per-child decryption + /// of the whole listing. + pub async fn enumerate_folder_children_node_uids( + &self, + folder_uid: &NodeUid, + ) -> Result> { + let mut timer = self.telemetry.start("enumerate_folder_children_node_uids"); - let mut nodes = Vec::new(); + let mut uids = Vec::new(); let mut anchor: Option = None; loop { @@ -249,20 +258,8 @@ impl ProtonDriveClient { break; } - let details = self - .get_link_details(&folder_uid.volume_id, &page.link_ids) - .await?; - - for child in &details.links { - match self - .build_node(&folder_uid.volume_id, child, &folder_key) - .await - { - Ok(node) => nodes.push(node), - Err(e) => { - tracing::warn!(link_id = %child.link.id, error = %e, "skipping undecryptable child"); - } - } + for link_id in page.link_ids { + uids.push(NodeUid::new(folder_uid.volume_id.clone(), link_id)); } if !page.more_results_exist { @@ -274,9 +271,9 @@ impl ProtonDriveClient { } } - timer.attr("node_count", nodes.len()); + timer.attr("node_count", uids.len()); timer.success(); - Ok(nodes) + Ok(uids) } /// Fetch decrypted metadata for many nodes in one pass. @@ -314,17 +311,17 @@ impl ProtonDriveClient { Ok(nodes) } - /// Enumerate the trashed nodes of the main volume. + /// Enumerate the [`NodeUid`]s of the main volume's trashed nodes. /// - /// Mirrors C# `VolumeOperations.EnumerateTrashAsync`: page the trash listing - /// (`GET volumes/{vid}/trash`), which groups trashed links by share, then - /// decrypt each group's links against their resolved parent keys. As with - /// other enumeration paths, a node that fails to decrypt is logged and - /// skipped rather than aborting the listing. - pub async fn enumerate_trash(&self) -> Result> { + /// Mirrors C# `VolumeOperations.EnumerateTrashAsync` (renamed to + /// `EnumerateTrashNodeUidsAsync` on the client): page the trash listing + /// (`GET volumes/{vid}/trash`), which groups trashed links by share, and + /// emit a [`NodeUid`] per link. Enumeration no longer fetches share keys or + /// decrypts — callers materialize via [`enumerate_nodes`](Self::enumerate_nodes). + pub async fn enumerate_trash_node_uids(&self) -> Result> { let volume_id = self.main_volume_id().await?; - let mut nodes = Vec::new(); + let mut uids = Vec::new(); let mut page = 0_usize; loop { @@ -334,25 +331,9 @@ impl ProtonDriveClient { let mut count = 0_usize; for group in &response.trash_by_share { - count += group.link_ids.len(); - for chunk in group.link_ids.chunks(MAX_BATCH_COUNT) { - let details = self.get_link_details(&volume_id, chunk).await?; - for detail in &details.links { - let parent_key = - match self.resolve_parent_key(&volume_id, &detail.link).await { - Ok(key) => key, - Err(e) => { - tracing::warn!(link_id = %detail.link.id, error = %e, "skipping trashed node: parent key unavailable"); - continue; - } - }; - match self.build_node(&volume_id, detail, &parent_key).await { - Ok(node) => nodes.push(node), - Err(e) => { - tracing::warn!(link_id = %detail.link.id, error = %e, "skipping undecryptable trashed node"); - } - } - } + for link_id in &group.link_ids { + count += 1; + uids.push(NodeUid::new(volume_id.clone(), link_id.clone())); } } @@ -363,7 +344,7 @@ impl ProtonDriveClient { page += 1; } - Ok(nodes) + Ok(uids) } /// The main volume id, resolved via My Files. diff --git a/crates/proton-drive-sdk/src/lib.rs b/crates/proton-drive-sdk/src/lib.rs index 56f1ecd..8eb664b 100644 --- a/crates/proton-drive-sdk/src/lib.rs +++ b/crates/proton-drive-sdk/src/lib.rs @@ -24,7 +24,8 @@ //! //! let drive = ProtonDriveClient::new(&session, b"mailbox-password".to_vec()); //! let root = drive.get_my_files_folder().await?; -//! for child in drive.enumerate_folder_children(&root.uid).await? { +//! let child_uids = drive.enumerate_folder_children_node_uids(&root.uid).await?; +//! for child in drive.enumerate_nodes(&child_uids).await? { //! println!("{} ({:?})", child.name, child.kind); //! } //! # Ok(())