From d9da0e226307b7f5ce3744e24deb8cdd8d0e7c0f Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 5 Nov 2026 19:06:03 +0100 Subject: [PATCH] build(sdk): bump to 0.11.0 --- Cargo.lock | 10 +++--- Cargo.toml | 5 +-- crates/pdfs-core/Cargo.toml | 1 + crates/pdfs-core/src/lib.rs | 1 + crates/pdfs-core/src/timestamp.rs | 33 +++++++++++++++++ crates/pdfs-fuse/src/control.rs | 2 ++ crates/pdfs-fuse/src/drive.rs | 14 +++++--- crates/pdfs-fuse/src/redate.rs | 1 + crates/pdfs-fuse/src/takeout.rs | 12 +++++-- docs/CHANGELOG.md | 7 ++++ docs/SDK-0.11.0.md | 60 +++++++++++++++++++++++++++++++ 11 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 crates/pdfs-core/src/timestamp.rs create mode 100644 docs/SDK-0.11.0.md diff --git a/Cargo.lock b/Cargo.lock index 83edbc3..0ad8a5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3540,6 +3540,7 @@ version = "3.7.0" dependencies = [ "async-trait", "base64 0.23.1", + "chrono", "dbus-secret-service-keyring-store", "directories", "ignore", @@ -3825,13 +3826,14 @@ dependencies = [ [[package]] name = "proton-drive-rs" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fa2a88130883010b32035e208aaa36fb275f6d7d78ed44a1069b1d33d24481" +checksum = "14a22e621abbc1c61b4640728a7de969c16b66d5d983b59c638d501fda25a7b9" dependencies = [ "async-trait", "base64 0.23.1", "bytes", + "chrono", "futures", "getrandom 0.4.3", "hex", @@ -3852,9 +3854,9 @@ dependencies = [ [[package]] name = "proton-sdk" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0bf8d50fa829d1a65b09c6855b606578d02734d4089874c971d1063c23c353" +checksum = "bd55a5e8b6600f3aa282ebbdf13ff366431ff9aae84d4eb20b0ef06d598919fe" dependencies = [ "aes-gcm 0.11.1", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 0bd0dbd..63cf0d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,9 @@ keywords = ["proton", "drive", "fuse", "sync", "linux"] categories = ["filesystem", "command-line-utilities"] [workspace.dependencies] -proton-sdk = { version = "0.10.1" } -proton-drive-rs = { version = "0.10.1" } +proton-sdk = { version = "0.11.0" } +proton-drive-rs = { version = "0.11.0" } +chrono = { version = "0.4", default-features = false, features = ["std"] } # Internal crates. pdfs-core = { path = "crates/pdfs-core" } diff --git a/crates/pdfs-core/Cargo.toml b/crates/pdfs-core/Cargo.toml index 82acd1a..cbc54c0 100644 --- a/crates/pdfs-core/Cargo.toml +++ b/crates/pdfs-core/Cargo.toml @@ -12,6 +12,7 @@ description = "Core auth/session/config for the Proton Drive Linux client" [dependencies] proton-sdk.workspace = true proton-drive-rs.workspace = true +chrono.workspace = true tokio.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/pdfs-core/src/lib.rs b/crates/pdfs-core/src/lib.rs index f24d291..723dfa2 100644 --- a/crates/pdfs-core/src/lib.rs +++ b/crates/pdfs-core/src/lib.rs @@ -29,6 +29,7 @@ pub mod similar; pub mod syncignore; pub mod syncroot; pub mod takeout; +pub mod timestamp; pub mod tray; pub mod update; diff --git a/crates/pdfs-core/src/timestamp.rs b/crates/pdfs-core/src/timestamp.rs new file mode 100644 index 0000000..1dc551e --- /dev/null +++ b/crates/pdfs-core/src/timestamp.rs @@ -0,0 +1,33 @@ +//! Convert the client's stored epoch seconds at the SDK boundary. + +use proton_drive_rs::ModificationTime; + +/// A UTC modification time, or `None` when outside the SDK's supported range. +pub fn modification_time(seconds: i64) -> Option { + chrono::DateTime::from_timestamp(seconds, 0).map(|time| time.fixed_offset()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epoch_seconds_preserve_the_instant_including_before_1970() { + for seconds in [-1, 0, 1_709_208_000, 1_782_482_460] { + let time = modification_time(seconds).unwrap(); + assert_eq!(time.timestamp(), seconds); + assert_eq!(time.offset().local_minus_utc(), 0); + assert_eq!(time.timestamp_subsec_nanos(), 0); + } + assert_eq!( + modification_time(-1).unwrap().to_rfc3339(), + "1969-12-31T23:59:59+00:00" + ); + } + + #[test] + fn out_of_range_seconds_omit_the_timestamp() { + assert!(modification_time(i64::MIN).is_none()); + assert!(modification_time(i64::MAX).is_none()); + } +} diff --git a/crates/pdfs-fuse/src/control.rs b/crates/pdfs-fuse/src/control.rs index d45fa95..6e7e8d1 100644 --- a/crates/pdfs-fuse/src/control.rs +++ b/crates/pdfs-fuse/src/control.rs @@ -938,6 +938,8 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U }); let metadata = proton_drive_rs::PhotoUploadMetadata { capture_time, + last_modification_time: capture_time + .and_then(pdfs_core::timestamp::modification_time), ..Default::default() }; let thumbnails = core.upload_thumbnails(Path::new(&source_path), &name); diff --git a/crates/pdfs-fuse/src/drive.rs b/crates/pdfs-fuse/src/drive.rs index 5d9a261..55fa4bb 100644 --- a/crates/pdfs-fuse/src/drive.rs +++ b/crates/pdfs-fuse/src/drive.rs @@ -346,7 +346,13 @@ impl DriveApi for ProtonDriveClient { name: &str, last_modification_time: Option, ) -> Result { - ProtonDriveClient::create_folder(self, parent_uid, name, last_modification_time).await + ProtonDriveClient::create_folder( + self, + parent_uid, + name, + last_modification_time.and_then(pdfs_core::timestamp::modification_time), + ) + .await } async fn upload_file( @@ -378,7 +384,7 @@ impl DriveApi for ProtonDriveClient { reader, intended_size, thumbnails, - last_modification_time, + last_modification_time.and_then(pdfs_core::timestamp::modification_time), aead, ) .await @@ -403,7 +409,7 @@ impl DriveApi for ProtonDriveClient { reader, intended_size, thumbnails, - last_modification_time, + last_modification_time.and_then(pdfs_core::timestamp::modification_time), aead, ) .await @@ -423,7 +429,7 @@ impl DriveApi for ProtonDriveClient { reader, intended_size, thumbnails, - last_modification_time, + last_modification_time.and_then(pdfs_core::timestamp::modification_time), ) .await } diff --git a/crates/pdfs-fuse/src/redate.rs b/crates/pdfs-fuse/src/redate.rs index aeda224..4452511 100644 --- a/crates/pdfs-fuse/src/redate.rs +++ b/crates/pdfs-fuse/src/redate.rs @@ -200,6 +200,7 @@ impl Core { let reader = CountingReader::new(std::io::BufReader::new(file), &guard); let metadata = PhotoUploadMetadata { capture_time: Some(candidate.wanted), + last_modification_time: pdfs_core::timestamp::modification_time(candidate.wanted), ..Default::default() }; let uploaded = self.rt.block_on(photos.upload_photo_from( diff --git a/crates/pdfs-fuse/src/takeout.rs b/crates/pdfs-fuse/src/takeout.rs index 456f160..4a23d86 100644 --- a/crates/pdfs-fuse/src/takeout.rs +++ b/crates/pdfs-fuse/src/takeout.rs @@ -344,6 +344,9 @@ impl Core { ); let metadata = PhotoUploadMetadata { capture_time: photo.capture_time, + last_modification_time: photo + .capture_time + .and_then(pdfs_core::timestamp::modification_time), ..Default::default() }; // A thumbnail is made from a file, so a photo that can have one @@ -365,10 +368,13 @@ impl Core { let thumbnails = self.upload_thumbnails(path, &photo.name); // No sidecar and no date in the name: the photo's own // EXIF, before Drive files it under today. + let capture_time = metadata + .capture_time + .or_else(|| pdfs_core::exiftime::capture_time_of(path)); let metadata = PhotoUploadMetadata { - capture_time: metadata - .capture_time - .or_else(|| pdfs_core::exiftime::capture_time_of(path)), + capture_time, + last_modification_time: capture_time + .and_then(pdfs_core::timestamp::modification_time), ..metadata }; std::fs::File::open(path) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 968efc8..e200cc0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,13 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). ## [Unreleased] +### Changed + +- Update `proton-sdk` and `proton-drive-rs` to 0.11.0. + Convert epoch timestamps at the SDK boundary and explicitly preserve photo modification + times now that they are independent of capture times. See [the SDK review](SDK-0.11.0.md) + for features and breaking changes. + ## [3.7.0] — 2026-11-02 Schema: **44**, unchanged. diff --git a/docs/SDK-0.11.0.md b/docs/SDK-0.11.0.md new file mode 100644 index 0000000..b583e49 --- /dev/null +++ b/docs/SDK-0.11.0.md @@ -0,0 +1,60 @@ +# SDK 0.11.0 review + +Reviewed the sibling `../proton-sdk-rs` checkout at `84039a1` against 0.10.1 +(`48dd410`), including its changelog and source diff. + +## Features + +- Folder creation, streaming uploads, draft replacement, and revision uploads + preserve the supplied timestamp's UTC offset and fractional seconds. +- `ModificationTime` is exported as an alias for + `chrono::DateTime`. +- Photo modification time can be set independently of capture time. Downloaded + claimed modification timestamps remain verbatim strings. +- Neither SDK crate defines Cargo feature flags. No feature opt-in is needed. + The Drive crate now depends directly on `chrono` 0.4; the SDK already used it. + The minimum Rust version remains 1.96, matching this workspace. + +## Breaking changes and adaptation + +The modification-time argument changes from `Option` to +`Option` on `create_folder`, `upload_file_from`, +`upload_file_replacing_draft_from`, `upload_new_revision`, and +`upload_new_revision_from`. `None` remains valid. + +The real `DriveApi` adapter converts the client's stored epoch seconds to UTC +through `pdfs_core::timestamp::modification_time`. Internal storage, the trait, +and the simulated Drive continue using epoch seconds. Out-of-range timestamps +are omitted rather than panicking. Existing upload paths still supply whole +seconds; this bump alone does not add subsecond storage or offset tracking. + +`PhotoUploadMetadata` gains `last_modification_time`, and the SDK no longer +copies `capture_time` into it. Our literals already use `Default`, so this would +compile while silently changing uploaded metadata. Control uploads, Takeout +imports (including the EXIF fallback), and redating now set both explicitly to +preserve the previous behavior when a capture time is known. Without a known +capture time, modification time stays omitted as before. + +No other public API changes were found in the reviewed diff. No database or +recovery migration is required by this SDK bump. + +## Dependencies + +The workspace requires the published 0.11.0 versions of both crates from +crates.io. No local patches or sibling checkout are needed to build it. + +## Verification + +The published crates' source files match the reviewed sibling checkout at +`84039a1`. On the final branch based on `main`, these checks passed: + +```sh +cargo test --workspace --locked +cargo fmt --all -- --check +cargo clippy --workspace --all-targets --locked -- -D warnings +``` + +The test suite reported 1,135 passed and 67 ignored; ignored tests were not run. +Tests ran outside the sandbox because control-socket tests require socket +permissions. No live account or mounted-filesystem acceptance testing was +performed.