enumeration returns NodeUids (enumerate_folder_children_node_uids / enumerate_trash_node_uids), caller materializes via enumerate_nodes. noise dropped: BOM/deps/kt-enum-order/cs-account-refactor

This commit is contained in:
nvrl committed 2026-06-26 14:15:54 +02:00
1 parent 5685923034
commit e15f2d3111
4 files changed
+37 -53

No files matched your search

+3 -2
View File
@@ -6,8 +6,8 @@ gitignored, so this file is the only durable record of what we last reviewed.
- **Upstream**: https://github.com/ProtonDriveApps/sdk - **Upstream**: https://github.com/ProtonDriveApps/sdk
- **Reconciled subtree**: `client/cs/sdk/src` - **Reconciled subtree**: `client/cs/sdk/src`
- **Pinned**: `fb4173ab823a62c1c9f5a11d1b8320d4ce0ea4dc` - **Pinned**: `36430318919c30fac42ec8577036a6d6e9de916f`
- **Date**: 2026-06-25 - **Date**: 2026-06-26
## Workflow ## Workflow
@@ -22,3 +22,4 @@ gitignored, so this file is the only durable record of what we last reviewed.
| date | from → to | ported | notes | | date | from → to | ported | notes |
|------|-----------|--------|-------| |------|-----------|--------|-------|
| 2026-06-25 | initial pin `fb4173ab` | — | baseline; delta reviewed, 0 behavioral changes outstanding | | 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 |
@@ -55,7 +55,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
); );
eprintln!("[*] enumerating children ..."); 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()); eprintln!("[+] {} children:", children.len());
for c in &children { for c in &children {
eprintln!(" - {:?} [{:?}] {}", c.name, c.kind, c.uid); eprintln!(" - {:?} [{:?}] {}", c.name, c.kind, c.uid);
+30 -49
View File
@@ -227,12 +227,21 @@ impl ProtonDriveClient {
Ok(Some(node)) Ok(Some(node))
} }
/// Enumerate the (non-trashed) children of a folder. /// Enumerate the [`NodeUid`]s of a folder's (non-trashed) children.
pub async fn enumerate_folder_children(&self, folder_uid: &NodeUid) -> Result<Vec<Node>> { ///
let mut timer = self.telemetry.start("enumerate_folder_children"); /// Mirrors C# `FolderOperations.EnumerateChildrenAsync` (renamed to
let folder_key = self.folder_node_key(folder_uid).await?; /// `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<Vec<NodeUid>> {
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<LinkId> = None; let mut anchor: Option<LinkId> = None;
loop { loop {
@@ -249,20 +258,8 @@ impl ProtonDriveClient {
break; break;
} }
let details = self for link_id in page.link_ids {
.get_link_details(&folder_uid.volume_id, &page.link_ids) uids.push(NodeUid::new(folder_uid.volume_id.clone(), link_id));
.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");
}
}
} }
if !page.more_results_exist { 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(); timer.success();
Ok(nodes) Ok(uids)
} }
/// Fetch decrypted metadata for many nodes in one pass. /// Fetch decrypted metadata for many nodes in one pass.
@@ -314,17 +311,17 @@ impl ProtonDriveClient {
Ok(nodes) 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 /// Mirrors C# `VolumeOperations.EnumerateTrashAsync` (renamed to
/// (`GET volumes/{vid}/trash`), which groups trashed links by share, then /// `EnumerateTrashNodeUidsAsync` on the client): page the trash listing
/// decrypt each group's links against their resolved parent keys. As with /// (`GET volumes/{vid}/trash`), which groups trashed links by share, and
/// other enumeration paths, a node that fails to decrypt is logged and /// emit a [`NodeUid`] per link. Enumeration no longer fetches share keys or
/// skipped rather than aborting the listing. /// decrypts — callers materialize via [`enumerate_nodes`](Self::enumerate_nodes).
pub async fn enumerate_trash(&self) -> Result<Vec<Node>> { pub async fn enumerate_trash_node_uids(&self) -> Result<Vec<NodeUid>> {
let volume_id = self.main_volume_id().await?; let volume_id = self.main_volume_id().await?;
let mut nodes = Vec::new(); let mut uids = Vec::new();
let mut page = 0_usize; let mut page = 0_usize;
loop { loop {
@@ -334,25 +331,9 @@ impl ProtonDriveClient {
let mut count = 0_usize; let mut count = 0_usize;
for group in &response.trash_by_share { for group in &response.trash_by_share {
count += group.link_ids.len(); for link_id in &group.link_ids {
for chunk in group.link_ids.chunks(MAX_BATCH_COUNT) { count += 1;
let details = self.get_link_details(&volume_id, chunk).await?; uids.push(NodeUid::new(volume_id.clone(), link_id.clone()));
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");
}
}
}
} }
} }
@@ -363,7 +344,7 @@ impl ProtonDriveClient {
page += 1; page += 1;
} }
Ok(nodes) Ok(uids)
} }
/// The main volume id, resolved via My Files. /// The main volume id, resolved via My Files.
+2 -1
View File
@@ -24,7 +24,8 @@
//! //!
//! let drive = ProtonDriveClient::new(&session, b"mailbox-password".to_vec()); //! let drive = ProtonDriveClient::new(&session, b"mailbox-password".to_vec());
//! let root = drive.get_my_files_folder().await?; //! 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); //! println!("{} ({:?})", child.name, child.kind);
//! } //! }
//! # Ok(()) //! # Ok(())