mirror of
https://github.com/narrrl/proton-sdk-rs.git
synced 2026-11-03 16:41:26 +01:00
feat(drive,photos): report recently accessed nodes
Port upstream 0aabea59: report_recently_accessed on ProtonDriveClient and ProtonPhotosClient, batches of 50. Also read the ThirdParty/Sdk flags from f257c20e on link and revision DTOs. Pin bumped to df3a717b.
This commit is contained in:
1 parent
7950eff337
commit
32014b1f07
7 files changed
+141
-7
No files matched your search
@@ -14,7 +14,7 @@ readme = "../../README.md"
|
||||
documentation = "https://docs.rs/proton-drive-rs"
|
||||
|
||||
[dependencies]
|
||||
proton-sdk = { path = "../proton-sdk", version = "0.7.3" }
|
||||
proton-sdk = { path = "../proton-sdk", version = "0.7.4" }
|
||||
tokio.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -66,7 +66,8 @@ use crate::dtos::{
|
||||
LinkType, ModulusResponse, MoveLinkRequest, MoveMultipleLinksItem, MoveMultipleLinksRequest,
|
||||
MultipleLinksRequest, MyFilesShareResponse, NodeNameAvailabilityRequest,
|
||||
NodeNameAvailabilityResponse, PhotoTagsRequest, PhotosAttributesDto,
|
||||
RemovePhotosFromAlbumRequest, RenameLinkRequest, RevisionConflict, RevisionCreationRequest,
|
||||
RemovePhotosFromAlbumRequest, RenameLinkRequest, ReportRecentlyAccessedItem,
|
||||
ReportRecentlyAccessedRequest, RevisionConflict, RevisionCreationRequest,
|
||||
RevisionCreationResponse, RevisionDto, RevisionListItemDto, RevisionListResponse,
|
||||
RevisionMetadataResponse, RevisionUpdateRequest, ShareInvitationDto, ShareInvitationsResponse,
|
||||
ShareMembersResponse, ShareMembershipSummaryDto, ShareResponse, ShareTargetType, ShareUrlDto,
|
||||
@@ -80,7 +81,7 @@ use crate::dtos::{
|
||||
use crate::events::{DriveEvent, DriveEventScopeId};
|
||||
use crate::node::{
|
||||
AlbumProperties, FileThumbnail, Node, NodeKind, NodeMoveItem, PhotoLocation, PhotoProperties,
|
||||
RevisionState, Thumbnail, ThumbnailType,
|
||||
RecentlyAccessedReportItem, RevisionState, Thumbnail, ThumbnailType,
|
||||
};
|
||||
use crate::photos::{
|
||||
AlbumItem, PhotoTag, PhotoTagsUpdate, PhotoUploadMetadata, PhotosTimelineItem,
|
||||
@@ -792,6 +793,55 @@ impl ProtonDriveClient {
|
||||
Ok(uids)
|
||||
}
|
||||
|
||||
/// Tell the server which nodes the user recently opened.
|
||||
///
|
||||
/// Mirrors upstream `ReportRecentlyAccessedAsync` (`POST recently-accessed-items`),
|
||||
/// in batches of 50. An item without an access time is stamped with the
|
||||
/// moment of the call. A failed batch ends the call; earlier batches stay
|
||||
/// reported.
|
||||
pub async fn report_recently_accessed(
|
||||
&self,
|
||||
items: &[RecentlyAccessedReportItem],
|
||||
) -> Result<()> {
|
||||
self.report_recently_accessed_to("recently-accessed-items", items)
|
||||
.await
|
||||
}
|
||||
|
||||
/// As [`report_recently_accessed`](Self::report_recently_accessed), for
|
||||
/// photos (`POST photos/recently-accessed-items`).
|
||||
pub(crate) async fn report_photos_recently_accessed(
|
||||
&self,
|
||||
items: &[RecentlyAccessedReportItem],
|
||||
) -> Result<()> {
|
||||
self.report_recently_accessed_to("photos/recently-accessed-items", items)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn report_recently_accessed_to(
|
||||
&self,
|
||||
path: &str,
|
||||
items: &[RecentlyAccessedReportItem],
|
||||
) -> Result<()> {
|
||||
/// Items per request (upstream `RecentlyAccessedOperations.BatchSize`).
|
||||
const REPORT_BATCH: usize = 50;
|
||||
|
||||
let now = now_epoch_seconds();
|
||||
for batch in items.chunks(REPORT_BATCH) {
|
||||
let request = ReportRecentlyAccessedRequest {
|
||||
items: batch
|
||||
.iter()
|
||||
.map(|item| ReportRecentlyAccessedItem {
|
||||
volume_id: item.uid.volume_id.clone(),
|
||||
link_id: item.uid.link_id.clone(),
|
||||
access_time: item.access_time.unwrap_or(now),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let _: proton_sdk::api::ApiResponse = self.http.post(path, &request).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch decrypted metadata for many nodes in one pass.
|
||||
///
|
||||
/// Mirrors C# `NodeOperations.EnumerateNodesAsync(uids)`: the uids are
|
||||
@@ -9432,6 +9482,8 @@ mod tests {
|
||||
passphrase_signature: None,
|
||||
signature_email: None,
|
||||
name_signature_email: None,
|
||||
third_party: false,
|
||||
sdk: false,
|
||||
};
|
||||
LinkDetailsDto {
|
||||
link,
|
||||
|
||||
@@ -415,6 +415,26 @@ pub struct AlbumUpdateLink {
|
||||
pub name_signature_email: String,
|
||||
}
|
||||
|
||||
/// `POST recently-accessed-items` (and its `photos/` twin) — tell the server
|
||||
/// which nodes the user opened. Upstream `ReportRecentlyAccessedItemsRequest`.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ReportRecentlyAccessedRequest {
|
||||
#[serde(rename = "RecentlyAccessedItems")]
|
||||
pub items: Vec<ReportRecentlyAccessedItem>,
|
||||
}
|
||||
|
||||
/// One entry of [`ReportRecentlyAccessedRequest`].
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ReportRecentlyAccessedItem {
|
||||
#[serde(rename = "VolumeID")]
|
||||
pub volume_id: VolumeId,
|
||||
#[serde(rename = "LinkID")]
|
||||
pub link_id: LinkId,
|
||||
/// Epoch seconds.
|
||||
#[serde(rename = "AccessTime")]
|
||||
pub access_time: i64,
|
||||
}
|
||||
|
||||
/// `POST photos/volumes/{vid}/albums/{lid}/remove-multiple` — take photos out
|
||||
/// of an album; they stay in the timeline. Ported from the TypeScript SDK
|
||||
/// (`PhotosAPIService.removePhotosFromAlbum`).
|
||||
@@ -637,6 +657,13 @@ pub struct LinkDto {
|
||||
pub signature_email: Option<String>,
|
||||
#[serde(rename = "NameSignatureEmail")]
|
||||
pub name_signature_email: Option<String>,
|
||||
/// Created by a client other than Proton's own (upstream `f257c20e`, for
|
||||
/// telemetry's `CreatedBy`). Absent on older backends, hence `false`.
|
||||
#[serde(rename = "ThirdParty", default)]
|
||||
pub third_party: bool,
|
||||
/// Created through the SDK; only meaningful with [`third_party`](Self::third_party).
|
||||
#[serde(rename = "Sdk", default)]
|
||||
pub sdk: bool,
|
||||
}
|
||||
|
||||
impl LinkDto {
|
||||
@@ -696,6 +723,12 @@ pub struct ActiveRevisionDto {
|
||||
/// revision's extended attributes. Decrypts to [`DecryptedExtendedAttributes`].
|
||||
#[serde(rename = "XAttr")]
|
||||
pub extended_attributes: Option<String>,
|
||||
/// As [`LinkDto::third_party`], for the revision (upstream `f257c20e`).
|
||||
#[serde(rename = "ThirdParty", default)]
|
||||
pub third_party: bool,
|
||||
/// As [`LinkDto::sdk`], for the revision.
|
||||
#[serde(rename = "Sdk", default)]
|
||||
pub sdk: bool,
|
||||
}
|
||||
|
||||
/// The decrypted `XAttr` JSON payload, read side. Mirrors C# `ExtendedAttributes`
|
||||
@@ -1711,6 +1744,34 @@ pub struct DeviceUpdateShareDto {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn recently_accessed_request_uses_the_wire_names() {
|
||||
let request = ReportRecentlyAccessedRequest {
|
||||
items: vec![ReportRecentlyAccessedItem {
|
||||
volume_id: VolumeId::new("v"),
|
||||
link_id: LinkId::new("l"),
|
||||
access_time: 1_700_000_000,
|
||||
}],
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&request).expect("report"),
|
||||
serde_json::json!({
|
||||
"RecentlyAccessedItems": [
|
||||
{"VolumeID": "v", "LinkID": "l", "AccessTime": 1_700_000_000}
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_creator_flags_default_to_first_party() {
|
||||
let revision: ActiveRevisionDto = serde_json::from_value(serde_json::json!({
|
||||
"RevisionID": "r", "CreateTime": 1, "EncryptedSize": 2, "XAttr": null
|
||||
}))
|
||||
.expect("revision");
|
||||
assert!(!revision.third_party && !revision.sdk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn album_rename_sends_the_name_and_no_cover() {
|
||||
let request = AlbumUpdateRequest {
|
||||
|
||||
@@ -57,7 +57,7 @@ pub use event_manager::{
|
||||
pub use events::{DriveEvent, DriveEventScopeId};
|
||||
pub use node::{
|
||||
AlbumProperties, FileThumbnail, Node, NodeKind, NodeMoveItem, PhotoLocation, PhotoProperties,
|
||||
RevisionState, Thumbnail, ThumbnailType,
|
||||
RecentlyAccessedReportItem, RevisionState, Thumbnail, ThumbnailType,
|
||||
};
|
||||
pub use photos::{
|
||||
AlbumItem, PhotoTag, PhotoTagsUpdate, PhotoUploadMetadata, PhotosTimelineItem,
|
||||
|
||||
@@ -20,6 +20,16 @@ pub struct NodeMoveItem {
|
||||
pub target_name: Option<String>,
|
||||
}
|
||||
|
||||
/// One node for [`report_recently_accessed`](crate::ProtonDriveClient::report_recently_accessed).
|
||||
///
|
||||
/// Mirrors upstream `RecentlyAccessedReportItem`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RecentlyAccessedReportItem {
|
||||
pub uid: NodeUid,
|
||||
/// When the node was accessed, epoch seconds; `None` means now.
|
||||
pub access_time: Option<i64>,
|
||||
}
|
||||
|
||||
/// A decrypted Drive node (folder or file).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Node {
|
||||
|
||||
@@ -20,7 +20,7 @@ use proton_sdk::session::ProtonApiSession;
|
||||
|
||||
use crate::client::ProtonDriveClient;
|
||||
use crate::events::{DriveEvent, DriveEventScopeId};
|
||||
use crate::node::{FileThumbnail, Node, Thumbnail, ThumbnailType};
|
||||
use crate::node::{FileThumbnail, Node, RecentlyAccessedReportItem, Thumbnail, ThumbnailType};
|
||||
|
||||
/// One photos-timeline entry: a photo node and its capture time (epoch
|
||||
/// seconds). C# `PhotosTimelineItem(NodeUid Uid, DateTime CaptureTime)`.
|
||||
@@ -144,6 +144,16 @@ impl ProtonPhotosClient {
|
||||
self.drive.enumerate_photos_nodes(uids).await
|
||||
}
|
||||
|
||||
/// Tell the server which photos the user recently opened. See
|
||||
/// [`ProtonDriveClient::report_recently_accessed`]; this posts to the
|
||||
/// photos route.
|
||||
pub async fn report_recently_accessed(
|
||||
&self,
|
||||
items: &[RecentlyAccessedReportItem],
|
||||
) -> Result<()> {
|
||||
self.drive.report_photos_recently_accessed(items).await
|
||||
}
|
||||
|
||||
/// Enumerate the photos timeline newest-first.
|
||||
/// C# `ProtonPhotosClient.EnumerateTimelineAsync`.
|
||||
pub async fn enumerate_timeline(&self) -> Result<Vec<PhotosTimelineItem>> {
|
||||
|
||||
Reference in new issue
Block a user