Compare commits

...
3 Commits
Author SHA1 Message Date
nvrl a0ab243be6 Merge branch 'main' into chore/sdk-0.11.0 2026-11-09 14:36:49 +01:00
nvrl 2930b457d4 fix(docs): exclude SDK review from wiki 2026-11-05 19:18:18 +01:00
nvrl d9da0e2263 build(sdk): bump to 0.11.0 2026-11-05 19:12:21 +01:00
12 changed files with 133 additions and 14 deletions

No files matched your search

Generated
+6 -4
View File
@@ -3540,6 +3540,7 @@ version = "3.7.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.23.1", "base64 0.23.1",
"chrono",
"dbus-secret-service-keyring-store", "dbus-secret-service-keyring-store",
"directories", "directories",
"ignore", "ignore",
@@ -3825,13 +3826,14 @@ dependencies = [
[[package]] [[package]]
name = "proton-drive-rs" name = "proton-drive-rs"
version = "0.10.1" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8fa2a88130883010b32035e208aaa36fb275f6d7d78ed44a1069b1d33d24481" checksum = "14a22e621abbc1c61b4640728a7de969c16b66d5d983b59c638d501fda25a7b9"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.23.1", "base64 0.23.1",
"bytes", "bytes",
"chrono",
"futures", "futures",
"getrandom 0.4.3", "getrandom 0.4.3",
"hex", "hex",
@@ -3852,9 +3854,9 @@ dependencies = [
[[package]] [[package]]
name = "proton-sdk" name = "proton-sdk"
version = "0.10.1" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af0bf8d50fa829d1a65b09c6855b606578d02734d4089874c971d1063c23c353" checksum = "bd55a5e8b6600f3aa282ebbdf13ff366431ff9aae84d4eb20b0ef06d598919fe"
dependencies = [ dependencies = [
"aes-gcm 0.11.1", "aes-gcm 0.11.1",
"async-trait", "async-trait",
+3 -2
View File
@@ -15,8 +15,9 @@ keywords = ["proton", "drive", "fuse", "sync", "linux"]
categories = ["filesystem", "command-line-utilities"] categories = ["filesystem", "command-line-utilities"]
[workspace.dependencies] [workspace.dependencies]
proton-sdk = { version = "0.10.1" } proton-sdk = { version = "0.11.0" }
proton-drive-rs = { version = "0.10.1" } proton-drive-rs = { version = "0.11.0" }
chrono = { version = "0.4", default-features = false, features = ["std"] }
# Internal crates. # Internal crates.
pdfs-core = { path = "crates/pdfs-core" } pdfs-core = { path = "crates/pdfs-core" }
+1
View File
@@ -12,6 +12,7 @@ description = "Core auth/session/config for the Proton Drive Linux client"
[dependencies] [dependencies]
proton-sdk.workspace = true proton-sdk.workspace = true
proton-drive-rs.workspace = true proton-drive-rs.workspace = true
chrono.workspace = true
tokio.workspace = true tokio.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
+1
View File
@@ -29,6 +29,7 @@ pub mod similar;
pub mod syncignore; pub mod syncignore;
pub mod syncroot; pub mod syncroot;
pub mod takeout; pub mod takeout;
pub mod timestamp;
pub mod tray; pub mod tray;
pub mod update; pub mod update;
+33
View File
@@ -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<ModificationTime> {
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());
}
}
+2
View File
@@ -939,6 +939,8 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U
}); });
let metadata = proton_drive_rs::PhotoUploadMetadata { let metadata = proton_drive_rs::PhotoUploadMetadata {
capture_time, capture_time,
last_modification_time: capture_time
.and_then(pdfs_core::timestamp::modification_time),
..Default::default() ..Default::default()
}; };
let thumbnails = core.upload_thumbnails(Path::new(&source_path), &name); let thumbnails = core.upload_thumbnails(Path::new(&source_path), &name);
+10 -4
View File
@@ -352,7 +352,13 @@ impl DriveApi for ProtonDriveClient {
name: &str, name: &str,
last_modification_time: Option<i64>, last_modification_time: Option<i64>,
) -> Result<NodeUid> { ) -> Result<NodeUid> {
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( async fn upload_file(
@@ -384,7 +390,7 @@ impl DriveApi for ProtonDriveClient {
reader, reader,
intended_size, intended_size,
thumbnails, thumbnails,
last_modification_time, last_modification_time.and_then(pdfs_core::timestamp::modification_time),
aead, aead,
) )
.await .await
@@ -409,7 +415,7 @@ impl DriveApi for ProtonDriveClient {
reader, reader,
intended_size, intended_size,
thumbnails, thumbnails,
last_modification_time, last_modification_time.and_then(pdfs_core::timestamp::modification_time),
aead, aead,
) )
.await .await
@@ -429,7 +435,7 @@ impl DriveApi for ProtonDriveClient {
reader, reader,
intended_size, intended_size,
thumbnails, thumbnails,
last_modification_time, last_modification_time.and_then(pdfs_core::timestamp::modification_time),
) )
.await .await
} }
+1
View File
@@ -200,6 +200,7 @@ impl Core {
let reader = CountingReader::new(std::io::BufReader::new(file), &guard); let reader = CountingReader::new(std::io::BufReader::new(file), &guard);
let metadata = PhotoUploadMetadata { let metadata = PhotoUploadMetadata {
capture_time: Some(candidate.wanted), capture_time: Some(candidate.wanted),
last_modification_time: pdfs_core::timestamp::modification_time(candidate.wanted),
..Default::default() ..Default::default()
}; };
let uploaded = self.rt.block_on(photos.upload_photo_from( let uploaded = self.rt.block_on(photos.upload_photo_from(
+9 -3
View File
@@ -344,6 +344,9 @@ impl Core {
); );
let metadata = PhotoUploadMetadata { let metadata = PhotoUploadMetadata {
capture_time: photo.capture_time, capture_time: photo.capture_time,
last_modification_time: photo
.capture_time
.and_then(pdfs_core::timestamp::modification_time),
..Default::default() ..Default::default()
}; };
// A thumbnail is made from a file, so a photo that can have one // 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); let thumbnails = self.upload_thumbnails(path, &photo.name);
// No sidecar and no date in the name: the photo's own // No sidecar and no date in the name: the photo's own
// EXIF, before Drive files it under today. // 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 { let metadata = PhotoUploadMetadata {
capture_time: metadata capture_time,
.capture_time last_modification_time: capture_time
.or_else(|| pdfs_core::exiftime::capture_time_of(path)), .and_then(pdfs_core::timestamp::modification_time),
..metadata ..metadata
}; };
std::fs::File::open(path) std::fs::File::open(path)
+6
View File
@@ -11,6 +11,12 @@ scratch (user data in `staging/` and `recovery/` is never touched by this).
## [Unreleased] ## [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.
Schema: **45**. Synced folders now distinguish device backups from My files attachments. Schema: **45**. Synced folders now distinguish device backups from My files attachments.
Existing folders remain device backups; older builds cannot open the upgraded database. Existing folders remain device backups; older builds cannot open the upgraded database.
+60
View File
@@ -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<chrono::FixedOffset>`.
- 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<i64>` to
`Option<ModificationTime>` 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.
+1 -1
View File
@@ -91,7 +91,7 @@ SECTIONS = [
] ]
# Too long or too internal for the wiki; links to them go to GitHub. # Too long or too internal for the wiki; links to them go to GitHub.
EXCLUDED = {"BUGS.md", "CHANGELOG.md"} EXCLUDED = {"BUGS.md", "CHANGELOG.md", "SDK-0.11.0.md"}
# Files outside docs/ whose links are checked but not published. # Files outside docs/ whose links are checked but not published.
CHECKED = ["README.md", "CONTRIBUTING.md", "SECURITY.md"] CHECKED = ["README.md", "CONTRIBUTING.md", "SECURITY.md"]