mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
docs(core,fuse,gui,cli): US spelling and correct schema lines
User-visible strings, doc comments and docs now say "Favorite" and "color", matching the `pdfs favorite` command; UI_UX_PLAN.md §8 records the choice. The changelog's 1.11.x and Unreleased entries said schema 30, but V31 (`photos.resolved_at`) shipped in 1.11.0. Adds the missing release links for 1.10.0 to 1.11.1.
This commit is contained in:
1 parent
8351382ef2
commit
91a5f21340
18 files changed
+78
-74
No files matched your search
@@ -146,11 +146,11 @@ enum Command {
|
||||
/// Skip this many photos from the start of the timeline.
|
||||
#[arg(long, default_value_t = 0)]
|
||||
offset: usize,
|
||||
/// List only favourited photos.
|
||||
/// List only favorited photos.
|
||||
#[arg(long)]
|
||||
favorites: bool,
|
||||
},
|
||||
/// Add or remove a photo's favourite mark.
|
||||
/// Add or remove a photo's favorite mark.
|
||||
Favorite {
|
||||
/// Photo node uid in `volume~link` form (from `pdfs photos`).
|
||||
uid: String,
|
||||
@@ -188,7 +188,7 @@ enum Command {
|
||||
/// and rewrites them at the right time.
|
||||
///
|
||||
/// Proton cannot edit a sealed capture time, so a rewrite means re-uploading
|
||||
/// the photo and trashing the original — favourites and albums are carried
|
||||
/// the photo and trashing the original — favorites and albums are carried
|
||||
/// across, and the original goes to Proton's trash, not away. Run it with
|
||||
/// `--dry-run` first.
|
||||
RedatePhotos {
|
||||
|
||||
@@ -63,7 +63,7 @@ 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
|
||||
/// Restrict the page to favorited photos. Older front-ends omit it and
|
||||
/// get the whole timeline, as before.
|
||||
#[serde(default)]
|
||||
favorites: bool,
|
||||
@@ -181,7 +181,7 @@ pub enum Request {
|
||||
/// time by more than a day: `IMG-20230219-WA0001.jpg` filed under 2026 is
|
||||
/// the signature of an import whose metadata sidecars did not match.
|
||||
///
|
||||
/// Favourites and album memberships are carried across; the original is
|
||||
/// Favorites and album memberships are carried across; the original is
|
||||
/// **trashed**, not deleted, so a bad run is recoverable from Proton's
|
||||
/// trash. With `dry_run` nothing is uploaded or trashed and the report says
|
||||
/// what would have been done.
|
||||
@@ -1395,8 +1395,8 @@ 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
|
||||
/// Whether the photo is favorited. Older daemons omit it, which a front-end
|
||||
/// reads as "not favorited" — the same thing it would show for a photo whose
|
||||
/// tags it has not learned yet.
|
||||
#[serde(default)]
|
||||
pub favorite: bool,
|
||||
@@ -2703,7 +2703,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A front-end that predates the favourites filter sends no `favorites`
|
||||
/// A front-end that predates the favorites 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() {
|
||||
|
||||
@@ -178,7 +178,7 @@ impl Db {
|
||||
ratio: r.get(3)?,
|
||||
thumb_state: r.get(4)?,
|
||||
kind: crate::control::PhotoKind::from_i64(r.get(5)?),
|
||||
// Album rows have no favourite state of their own: an album
|
||||
// Album rows have no favorite state of their own: an album
|
||||
// photo on someone else's volume is not in our timeline, and
|
||||
// favouriting one is not supported yet anyway.
|
||||
favorite: false,
|
||||
@@ -239,7 +239,7 @@ impl Db {
|
||||
ratio: r.get(3)?,
|
||||
thumb_state: r.get(4)?,
|
||||
kind: crate::control::PhotoKind::from_i64(r.get(5)?),
|
||||
// Album rows have no favourite state of their own: an album
|
||||
// Album rows have no favorite state of their own: an album
|
||||
// photo on someone else's volume is not in our timeline, and
|
||||
// favouriting one is not supported yet anyway.
|
||||
favorite: false,
|
||||
|
||||
@@ -684,7 +684,7 @@ CREATE INDEX idx_album_photos_seq ON album_photos(album_uid, seq);
|
||||
CREATE INDEX idx_album_photos_uid ON album_photos(uid);
|
||||
";
|
||||
|
||||
/// V20: whether a photo is a favourite.
|
||||
/// V20: whether a photo is a favorite.
|
||||
///
|
||||
/// A column rather than a tag table: `Favorite` is the only Proton photo tag a
|
||||
/// user sets by hand (the rest — Video, Screenshot, Selfie, Raw… — are the
|
||||
@@ -938,7 +938,7 @@ CREATE INDEX IF NOT EXISTS idx_photos_group ON photos(group_key);
|
||||
/// Schema v31: which photos a refresh still has to read from the server.
|
||||
///
|
||||
/// A refresh used to resolve every photo's node to learn its name, media type,
|
||||
/// favourite tag, content hash and photo relation — minutes of round-trips for a
|
||||
/// favorite tag, content hash and photo relation — minutes of round-trips for a
|
||||
/// library of any size, repeated every time the timeline went stale. `resolved_at`
|
||||
/// records that the work was already done, so a refresh only reads the photos it
|
||||
/// has never read and the ones a remote event marked stale (`resolved_at = NULL`).
|
||||
|
||||
@@ -455,7 +455,7 @@ impl Db {
|
||||
/// restricts the page to one tab (Photos / Videos / Raw); `range`, when set,
|
||||
/// restricts it to a `[from, to)` capture-time window (epoch seconds) — the
|
||||
/// date scrubber's jump. `offset` is relative to whatever the filters leave.
|
||||
/// `favorites` restricts the page to favourited photos.
|
||||
/// `favorites` restricts the page to favorited photos.
|
||||
///
|
||||
/// The page holds one row per *group*: the RAW and the JPEG of one shot are
|
||||
/// one tile. The Raw tab is the exception and lists every raw file, because
|
||||
@@ -620,7 +620,7 @@ impl Db {
|
||||
Ok(photos)
|
||||
}
|
||||
|
||||
/// Record a photo's favourite flag locally, after the server accepted the
|
||||
/// Record a photo's favorite flag locally, after the server accepted the
|
||||
/// change. A uid the timeline does not hold is a no-op — an album photo on
|
||||
/// someone else's volume is never in our own `photos` table.
|
||||
pub fn photos_set_favorite(&self, uid: &str, favorite: bool) -> Result<()> {
|
||||
|
||||
@@ -222,7 +222,7 @@ fn favorites_are_remembered_across_refreshes_and_filter_a_page() {
|
||||
assert_eq!(
|
||||
favorites.iter().map(|p| p.uid.as_str()).collect::<Vec<_>>(),
|
||||
["p1"],
|
||||
"only the favourited photo is in a favourites page"
|
||||
"only the favorited photo is in a favorites page"
|
||||
);
|
||||
|
||||
// A local toggle survives a refresh that could not resolve the photo's node
|
||||
|
||||
@@ -812,7 +812,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidecar_supplies_capture_time_and_favourite() {
|
||||
fn sidecar_supplies_capture_time_and_favorite() {
|
||||
let mut sidecars = HashMap::new();
|
||||
sidecar(
|
||||
&mut sidecars,
|
||||
|
||||
@@ -511,7 +511,7 @@ impl PhotoEventPlan {
|
||||
///
|
||||
/// `is_echo` claims an event as this daemon's own write. The feed replays our
|
||||
/// writes back at us and a `NodeUpdated` carries no revision id, so without this
|
||||
/// every favourite the user toggles here would mark its own photo stale and buy
|
||||
/// every favorite the user toggles here would mark its own photo stale and buy
|
||||
/// a resolve of a node we just wrote.
|
||||
fn classify_photo_events(
|
||||
events: &[DriveEvent],
|
||||
@@ -529,7 +529,7 @@ fn classify_photo_events(
|
||||
if *is_trashed {
|
||||
plan.gone.push(node_uid.to_string());
|
||||
} else if !is_echo(node_uid) {
|
||||
// An untrash, a rename, a favourite toggled on the phone: the
|
||||
// An untrash, a rename, a favorite toggled on the phone: the
|
||||
// event says which node changed but never what about it, so
|
||||
// the uid is marked stale and the next refresh reads it.
|
||||
plan.touched.push(node_uid.to_string());
|
||||
@@ -974,7 +974,7 @@ mod tests {
|
||||
},
|
||||
DriveEvent::NodeUpdated {
|
||||
id: DriveEventId::from("e3"),
|
||||
node_uid: uid("favourited"),
|
||||
node_uid: uid("favorited"),
|
||||
parent_node_uid: None,
|
||||
is_trashed: false,
|
||||
is_shared: false,
|
||||
@@ -986,7 +986,7 @@ mod tests {
|
||||
plan,
|
||||
PhotoEventPlan {
|
||||
gone: vec![uid("deleted").to_string(), uid("trashed").to_string()],
|
||||
touched: vec![uid("favourited").to_string()],
|
||||
touched: vec![uid("favorited").to_string()],
|
||||
full: false,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -387,9 +387,9 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U
|
||||
Some(node_uid) => match core.set_photo_favorite(&node_uid, favorite) {
|
||||
Ok(()) => CtlResponse::Ok {
|
||||
message: if favorite {
|
||||
"added to favourites".to_string()
|
||||
"added to favorites".to_string()
|
||||
} else {
|
||||
"removed from favourites".to_string()
|
||||
"removed from favorites".to_string()
|
||||
},
|
||||
},
|
||||
Err(e) => CtlResponse::error(e),
|
||||
|
||||
@@ -501,7 +501,7 @@ impl Core {
|
||||
.map_err(|e| CoreError::from_api(&e, "update photo tags"))?;
|
||||
// The photo event feed replays this write back at us as a bare
|
||||
// `NodeUpdated`, which is indistinguishable from the same photo being
|
||||
// favourited on a phone. Claim the echo, or the gallery pays for a
|
||||
// favorited on a phone. Claim the echo, or the gallery pays for a
|
||||
// resolve of a node whose one changed field is already stored below.
|
||||
self.note_self_change(uid);
|
||||
self.db
|
||||
@@ -1469,7 +1469,7 @@ impl Core {
|
||||
NodeKind::File { media_type, .. } => Some(media_type.clone()),
|
||||
NodeKind::Folder => None,
|
||||
};
|
||||
// The favourite state is a tag on the photo node, so it
|
||||
// The favorite state is a tag on the photo node, so it
|
||||
// rides along with the metadata resolve rather than
|
||||
// costing a listing of its own.
|
||||
let favorite = node
|
||||
@@ -1507,7 +1507,7 @@ impl Core {
|
||||
let key = it.uid.to_string();
|
||||
let main_uid = main_of.get(&key).cloned();
|
||||
// An unresolved photo keeps whatever was learned before, for the
|
||||
// favourite flag as much as for the name and media type.
|
||||
// favorite flag as much as for the name and media type.
|
||||
match meta.get(&key).cloned() {
|
||||
Some(m) => db::TimelineRow {
|
||||
name: m.name,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//!
|
||||
//! Proton has no API for editing a sealed capture time, so the repair is a
|
||||
//! rewrite: download the photo, upload it again with the capture time its file
|
||||
//! name implies, put the new copy back into the albums and favourites the old
|
||||
//! name implies, put the new copy back into the albums and favorites the old
|
||||
//! one was in, then **trash** (not delete) the original. Trash rather than
|
||||
//! delete so a bad run is recoverable from Proton's own trash for 30 days.
|
||||
//!
|
||||
@@ -213,7 +213,7 @@ impl Core {
|
||||
drop(guard);
|
||||
let new_uid = uploaded.map_err(|e| CoreError::from_api(&e, "re-upload photo"))?;
|
||||
|
||||
// Favourite and albums before the trash: a failure here leaves both
|
||||
// Favorite and albums before the trash: a failure here leaves both
|
||||
// copies on the account, which is recoverable by hand. Losing the
|
||||
// original first would not be.
|
||||
if candidate.favorite {
|
||||
@@ -227,7 +227,7 @@ impl Core {
|
||||
// just set does not come back as a remote change.
|
||||
Ok(_) => self.note_self_change(&new_uid),
|
||||
Err(e) => {
|
||||
warn!(name = candidate.name, error = %e, "cannot re-favourite re-dated photo")
|
||||
warn!(name = candidate.name, error = %e, "cannot re-favorite re-dated photo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,9 +357,9 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
// Favourites are a separate call: the upload seal takes classification
|
||||
// Favorites are a separate call: the upload seal takes classification
|
||||
// tags, but `Favorite` is not one of them — it goes through the photos
|
||||
// volume's dedicated favourite endpoint.
|
||||
// volume's dedicated favorite endpoint.
|
||||
if !favorites.is_empty() {
|
||||
let updates: Vec<PhotoTagsUpdate> = favorites
|
||||
.into_iter()
|
||||
@@ -370,7 +370,7 @@ impl Core {
|
||||
})
|
||||
.collect();
|
||||
if let Err(e) = self.rt.block_on(photos.update_photos(&updates)) {
|
||||
warn!(error = %e, "marking imported favourites failed");
|
||||
warn!(error = %e, "marking imported favorites failed");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) struct Viewer {
|
||||
pub(crate) info_map: gtk4::Button,
|
||||
/// Coordinates behind `info_map`, once a photo with GPS tags is shown.
|
||||
pub(crate) coords: RefCell<Option<(f64, f64)>>,
|
||||
/// The favourite toggle in the top bar, and whether it is being set from
|
||||
/// The favorite toggle in the top bar, and whether it is being set from
|
||||
/// the model rather than by the user — the same suppression the details pane
|
||||
/// uses for its pin switch, so painting a photo doesn't fire a round-trip.
|
||||
pub(crate) favorite: gtk4::ToggleButton,
|
||||
@@ -51,7 +51,7 @@ pub(crate) struct ExifInfo {
|
||||
pub(crate) coords: Option<(f64, f64)>,
|
||||
}
|
||||
|
||||
/// Paint the favourite toggle without firing its handler.
|
||||
/// Paint the favorite toggle without firing its handler.
|
||||
pub(crate) fn show_favorite(viewer: &Rc<Viewer>, favorite: bool) {
|
||||
viewer.favorite_suppress.set(true);
|
||||
viewer.favorite.set_active(favorite);
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn show_favorite(viewer: &Rc<Viewer>, favorite: bool) {
|
||||
viewer.favorite_suppress.set(false);
|
||||
}
|
||||
|
||||
/// Filled star for a favourite, outline for the rest.
|
||||
/// Filled star for a favorite, outline for the rest.
|
||||
fn set_favorite_icon(button: >k4::ToggleButton, favorite: bool) {
|
||||
button.set_icon_name(if favorite {
|
||||
"starred-symbolic"
|
||||
@@ -68,7 +68,7 @@ fn set_favorite_icon(button: >k4::ToggleButton, favorite: bool) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Write a favourite change back into the loaded gallery page, so returning to
|
||||
/// Write a favorite change back into the loaded gallery page, so returning to
|
||||
/// the grid (or reopening the photo) shows what was just set without a reload.
|
||||
pub(crate) fn set_gallery_favorite(ui: &Rc<Ui>, uid: &str, favorite: bool) {
|
||||
let Some(idx) = find_photo_index(&ui.gallery.model, uid) else {
|
||||
@@ -147,7 +147,7 @@ pub(crate) fn load_photo(ui: &Rc<Ui>, viewer: &Rc<Viewer>, uid: String) {
|
||||
*viewer.path.borrow_mut() = None;
|
||||
clear_info(viewer);
|
||||
|
||||
// The favourite state comes from the timeline page the gallery already has,
|
||||
// The favorite state comes from the timeline page the gallery already has,
|
||||
// so the button is right on the first frame instead of after a round-trip.
|
||||
let favorite = find_photo_index(&ui.gallery.model, &uid)
|
||||
.and_then(|idx| ui.gallery.model.item(idx))
|
||||
@@ -556,7 +556,7 @@ pub(crate) fn open_photo_viewer(ui: &Rc<Ui>, initial_uid: String) {
|
||||
|
||||
let favorite_btn = gtk4::ToggleButton::builder()
|
||||
.icon_name("non-starred-symbolic")
|
||||
.tooltip_text("Favourite")
|
||||
.tooltip_text("Favorite")
|
||||
.valign(gtk4::Align::Center)
|
||||
.build();
|
||||
favorite_btn.add_css_class("flat");
|
||||
@@ -758,7 +758,7 @@ pub(crate) fn open_photo_viewer(ui: &Rc<Ui>, initial_uid: String) {
|
||||
_ => Some("The mount service didn't respond.".to_string()),
|
||||
};
|
||||
if let Some(detail) = failed {
|
||||
toast_error(&ui_result, "Couldn't change the favourite", &detail);
|
||||
toast_error(&ui_result, "Couldn't change the favorite", &detail);
|
||||
// The server refused, so the button must go back to describing
|
||||
// what is actually stored — without firing this handler again.
|
||||
if *viewer_result.uid.borrow() == uid {
|
||||
|
||||
@@ -84,7 +84,7 @@ pub(crate) struct GalleryState {
|
||||
/// The capture-time window the timeline is currently filtered to, or `None`
|
||||
/// for the whole span. Read by [`load_gallery`], set by the date dropdown.
|
||||
pub(crate) range: Cell<Option<(i64, i64)>>,
|
||||
/// The favourites toggle, and whether it is on. When on, the timeline is
|
||||
/// The favorites toggle, and whether it is on. When on, the timeline is
|
||||
/// restricted to photos carrying Proton's `Favorite` tag.
|
||||
pub(crate) favorites_btn: gtk4::ToggleButton,
|
||||
pub(crate) favorites: Cell<bool>,
|
||||
@@ -495,12 +495,12 @@ pub(crate) fn build_gallery_page() -> (gtk4::Widget, GalleryWidgets) {
|
||||
tab_group.append(btn);
|
||||
}
|
||||
|
||||
// Favourites: a filter, not a tab — it cuts across Photos / Videos / Raw, so
|
||||
// Favorites: a filter, not a tab — it cuts across Photos / Videos / Raw, so
|
||||
// it stays outside the segmented control rather than becoming a fifth option
|
||||
// that would silently drop the kind the user picked.
|
||||
let favorites_btn = gtk4::ToggleButton::builder()
|
||||
.icon_name("starred-symbolic")
|
||||
.tooltip_text("Show only favourites")
|
||||
.tooltip_text("Show only favorites")
|
||||
.build();
|
||||
favorites_btn.add_css_class("pill");
|
||||
|
||||
@@ -962,7 +962,7 @@ pub(crate) fn wire_gallery(
|
||||
});
|
||||
}
|
||||
|
||||
// Favourites: reload the timeline restricted to favourites (or back to all).
|
||||
// Favorites: reload the timeline restricted to favorites (or back to all).
|
||||
// Independent of the kind tabs and the date jump, both of which keep their
|
||||
// current value across the toggle.
|
||||
let ui_fav = ui.clone();
|
||||
@@ -2118,7 +2118,7 @@ pub(crate) fn short_capture_time(secs: i64) -> String {
|
||||
|
||||
/// Fetch a timeline page from the daemon. When `append` is false the model is
|
||||
/// cleared first (fresh load); otherwise the next page is tacked on.
|
||||
/// The empty state for a timeline filtered to `kind`, favourites and/or a
|
||||
/// The empty state for a timeline filtered to `kind`, favorites and/or a
|
||||
/// `month`: what is missing, in the filter's own words.
|
||||
pub(crate) fn empty_timeline_text(
|
||||
kind: Option<PhotoKind>,
|
||||
@@ -2138,12 +2138,12 @@ pub(crate) fn empty_timeline_text(
|
||||
"Photos you upload to Proton Drive appear here.".to_string(),
|
||||
),
|
||||
(true, None, None) => (
|
||||
"No favourites yet".to_string(),
|
||||
"No favorites yet".to_string(),
|
||||
"Star a photo in the viewer or from its menu to find it here.".to_string(),
|
||||
),
|
||||
(true, _, _) => (
|
||||
format!("No favourite {what}{when}"),
|
||||
"Turn off the favourites filter to see everything.".to_string(),
|
||||
format!("No favorite {what}{when}"),
|
||||
"Turn off the favorites filter to see everything.".to_string(),
|
||||
),
|
||||
(false, _, _) => (
|
||||
format!("No {what}{when}"),
|
||||
@@ -2222,9 +2222,9 @@ fn show_photo_menu(ui: &Rc<Ui>, photo: &PhotoItem, anchor: >k4::Button, x: f64
|
||||
let (ui_c, uid, favorite) = (ui.clone(), photo.uid.clone(), photo.favorite);
|
||||
item(
|
||||
if favorite {
|
||||
"Remove from Favourites"
|
||||
"Remove from Favorites"
|
||||
} else {
|
||||
"Add to Favourites"
|
||||
"Add to Favorites"
|
||||
},
|
||||
if favorite {
|
||||
"non-starred-symbolic"
|
||||
@@ -2269,18 +2269,18 @@ fn set_photo_favorite(ui: &Rc<Ui>, uid: String, favorite: bool) {
|
||||
toast(
|
||||
&ui,
|
||||
if favorite {
|
||||
"Added to Favourites"
|
||||
"Added to Favorites"
|
||||
} else {
|
||||
"Removed from Favourites"
|
||||
"Removed from Favorites"
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(Ok(Response::Error { message, .. })) => {
|
||||
toast_error(&ui, "Couldn't change the favourite", &message)
|
||||
toast_error(&ui, "Couldn't change the favorite", &message)
|
||||
}
|
||||
_ => toast_error(
|
||||
&ui,
|
||||
"Couldn't change the favourite",
|
||||
"Couldn't change the favorite",
|
||||
"The mount service didn't respond.",
|
||||
),
|
||||
}
|
||||
@@ -2540,14 +2540,14 @@ mod tests {
|
||||
#[test]
|
||||
fn an_empty_filter_names_what_it_filtered() {
|
||||
assert_eq!(empty_timeline_text(None, false, None).0, "No photos yet");
|
||||
assert_eq!(empty_timeline_text(None, true, None).0, "No favourites yet");
|
||||
assert_eq!(empty_timeline_text(None, true, None).0, "No favorites yet");
|
||||
assert_eq!(
|
||||
empty_timeline_text(Some(PhotoKind::Video), false, Some("June 2024")).0,
|
||||
"No videos in June 2024"
|
||||
);
|
||||
assert_eq!(
|
||||
empty_timeline_text(Some(PhotoKind::Raw), true, None).0,
|
||||
"No favourite raw files"
|
||||
"No favorite raw files"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
let appearance_group = adw::PreferencesGroup::builder().title("Appearance").build();
|
||||
let accent_row = adw::SwitchRow::builder()
|
||||
.title("Proton purple accent")
|
||||
.subtitle("Use the Proton brand colour instead of the system accent colour")
|
||||
.subtitle("Use the Proton brand color instead of the system accent color")
|
||||
.build();
|
||||
appearance_group.add(&accent_row);
|
||||
|
||||
@@ -531,11 +531,7 @@ pub(crate) fn wire_settings(
|
||||
let mut config = ui_accent.dirs.load_config();
|
||||
config.proton_accent = Some(on);
|
||||
if let Err(e) = ui_accent.dirs.save_config(&config) {
|
||||
toast_error(
|
||||
&ui_accent,
|
||||
"Couldn't save the accent colour",
|
||||
&e.to_string(),
|
||||
);
|
||||
toast_error(&ui_accent, "Couldn't save the accent color", &e.to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+10
-3
@@ -11,7 +11,7 @@ scratch (user data in `staging/` and `recovery/` is never touched by this).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
Schema: **30** (unchanged).
|
||||
Schema: **31** (unchanged).
|
||||
|
||||
### Added
|
||||
- **Pause and resume sync.** The Sync page, the tray and `pdfs sync pause [--for 1h]` /
|
||||
@@ -66,10 +66,12 @@ Schema: **30** (unchanged).
|
||||
keyring, a skipped two-factor or verification step — instead of the raw error. The button spins
|
||||
while signing in, and the page links to account creation and password reset. The two-factor
|
||||
dialog mentions recovery codes.
|
||||
- **US spelling throughout.** The app, the CLI and the docs say "Favorite" and "color", matching the
|
||||
`pdfs favorite` command.
|
||||
|
||||
## [1.11.1] — 2026-09-19
|
||||
|
||||
Schema: **30** (unchanged).
|
||||
Schema: **31** (unchanged).
|
||||
|
||||
### Fixed
|
||||
- **An upload parked behind a temp-file rename no longer stays parked forever.** A create for a
|
||||
@@ -96,7 +98,7 @@ Schema: **30** (unchanged).
|
||||
|
||||
## [1.11.0] — 2026-09-19
|
||||
|
||||
Schema: **30** (`photos.content_hash` / `main_uid` / `group_key`); SDK bumped to `proton-sdk` /
|
||||
Schema: **31** (V30 `photos.content_hash` / `main_uid` / `group_key`, V31 `photos.resolved_at`); SDK bumped to `proton-sdk` /
|
||||
`proton-drive-rs` **0.6.5**.
|
||||
|
||||
### Changed
|
||||
@@ -882,6 +884,11 @@ First stable release: FUSE files-on-demand mount, sync daemon under `proton-driv
|
||||
- The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated
|
||||
by a new POSIX compliance suite for the filesystem.
|
||||
|
||||
[1.11.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.11.1
|
||||
[1.11.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.11.0
|
||||
[1.10.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.10.2
|
||||
[1.10.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.10.1
|
||||
[1.10.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.10.0
|
||||
[1.9.3]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.9.3
|
||||
[1.9.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.9.2
|
||||
[1.9.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.9.1
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ Consolidated from the historical feature roadmap; current bug status lives in [`
|
||||
* **Custom FUSE Mount Options (Horizon 1)**: Expose custom mounting parameters in configuration (e.g. `ro` for read-only, `direct_io` to bypass kernel caching, `allow_other`). Auto-generate systemd user mount units upon sync folder configuration.
|
||||
* **Symbolic Link & Hard Link Virtualization (Horizon 2)**: Add symbolic link virtualization inside the metadata store (`NodeType::Symlink`) by intercepting `readlink(2)` and `symlink(2)`. Targets should sync remotely as small encrypted metadata placeholder files on Drive (`proton-vfs-symlink: <target>`).
|
||||
* **Desktop File Manager Integration (Horizon 2)**: Develop shell extension plugins for popular Linux file managers (Nautilus Python plugin, Dolphin Service Menus, Thunar custom actions) to offer context-menu shortcuts ("Pin", "Unpin", "Copy Share Link", "View Version History") and file overlay sync badges.
|
||||
* **Integrated Photo Gallery Enhancements (Horizon 2)**: Album *editing* — rename, cover photo, delete, and removing photos from an album — is still unported in the SDK (TS `updateAlbum` / `deleteAlbum` / `removePhotos`), as is adding a photo that lives on someone else's volume (TS `copyPhoto`). Album **creation** and adding own-volume photos landed with the Google Photos import. Still open: an auto-upload Pictures folder pipeline, and a GUI for creating albums by hand. Exif display, the date-based timeline scrubber and favourites are implemented.
|
||||
* **Integrated Photo Gallery Enhancements (Horizon 2)**: Album *editing* — rename, cover photo, delete, and removing photos from an album — is still unported in the SDK (TS `updateAlbum` / `deleteAlbum` / `removePhotos`), as is adding a photo that lives on someone else's volume (TS `copyPhoto`). Album **creation** and adding own-volume photos landed with the Google Photos import. Still open: an auto-upload Pictures folder pipeline, and a GUI for creating albums by hand. Exif display, the date-based timeline scrubber and favorites are implemented.
|
||||
* **Google Photos import follow-ups**: the importer uploads photos one at a time — a large export would finish sooner with a bounded concurrent upload pool. Trash folders are recognized by a hardcoded list of localized names ([`takeout.rs`](../crates/pdfs-core/src/takeout.rs) `TRASH_FOLDERS`); a locale not on it imports its trash as ordinary photos. Motion-photo `.MP`/`.MV` sidecar videos are ignored rather than uploaded as related photos. Live validation against a real export is pending.
|
||||
* **Random-Access Media Streaming seek support (Horizon 2)**: Intercept out-of-order sparse reads (player seeks) in `pdfs-fuse` and prioritize downloading blocks around the seek offset, aborting/postponing sequential pre-fetches.
|
||||
* **Dynamic Sync Dashboard & Queue Visualization (Horizon 2)**: GUI real-time transfer list (progress bars, speed, ETA), sync history feed, storage usage breakdown, and global pause/resume sync controls.
|
||||
@@ -135,10 +135,10 @@ Three properties that shape the code:
|
||||
- **The active revision cannot be deleted.** `Core::delete_revision_for_uid` checks that locally so the user gets a sentence rather than an API code, and the dialog gives the current row no delete button.
|
||||
- **Saving a version never overwrites.** `SaveRevisionAs` refuses an existing destination and removes a partial file if the download fails — a half-written export looks identical to a good one to every tool that opens it afterwards.
|
||||
|
||||
### Photo Favourites (Implemented)
|
||||
### Photo Favorites (Implemented)
|
||||
**Files**: [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs)
|
||||
|
||||
The gallery can mark and filter favourites, through the SDK's photo-tag API (`update_photos`). Schema **v20** adds `photos.favorite`; the flag is learned in the timeline enrichment pass that already resolves each photo's name and media type, and is kept across a refresh that could not resolve a photo — the same learned-and-kept rule as `media_type`. The lightbox carries a star toggle, the Photos header a favourites filter, and the CLI has `pdfs favorite <uid> [--remove]` plus `pdfs photos --favorites`.
|
||||
The gallery can mark and filter favorites, through the SDK's photo-tag API (`update_photos`). Schema **v20** adds `photos.favorite`; the flag is learned in the timeline enrichment pass that already resolves each photo's name and media type, and is kept across a refresh that could not resolve a photo — the same learned-and-kept rule as `media_type`. The lightbox carries a star toggle, the Photos header a favorites filter, and the CLI has `pdfs favorite <uid> [--remove]` plus `pdfs photos --favorites`.
|
||||
|
||||
Favouriting a photo that is not on this account's own photos volume (shared with us, or album-only) needs it re-encrypted for our timeline root, which the SDK does not implement; the daemon surfaces that as an error rather than silently doing nothing.
|
||||
|
||||
@@ -155,7 +155,7 @@ The gallery lays each day out in justified rows (`justify_rows` / `plan_rows` /
|
||||
### RAW + JPEG Grouping (Implemented)
|
||||
**Files**: [`photos.rs`](../crates/pdfs-core/src/db/photos.rs), [`migrations.rs`](../crates/pdfs-core/src/db/migrations.rs), [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs)
|
||||
|
||||
Schema **v30** adds `content_hash`, `main_uid` and an indexed `group_key` to `photos`. The first two come from the server's `PhotoProperties` and are filled by the enrichment pass in `refresh_timeline` that already resolves each photo's name, media type and favourite tag; `group_key` is computed in `photos_replace`, where the whole timeline is in hand.
|
||||
Schema **v30** adds `content_hash`, `main_uid` and an indexed `group_key` to `photos`. The first two come from the server's `PhotoProperties` and are filled by the enrichment pass in `refresh_timeline` that already resolves each photo's name, media type and favorite tag; `group_key` is computed in `photos_replace`, where the whole timeline is in hand.
|
||||
|
||||
- **Precedence.** The server relation first (`main_photo_uid` and `related_photo_uids`, read from whichever end resolves — the two can land in different enrichment chunks); then same capture day, same case-insensitive name stem, and one member raw while the other is not; otherwise the photo is its own group. The union-find pass is over the timeline in memory, so it costs one pass per refresh rather than a query per photo.
|
||||
- **Representative.** The non-raw member when there is one — a JPEG decodes in milliseconds and is what the person expects to see. `group_key` holds that member's uid, so "is this the tile" is `uid = group_key`, an index scan.
|
||||
|
||||
+8
-7
@@ -21,7 +21,7 @@ The client is functionally rich but the UI is built page by page, and it shows:
|
||||
`AdwHeaderBar` whose title is always "Proton Drive". Two stacked bars, wasted height, no current
|
||||
folder in the title.
|
||||
- **Icons and wording drift.** Upload uses `document-send-symbolic` (reads as "send"), offline uses
|
||||
stars (reads as "favourite"), trash icons mean "stop syncing", "remove device" and "remove
|
||||
stars (reads as "favorite"), trash icons mean "stop syncing", "remove device" and "remove
|
||||
bookmark". "Keep offline" / "Unpin" / "Remove offline copy" / "Available offline" are one concept.
|
||||
"Computers" vs "device", "Gallery" vs "Photos", "mount service" vs "daemon" vs "Connect".
|
||||
- **Theming is hardcoded.** `PROTON_PURPLE` overrides the system accent; badge, viewer and tile
|
||||
@@ -212,7 +212,7 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [
|
||||
- Header: title "Photos", `InlineViewSwitcher` Timeline | Albums in the header, Select toggle,
|
||||
`+ Upload` (fix: use `adw::ButtonContent`, today `.icon_name()` overwrites `.label()` at
|
||||
`photos.rs:426-432`), ⋮ menu: Import from Google Photos…, Refresh, Rebuild library.
|
||||
- Filters: a single filter dropdown/`ToggleGroup` (All, Photos, Videos, RAW, Favourites) instead of
|
||||
- Filters: a single filter dropdown/`ToggleGroup` (All, Photos, Videos, RAW, Favorites) instead of
|
||||
two segmented rows where "Photos" appears twice. Counts follow the active filter.
|
||||
- **Month scrubber** on the right edge that *jumps* (not filters); the month dropdown becomes a real
|
||||
"Filter by date" if kept.
|
||||
@@ -221,9 +221,9 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [
|
||||
- Tiles: `ContentFit::Cover` (crop) in justified rows, known aspect ratio from metadata to prevent
|
||||
reflow, placeholder as a tinted card of the final size, subtle hover (no 1.06 scale with heavy
|
||||
shadow on a 2 px gap grid), last row justified up to a max stretch.
|
||||
- Context menu on tiles: Open, Favourite, Add to album, Download/Save copy, Show in My files, Move to
|
||||
Trash. Selection: Shift range, Ctrl+A, bulk Favourite, Add to album, Download.
|
||||
- Empty states per filter ("No favourites yet", "No videos in June 2024") instead of the global
|
||||
- Context menu on tiles: Open, Favorite, Add to album, Download/Save copy, Show in My files, Move to
|
||||
Trash. Selection: Shift range, Ctrl+A, bulk Favorite, Add to album, Download.
|
||||
- Empty states per filter ("No favorites yet", "No videos in June 2024") instead of the global
|
||||
"No photos yet" with upload buttons.
|
||||
- Import: runs show a banner on Photos ("Importing from Google Photos — 1,204 of 5,000 · View") and
|
||||
Back returns to where the user came from.
|
||||
@@ -233,7 +233,7 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [
|
||||
(pinch, Ctrl+scroll, double-click), info panel beside the image instead of over the top bar,
|
||||
pages beyond the loaded 60, counter against the real total, videos play inline with
|
||||
`gtk::Video`, trash with the same confirm/undo rule as the grid, real filename in "Save a copy",
|
||||
success toast, "Open with…" app chooser, favourite tooltip reflects state, readable dates.
|
||||
success toast, "Open with…" app chooser, favorite tooltip reflects state, readable dates.
|
||||
|
||||
### Shared with me / Shared by me
|
||||
|
||||
@@ -292,7 +292,8 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [
|
||||
| Photos section | Photos | `image-x-generic-symbolic` |
|
||||
| Sharing | Shared by me / Shared with me | distinct icons (`emblem-shared` / `folder-publicshare`) |
|
||||
|
||||
Spelling: pick one (US "Favorite" or UK "Favourite") and apply it everywhere. Title Case for
|
||||
Spelling: US English throughout ("Favorite", "color"), matching the `pdfs favorite` command and
|
||||
Proton's own apps. Title Case for
|
||||
dialog headings and buttons in dialogs, Sentence case for menu items and rows (HIG).
|
||||
|
||||
## 9. Bugs found during the audit (quick wins)
|
||||
|
||||
Reference in new issue
Block a user