mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 00:47:00 +01:00
fix(core,fuse,cli,gui): say when an upgrade left the old daemon running
A package upgrade replaces the binaries but does not restart the user service, so the daemon keeps the old release and its bugs until the next login. On 3.2.0 the reporter of #33 still saw "Couldn't sync Documents: read .../snapshots/37: Permission denied", which only a daemon older than 3.2.0 can produce: since then the scan leaves an unreadable subfolder out and only an unreadable sync root fails the pass (B206). The daemon now reports its release in Status; one from before the field omits it, which reads as older. When the daemon is older than the front-end, the tray says "Update not finished - restart Proton Drive" and offers a restart, the sync card shows "Restart to finish updating" with a Restart button, and pdfs status and pdfs diagnose print the systemctl command. A newer daemon is not flagged, since then the front-end is the stale one. Refs #33
This commit is contained in:
1 parent
87361a4573
commit
91fa1614ed
19 files changed
+3988
-3562
No files matched your search
@@ -581,6 +581,7 @@ fn build_window(app: &adw::Application) {
|
||||
pause: locations_widgets.pause.clone(),
|
||||
paused: Cell::new(false),
|
||||
sign_in: locations_widgets.card_sign_in.clone(),
|
||||
restart: locations_widgets.card_restart.clone(),
|
||||
},
|
||||
queue: QueueState {
|
||||
group: locations_widgets.queue_group.clone(),
|
||||
|
||||
@@ -48,6 +48,8 @@ pub(crate) struct SyncCard {
|
||||
pub(crate) paused: Cell<bool>,
|
||||
/// Shown only while Proton refuses the session; opens the sign-in page.
|
||||
pub(crate) sign_in: gtk4::Button,
|
||||
/// Shown only while the daemon is older than the app; restarts it.
|
||||
pub(crate) restart: gtk4::Button,
|
||||
}
|
||||
|
||||
/// What a folder row's controls are built from. The subtitle and progress bar
|
||||
@@ -111,6 +113,7 @@ pub(crate) struct LocationsWidgets {
|
||||
pub(crate) card_title: gtk4::Label,
|
||||
pub(crate) card_detail: gtk4::Label,
|
||||
pub(crate) card_sign_in: gtk4::Button,
|
||||
pub(crate) card_restart: gtk4::Button,
|
||||
pub(crate) pause: adw::SplitButton,
|
||||
pub(crate) queue_group: adw::PreferencesGroup,
|
||||
pub(crate) retry_all: gtk4::Button,
|
||||
@@ -137,7 +140,7 @@ pub(crate) fn build_locations_page(history: >k4::Widget) -> (gtk4::Widget, Loc
|
||||
.build();
|
||||
add_folder.add_css_class("flat");
|
||||
let transfers_group = build_transfers_group();
|
||||
let (card, card_icon, card_title, card_detail, card_sign_in) = build_sync_card();
|
||||
let (card, card_icon, card_title, card_detail, card_sign_in, card_restart) = build_sync_card();
|
||||
let pause = build_pause_button();
|
||||
let (queue_group, retry_all) = build_queue_group();
|
||||
let conflicts_group = adw::PreferencesGroup::builder()
|
||||
@@ -257,6 +260,7 @@ pub(crate) fn build_locations_page(history: >k4::Widget) -> (gtk4::Widget, Loc
|
||||
card_title,
|
||||
card_detail,
|
||||
card_sign_in,
|
||||
card_restart,
|
||||
pause,
|
||||
queue_group,
|
||||
retry_all,
|
||||
@@ -299,6 +303,11 @@ pub(crate) fn wire_locations(ui: &Rc<Ui>, retry: >k4::Button, add_folder: >k
|
||||
.card
|
||||
.sign_in
|
||||
.connect_clicked(move |_| show_login(&ui_sign_in));
|
||||
let ui_restart = ui.clone();
|
||||
ui.locations
|
||||
.card
|
||||
.restart
|
||||
.connect_clicked(move |_| restart_service_then(&ui_restart, |_| {}));
|
||||
let ui_retry = ui.clone();
|
||||
ui.locations
|
||||
.queue
|
||||
@@ -396,6 +405,7 @@ fn build_sync_card() -> (
|
||||
gtk4::Label,
|
||||
gtk4::Label,
|
||||
gtk4::Button,
|
||||
gtk4::Button,
|
||||
) {
|
||||
let icon = gtk4::Image::builder()
|
||||
.icon_name("pdfs-sync-symbolic")
|
||||
@@ -430,10 +440,18 @@ fn build_sync_card() -> (
|
||||
.build();
|
||||
sign_in.add_css_class("pill");
|
||||
sign_in.add_css_class("suggested-action");
|
||||
let restart = gtk4::Button::builder()
|
||||
.label(gettext("Restart"))
|
||||
.valign(gtk4::Align::Center)
|
||||
.visible(false)
|
||||
.build();
|
||||
restart.add_css_class("pill");
|
||||
restart.add_css_class("suggested-action");
|
||||
card.append(&icon);
|
||||
card.append(&text);
|
||||
card.append(&sign_in);
|
||||
(card, icon, title, detail, sign_in)
|
||||
card.append(&restart);
|
||||
(card, icon, title, detail, sign_in, restart)
|
||||
}
|
||||
|
||||
/// Pause/Resume, with timed pauses in its menu.
|
||||
|
||||
@@ -1232,6 +1232,7 @@ pub(crate) fn paint_status(ui: &Rc<Ui>, status: Response) {
|
||||
paused_until,
|
||||
session_expired,
|
||||
tokens_unsaved,
|
||||
version,
|
||||
..
|
||||
} = status
|
||||
else {
|
||||
@@ -1246,6 +1247,8 @@ pub(crate) fn paint_status(ui: &Rc<Ui>, status: Response) {
|
||||
SyncState::SessionExpired { queued }
|
||||
} else if tokens_unsaved {
|
||||
SyncState::TokensUnsaved
|
||||
} else if pdfs_core::control::daemon_outdated(&version) {
|
||||
SyncState::Outdated { running: version }
|
||||
} else if paused {
|
||||
SyncState::Paused {
|
||||
until: paused_until,
|
||||
@@ -1317,6 +1320,12 @@ pub(crate) enum SyncState {
|
||||
/// The keyring refuses the session's refreshed tokens. Sync goes on, but
|
||||
/// a reboot before the keyring takes them signs the user out.
|
||||
TokensUnsaved,
|
||||
/// The daemon is an older release than this window: an upgrade replaced
|
||||
/// the files but left the service running, with the old release's bugs,
|
||||
/// until it restarts (#33). `running` is empty when it is too old to say.
|
||||
Outdated {
|
||||
running: String,
|
||||
},
|
||||
/// The user paused syncing; nothing goes up until it resumes. Outranks
|
||||
/// everything else, because it is the reason for everything else.
|
||||
Paused {
|
||||
@@ -1352,6 +1361,7 @@ fn paint_sync_status(ui: &Rc<Ui>, state: SyncState) {
|
||||
let paused = matches!(state, SyncState::Paused { .. });
|
||||
let connected = !matches!(state, SyncState::Disconnected | SyncState::NotResponding);
|
||||
let expired = matches!(state, SyncState::SessionExpired { .. });
|
||||
let outdated = matches!(state, SyncState::Outdated { .. });
|
||||
let (icon, class, title, detail) = match state {
|
||||
SyncState::SessionExpired { queued } => (
|
||||
"dialog-password-symbolic",
|
||||
@@ -1374,6 +1384,20 @@ fn paint_sync_status(ui: &Rc<Ui>, state: SyncState) {
|
||||
"Unlock your keyring, or the next reboot signs you out",
|
||||
)),
|
||||
),
|
||||
SyncState::Outdated { running } => (
|
||||
"dialog-warning-symbolic",
|
||||
Some("warning"),
|
||||
gettext("Restart to finish updating"),
|
||||
Some(if running.is_empty() {
|
||||
gettext("An older version of Proton Drive is still running")
|
||||
} else {
|
||||
// Translators: {version} is a release number such as "3.2.0".
|
||||
gettext_f(
|
||||
"Proton Drive {version} is still running",
|
||||
&[("version", &running)],
|
||||
)
|
||||
}),
|
||||
),
|
||||
SyncState::Paused { until, queued } => (
|
||||
"media-playback-pause-symbolic",
|
||||
Some("warning"),
|
||||
@@ -1445,6 +1469,7 @@ fn paint_sync_status(ui: &Rc<Ui>, state: SyncState) {
|
||||
image.add_css_class(class);
|
||||
}
|
||||
ui.locations.card.sign_in.set_visible(expired);
|
||||
ui.locations.card.restart.set_visible(outdated);
|
||||
ui.status.status_title.set_label(&title);
|
||||
ui.status.status_detail.set_visible(detail.is_some());
|
||||
ui.status
|
||||
|
||||
@@ -22,7 +22,8 @@ use ksni::{MenuItem, Status, ToolTip, Tray};
|
||||
use pdfs_core::auth;
|
||||
use pdfs_core::config::AppDirs;
|
||||
use pdfs_core::control::{
|
||||
Event, Feed, JobItem, Request, Response, TransferDirection, TransferItem, follow, send,
|
||||
Event, Feed, JobItem, Request, Response, TransferDirection, TransferItem, daemon_outdated,
|
||||
follow, send,
|
||||
};
|
||||
use pdfs_core::service;
|
||||
|
||||
@@ -86,6 +87,9 @@ struct DriveState {
|
||||
paused: bool,
|
||||
/// Queued changes that keep failing, for the "N issues" item.
|
||||
failing: u64,
|
||||
/// Whether the daemon is an older release than the tray, left running by
|
||||
/// an upgrade; offers the restart that finishes it.
|
||||
outdated: bool,
|
||||
}
|
||||
|
||||
/// Summarise a work snapshot into one menu line, or empty when idle. A single
|
||||
@@ -166,6 +170,7 @@ fn poll_state(socket: &Path, default_mountpoint: &Path) -> DriveState {
|
||||
sync: String::new(),
|
||||
paused: false,
|
||||
failing: 0,
|
||||
outdated: false,
|
||||
},
|
||||
// No daemon: describe login state instead so the menu is still useful.
|
||||
Err(_) => {
|
||||
@@ -202,6 +207,7 @@ fn poll_state(socket: &Path, default_mountpoint: &Path) -> DriveState {
|
||||
sync: String::new(),
|
||||
paused: false,
|
||||
failing: 0,
|
||||
outdated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,6 +224,7 @@ fn unexpected_reply(default_mountpoint: &Path) -> DriveState {
|
||||
sync: String::new(),
|
||||
paused: false,
|
||||
failing: 0,
|
||||
outdated: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +246,7 @@ fn status_state(
|
||||
paused,
|
||||
session_expired,
|
||||
tokens_unsaved,
|
||||
version,
|
||||
..
|
||||
} = status
|
||||
else {
|
||||
@@ -246,9 +254,10 @@ fn status_state(
|
||||
};
|
||||
let (pinned, online, failing_ops, paused) = (*pinned, *online, *failing_ops, *paused);
|
||||
let queued = i18n::pending_summary(*pending_uploads, *pending_changes);
|
||||
let outdated = daemon_outdated(version);
|
||||
let phase = if *session_expired {
|
||||
Phase::SessionExpired
|
||||
} else if *tokens_unsaved {
|
||||
} else if *tokens_unsaved || outdated {
|
||||
Phase::Attention
|
||||
} else {
|
||||
phase_of(
|
||||
@@ -265,6 +274,9 @@ fn status_state(
|
||||
_ if *session_expired => gettext("Session expired — sign in again"),
|
||||
// Sync goes on meanwhile, but a reboot ends it until the next login.
|
||||
_ if *tokens_unsaved => gettext("Sign-in not saved — unlock your keyring"),
|
||||
// An upgrade replaced the files but not the running service, which
|
||||
// keeps the old release's bugs until it restarts.
|
||||
_ if outdated => gettext("Update not finished — restart Proton Drive"),
|
||||
_ if paused => gettext("Sync paused"),
|
||||
_ if failing_ops > 0 => ngettext_f(
|
||||
"{n} change needs attention",
|
||||
@@ -302,6 +314,7 @@ fn status_state(
|
||||
mountpoint: PathBuf::from(mountpoint),
|
||||
sync: sync_line(items, jobs),
|
||||
failing: failing_ops,
|
||||
outdated,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,6 +458,13 @@ impl Tray for DriveTray {
|
||||
_ => items.push(item(&gettext("Open Proton Drive"), |_| open_manager(&[]))),
|
||||
}
|
||||
|
||||
if self.state.outdated {
|
||||
items.push(item(&gettext("Restart Proton Drive"), |this| {
|
||||
service::restart();
|
||||
this.state.line = gettext("Connecting…");
|
||||
}));
|
||||
}
|
||||
|
||||
if self.state.mounted {
|
||||
items.push(
|
||||
StandardItem {
|
||||
@@ -626,9 +646,14 @@ mod tests {
|
||||
}
|
||||
|
||||
fn status(extra: &str) -> Response {
|
||||
status_from(env!("CARGO_PKG_VERSION"), extra)
|
||||
}
|
||||
|
||||
fn status_from(version: &str, extra: &str) -> Response {
|
||||
serde_json::from_str(&format!(
|
||||
r#"{{"Status":{{"username":"u","mountpoint":"/m","pinned":0,"used":0,"budget":0,
|
||||
"pins":[],"online":false,"pending_uploads":3,"failing_ops":2,"paused":true{extra}}}}}"#
|
||||
"pins":[],"online":false,"pending_uploads":3,"failing_ops":2,"paused":true,
|
||||
"version":"{version}"{extra}}}}}"#
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
@@ -660,6 +685,16 @@ mod tests {
|
||||
assert_eq!(state.line, "Sign-in not saved — unlock your keyring");
|
||||
}
|
||||
|
||||
/// #33: a daemon an upgrade left running on the old release is named,
|
||||
/// above a pause, and the menu offers the restart that finishes it.
|
||||
#[test]
|
||||
fn a_daemon_older_than_the_tray_needs_a_restart() {
|
||||
let state = status_state(&status_from("0.1.0", ""), &[], &[], Path::new("/m"));
|
||||
assert_eq!(state.phase, Phase::Attention);
|
||||
assert_eq!(state.line, "Update not finished — restart Proton Drive");
|
||||
assert!(state.outdated);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_daemon_that_does_not_say_has_a_live_session() {
|
||||
let state = status_state(&status(""), &[], &[], Path::new("/m"));
|
||||
|
||||
Reference in new issue
Block a user