mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-13 09:27:03 +01:00
persistent entity cache, streaming batche
This commit is contained in:
1 parent
2336ad9e87
commit
a5e03ad8da
33 files changed
+1936
-65
No files matched your search
@@ -63,6 +63,10 @@ pub enum Request {
|
||||
/// timeline. Like `kind`, the offset is relative to the filtered set.
|
||||
#[serde(default)]
|
||||
range: Option<(i64, i64)>,
|
||||
/// Restrict the page to favourited photos. Older front-ends omit it and
|
||||
/// get the whole timeline, as before.
|
||||
#[serde(default)]
|
||||
favorites: bool,
|
||||
},
|
||||
/// The months the timeline spans (newest first, with per-month counts) so a
|
||||
/// front-end can build a date scrubber without paging the whole library.
|
||||
@@ -91,6 +95,14 @@ pub enum Request {
|
||||
PhotoThumbs { uids: Vec<String> },
|
||||
/// Download a photo's full content into the cache; replies with its path.
|
||||
OpenPhoto { uid: String },
|
||||
/// Add or remove Proton's `Favorite` tag on a photo. Replies with
|
||||
/// [`Response::Ok`].
|
||||
///
|
||||
/// Favouriting a photo that is *not* on this account's own photos volume —
|
||||
/// one shared with us, or one that lives only in an album — needs it
|
||||
/// re-encrypted for our timeline, which the SDK does not implement yet; the
|
||||
/// daemon reports that as an error rather than silently doing nothing.
|
||||
SetPhotoFavorite { uid: String, favorite: bool },
|
||||
/// Upload the photo at `source_path` under the given name and media type.
|
||||
///
|
||||
/// A path, not the bytes: this protocol is line-delimited JSON, and
|
||||
@@ -325,6 +337,39 @@ pub enum Request {
|
||||
/// The by-uid twin of [`Request::RemovePublicLink`].
|
||||
RemovePublicLinkByUid { uid: String, id: String },
|
||||
|
||||
// ---- revisions --------------------------------------------------------
|
||||
/// List the version history of the file at mountpoint-relative `path`,
|
||||
/// newest first. Replies with [`Response::Revisions`].
|
||||
ListRevisions { path: String },
|
||||
/// The by-uid twin of [`Request::ListRevisions`].
|
||||
ListRevisionsByUid { uid: String },
|
||||
/// Make revision `revision_id` of the file at `path` current again. The
|
||||
/// server applies this asynchronously, so the restored content may take a
|
||||
/// moment to appear. Replies with [`Response::Ok`].
|
||||
RestoreRevision { path: String, revision_id: String },
|
||||
/// The by-uid twin of [`Request::RestoreRevision`].
|
||||
RestoreRevisionByUid { uid: String, revision_id: String },
|
||||
/// Permanently delete revision `revision_id` of the file at `path`. The
|
||||
/// content is unrecoverable, and the server refuses to delete the revision
|
||||
/// that is currently active. Replies with [`Response::Ok`].
|
||||
DeleteRevision { path: String, revision_id: String },
|
||||
/// The by-uid twin of [`Request::DeleteRevision`].
|
||||
DeleteRevisionByUid { uid: String, revision_id: String },
|
||||
/// Write revision `revision_id` of the file at `path` to the absolute local
|
||||
/// path `dest`, leaving the file's current content untouched. Replies with
|
||||
/// [`Response::Ok`].
|
||||
SaveRevisionAs {
|
||||
path: String,
|
||||
revision_id: String,
|
||||
dest: String,
|
||||
},
|
||||
/// The by-uid twin of [`Request::SaveRevisionAs`].
|
||||
SaveRevisionAsByUid {
|
||||
uid: String,
|
||||
revision_id: String,
|
||||
dest: String,
|
||||
},
|
||||
|
||||
// ---- shared by me -----------------------------------------------------
|
||||
/// List the nodes I have shared with others — collaborative shares that still
|
||||
/// have members, pending invitations or a public link. Replies with
|
||||
@@ -608,6 +653,36 @@ pub struct SharedItem {
|
||||
pub link: Option<PublicLinkInfo>,
|
||||
}
|
||||
|
||||
/// One entry of a file's version history (in [`Response::Revisions`]).
|
||||
///
|
||||
/// The size and modification time are the ones the *uploader* claimed in the
|
||||
/// revision's extended attributes, so an old client that wrote none leaves them
|
||||
/// `None`; `size_on_storage` is always known but counts ciphertext, which is
|
||||
/// larger than the file. A front-end shows the claimed size when it has one and
|
||||
/// falls back to the storage size.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RevisionInfo {
|
||||
/// Server-assigned id — the handle for [`Request::RestoreRevision`],
|
||||
/// [`Request::DeleteRevision`] and [`Request::SaveRevisionAs`].
|
||||
pub id: String,
|
||||
/// True for the revision that is the file's current content. It cannot be
|
||||
/// deleted, and restoring it is a no-op.
|
||||
pub is_active: bool,
|
||||
/// When the revision was created, epoch seconds.
|
||||
pub created: i64,
|
||||
/// Encrypted size on Proton's storage, in bytes.
|
||||
pub size_on_storage: i64,
|
||||
/// Plaintext size as claimed by the uploader, when it wrote one.
|
||||
pub claimed_size: Option<i64>,
|
||||
/// Modification time as claimed by the uploader (ISO-8601, verbatim).
|
||||
pub claimed_modified: Option<String>,
|
||||
/// The address that signed the revision, when it was not signed anonymously
|
||||
/// by the node key.
|
||||
pub signed_by: Option<String>,
|
||||
/// Whether the revision carries thumbnails.
|
||||
pub has_thumbnails: bool,
|
||||
}
|
||||
|
||||
/// What happened, in an [`ActivityEntry`]. Kept coarse: a front-end maps each to
|
||||
/// an icon and a verb, and the human detail lives in the entry's fields.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -1014,6 +1089,11 @@ pub struct PhotoItem {
|
||||
/// the split omit it; a front-end then treats everything as a still photo.
|
||||
#[serde(default = "default_photo_kind")]
|
||||
pub kind: PhotoKind,
|
||||
/// Whether the photo is favourited. Older daemons omit it, which a front-end
|
||||
/// reads as "not favourited" — the same thing it would show for a photo whose
|
||||
/// tags it has not learned yet.
|
||||
#[serde(default)]
|
||||
pub favorite: bool,
|
||||
}
|
||||
|
||||
/// A daemon too old to classify a timeline entry is assumed to have served a
|
||||
@@ -1209,6 +1289,9 @@ pub enum Response {
|
||||
AccountQuota { max_space: i64, used_space: i64 },
|
||||
/// Nodes I have shared with others (reply to [`Request::ListSharedByMe`]).
|
||||
SharedByMe { items: Vec<SharedItem> },
|
||||
/// A file's version history, newest first (reply to
|
||||
/// [`Request::ListRevisions`]).
|
||||
Revisions { items: Vec<RevisionInfo> },
|
||||
/// The daemon's recent activity, newest first (reply to
|
||||
/// [`Request::ListActivity`]).
|
||||
Activity { items: Vec<ActivityEntry> },
|
||||
@@ -1858,6 +1941,101 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The revision requests come in path and by-uid pairs like the share ones,
|
||||
/// and carry a revision id that must survive the round-trip verbatim: acting
|
||||
/// on the wrong revision deletes content the user meant to keep.
|
||||
#[test]
|
||||
fn revision_requests_roundtrip_in_both_addressing_forms() {
|
||||
let cases = [
|
||||
(
|
||||
Request::ListRevisions { path: "a/b".into() },
|
||||
r#"{"ListRevisions":{"path":"a/b"}}"#,
|
||||
),
|
||||
(
|
||||
Request::ListRevisionsByUid {
|
||||
uid: "vol~link".into(),
|
||||
},
|
||||
r#"{"ListRevisionsByUid":{"uid":"vol~link"}}"#,
|
||||
),
|
||||
(
|
||||
Request::RestoreRevision {
|
||||
path: "a/b".into(),
|
||||
revision_id: "rev-1".into(),
|
||||
},
|
||||
r#"{"RestoreRevision":{"path":"a/b","revision_id":"rev-1"}}"#,
|
||||
),
|
||||
(
|
||||
Request::RestoreRevisionByUid {
|
||||
uid: "vol~link".into(),
|
||||
revision_id: "rev-1".into(),
|
||||
},
|
||||
r#"{"RestoreRevisionByUid":{"uid":"vol~link","revision_id":"rev-1"}}"#,
|
||||
),
|
||||
(
|
||||
Request::DeleteRevision {
|
||||
path: "a/b".into(),
|
||||
revision_id: "rev-2".into(),
|
||||
},
|
||||
r#"{"DeleteRevision":{"path":"a/b","revision_id":"rev-2"}}"#,
|
||||
),
|
||||
(
|
||||
Request::DeleteRevisionByUid {
|
||||
uid: "vol~link".into(),
|
||||
revision_id: "rev-2".into(),
|
||||
},
|
||||
r#"{"DeleteRevisionByUid":{"uid":"vol~link","revision_id":"rev-2"}}"#,
|
||||
),
|
||||
(
|
||||
Request::SaveRevisionAs {
|
||||
path: "a/b".into(),
|
||||
revision_id: "rev-3".into(),
|
||||
dest: "/tmp/out.bin".into(),
|
||||
},
|
||||
r#"{"SaveRevisionAs":{"path":"a/b","revision_id":"rev-3","dest":"/tmp/out.bin"}}"#,
|
||||
),
|
||||
(
|
||||
Request::SaveRevisionAsByUid {
|
||||
uid: "vol~link".into(),
|
||||
revision_id: "rev-3".into(),
|
||||
dest: "/tmp/out.bin".into(),
|
||||
},
|
||||
r#"{"SaveRevisionAsByUid":{"uid":"vol~link","revision_id":"rev-3","dest":"/tmp/out.bin"}}"#,
|
||||
),
|
||||
];
|
||||
for (request, wire) in cases {
|
||||
assert_eq!(serde_json::to_string(&request).unwrap(), wire);
|
||||
let decoded: Request = serde_json::from_str(wire).unwrap();
|
||||
assert_eq!(serde_json::to_string(&decoded).unwrap(), wire);
|
||||
}
|
||||
}
|
||||
|
||||
/// A front-end that predates the favourites filter sends no `favorites`
|
||||
/// field, and must keep getting the whole timeline rather than an empty page.
|
||||
#[test]
|
||||
fn timeline_requests_without_the_favorites_filter_still_decode() {
|
||||
let decoded: Request =
|
||||
serde_json::from_str(r#"{"PhotosTimeline":{"offset":0,"limit":50}}"#).unwrap();
|
||||
assert!(matches!(
|
||||
decoded,
|
||||
Request::PhotosTimeline {
|
||||
favorites: false,
|
||||
kind: None,
|
||||
range: None,
|
||||
..
|
||||
}
|
||||
));
|
||||
|
||||
let wire = serde_json::to_string(&Request::SetPhotoFavorite {
|
||||
uid: "vol~link".into(),
|
||||
favorite: true,
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
wire,
|
||||
r#"{"SetPhotoFavorite":{"uid":"vol~link","favorite":true}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_share_requests_keep_their_existing_wire_shape() {
|
||||
let cases = [
|
||||
|
||||
Reference in new issue
Block a user