diff --git a/Cargo.toml b/Cargo.toml
index ad68beb27d8b9672feea08cc4644a8486a16fd20..ed555e2a119f9f13d883a8ce5b3883d0d71fd9be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ ctrlc = { version = "3.1", features = ["termination"] }
gettext-rs = { version = "0.4", features = ["gettext-system"] }
humansize = "1.1"
matches = "0.1"
-nix = "0.17"
+nix = "0.18"
secret-service = "1.1"
walkdir = "2.3"
zxcvbn = "2.0"
diff --git a/build-aux/generate_ui_bindings.py b/build-aux/generate_ui_bindings.py
index ff26be2516a6ff751d7be98aa55d5a0f5f52e429..dad426e85d13049a3dba793bfe50b01a1e4604ad 100755
--- a/build-aux/generate_ui_bindings.py
+++ b/build-aux/generate_ui_bindings.py
@@ -5,8 +5,6 @@ import glob
import os.path as path
import os
-os.chdir("data")
-
UI_PATH = "ui/*.ui"
SRC_PATH = "../src/ui/builder.rs"
@@ -15,6 +13,7 @@ SRC_PATH = "../src/ui/builder.rs"
def main():
global UI_PATH, SRC_PATH
+ os.chdir("data")
ui_files = glob.glob(UI_PATH)
ui_files.sort()
diff --git a/data/style.css b/data/style.css
index 951de5d24e8adce0d2eea28c5851e690ad94b726..46e6d91703e4c73fe7ba9fd3a16f5fc133a6452e 100644
--- a/data/style.css
+++ b/data/style.css
@@ -20,6 +20,19 @@ list.settings.custom {
border: none;
}
+.configs {
+ background-color: inherit;
+}
+.configs row:not(:hover) {
+ background-color: @theme_base_color;
+}
+list.configs > row {
+ margin: 5px;
+ padding: 10px 15px;
+ border-radius: 7px;
+ border: @borders 1px solid;
+}
+
levelbar .full {
background-color: #e01b24;
border-color: #c01c27;
@@ -66,4 +79,4 @@ list.settings row.activatable:hover image {
padding: 0;
min-width: 32px;
min-height: 32px;
-}
\ No newline at end of file
+}
diff --git a/data/ui/device_missing.ui b/data/ui/device_missing.ui
new file mode 100644
index 0000000000000000000000000000000000000000..9fd3748b5da0707746612b5241bf7f7373492dc0
--- /dev/null
+++ b/data/ui/device_missing.ui
@@ -0,0 +1,119 @@
+
+
+
+
+
+
diff --git a/data/ui/main.ui b/data/ui/main.ui
index 8643a3361e006e1f78c669429c8432d2b0c5d0b4..8d0ac2a3cf4ec96d6e060605ee58ce58ad9c95ab 100644
--- a/data/ui/main.ui
+++ b/data/ui/main.ui
@@ -33,6 +33,7 @@
+
True
False
@@ -175,6 +176,21 @@
2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
False
@@ -318,6 +334,7 @@
+
500
True
@@ -411,6 +428,15 @@
2
+
+
+
+
+
+
+
+
+
False
@@ -746,7 +772,7 @@ This might take a while.
@@ -769,58 +795,6 @@ This might take a while.
True
False
vertical
-
-
- True
- False
-
-
- False
- 6
- end
-
-
-
-
-
- False
- False
- 0
-
-
-
-
- False
- 16
-
-
- True
- False
- The device containing this backup is not connected.
-
-
- False
- True
- 0
-
-
-
-
- False
- False
- 0
-
-
-
-
-
-
-
- False
- True
- 0
-
-
True
diff --git a/src/shared.rs b/src/shared.rs
index 382338ecb3ee93482712091a01be42fec27bd634..c3122ca3d312edd8ec003a338259303daebe86dd 100644
--- a/src/shared.rs
+++ b/src/shared.rs
@@ -92,6 +92,12 @@ impl BackupConfig {
let repo_file = gio::File::new_for_path(&repo);
let none: Option<&gio::Cancellable> = None;
let mount = repo_file.find_enclosing_mount(none).ok();
+ debug!(
+ "Mount found: {:?} {:?} {:?}",
+ &repo,
+ &mount,
+ repo_file.is_native()
+ );
let drive = mount.as_ref().and_then(gio::Mount::get_drive);
let volume_uuid = mount.as_ref().and_then(get_mount_uuid);
diff --git a/src/ui.rs b/src/ui.rs
index 9b4313a6509170044149efbc346dbc2f03dc7022..8e509ccc1235fefc4a2dadeb8377872084a667f0 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -16,6 +16,7 @@ mod archives;
#[allow(dead_code)]
mod builder;
mod detail;
+mod device_missing;
mod encryption_password;
mod globals;
mod main_pending;
diff --git a/src/ui/archives.rs b/src/ui/archives.rs
index 8f4988f4084c2f3725e922551d72d416c35150d0..83b18bf6598692d2f115c9e40810932c88e4db50 100644
--- a/src/ui/archives.rs
+++ b/src/ui/archives.rs
@@ -154,20 +154,23 @@ fn show_archives(archive_list: Result, BorgErr>) {
pub fn show() {
let backup = SETTINGS.load().backups.get_active().unwrap().clone();
- ui::utils::clear(&main_ui().archive_list());
- main_ui().main_stack().set_visible_child_name("archives");
-
- let label = gtk::Spinner::new();
- main_ui().archive_list().set_placeholder(Some(&label));
- label.start();
- label.show();
-
- ui::utils::Async::borg(
- "list_archives",
- borg::Borg::new(backup),
- |borg| borg.list(),
- show_archives,
- );
+
+ ui::device_missing::main(backup.clone(), move || {
+ ui::utils::clear(&main_ui().archive_list());
+ main_ui().main_stack().set_visible_child_name("archives");
+
+ let label = gtk::Spinner::new();
+ main_ui().archive_list().set_placeholder(Some(&label));
+ label.start();
+ label.show();
+
+ ui::utils::Async::borg(
+ "list_archives",
+ borg::Borg::new(backup.clone()),
+ |borg| borg.list(),
+ show_archives,
+ );
+ });
}
fn find_first_populated_dir(dir: &std::path::Path) -> std::path::PathBuf {
diff --git a/src/ui/builder.rs b/src/ui/builder.rs
index 363eed955ed164ebae12fffe59511daa93043bc6..33033c08e6ce0d6755edb0d0c2179b6898bd2dd4 100644
--- a/src/ui/builder.rs
+++ b/src/ui/builder.rs
@@ -19,6 +19,50 @@ impl About {
}
}
+pub struct DeviceMissing {
+ builder: gtk::Builder,
+}
+
+impl DeviceMissing {
+ pub fn new() -> Self {
+ Self {
+ builder: gtk::Builder::from_string(include_str!(concat!(
+ data_dir!(),
+ "/ui/device_missing.ui"
+ ))),
+ }
+ }
+
+ fn get>(&self, id: &str) -> T {
+ gtk::prelude::BuilderExtManual::get_object(&self.builder, id).unwrap_or_else(|| {
+ panic!(
+ "Object with id '{}' not found in 'ui/device_missing.ui'",
+ id
+ )
+ })
+ }
+
+ pub fn cancel(&self) -> gtk::Button {
+ self.get("cancel")
+ }
+
+ pub fn device(&self) -> gtk::Label {
+ self.get("device")
+ }
+
+ pub fn icon(&self) -> gtk::Box {
+ self.get("icon")
+ }
+
+ pub fn mount(&self) -> gtk::Label {
+ self.get("mount")
+ }
+
+ pub fn window(&self) -> gtk::Dialog {
+ self.get("window")
+ }
+}
+
pub struct EncryptionPassword {
builder: gtk::Builder,
}
@@ -155,10 +199,6 @@ impl Main {
self.get("delete_backup_conf")
}
- pub fn detail_device_not_connected(&self) -> gtk::InfoBar {
- self.get("detail_device_not_connected")
- }
-
pub fn detail_menu(&self) -> gtk::MenuButton {
self.get("detail_menu")
}
diff --git a/src/ui/detail.rs b/src/ui/detail.rs
index 7c4efff2623c13d53d96496454c6cd420a70274b..4ab9203070ac850a18dfbe582663485d113510ab 100644
--- a/src/ui/detail.rs
+++ b/src/ui/detail.rs
@@ -12,7 +12,7 @@ use crate::ui::prelude::*;
use crate::ui::utils::{self, WidgetEnh};
pub fn init() {
- main_ui().backup_run().connect_clicked(on_backup_run);
+ main_ui().backup_run().connect_clicked(|_| on_backup_run());
main_ui()
.target_listbox()
@@ -115,9 +115,7 @@ fn stop_backup_create() {
}
}
-fn on_backup_run(button: >k::Button) {
- button.set_sensitive(false);
-
+fn on_backup_run() {
let config = SETTINGS.load().backups.get_active().unwrap().clone();
let backup_id = ACTIVE_BACKUP_ID.get().unwrap();
@@ -131,7 +129,7 @@ fn on_backup_run(button: >k::Button) {
if unmount {
trace!("User decided to unmount repo.");
if !ui::utils::dialog_catch_err(
- borg::Borg::new(config).umount(),
+ borg::Borg::new(config.clone()).umount(),
gettext("Failed to unmount repository."),
) {
ACTIVE_MOUNTS.update(|mounts| {
@@ -144,8 +142,11 @@ fn on_backup_run(button: >k::Button) {
}
}
- let backups = &SETTINGS.load().backups;
- let backup = backups.get(&backup_id).unwrap().clone();
+ ui::device_missing::main(config.clone(), move || run_backup(config.clone()));
+}
+
+pub fn run_backup(backup: shared::BackupConfig) {
+ let backup_id = backup.id.clone();
let communication: borg::Communication = Default::default();
@@ -162,6 +163,7 @@ fn on_backup_run(button: >k::Button) {
BACKUP_COMMUNICATION.update(|c| {
c.remove(&backup_id);
});
+ let user_aborted = matches!(result, Err(shared::BorgErr::UserAborted));
let result_string_err = result.map_err(|err| format!("{}", err));
let run_info = Some(shared::RunInfo::new(result_string_err.clone()));
refresh_offline(&run_info);
@@ -169,7 +171,10 @@ fn on_backup_run(button: >k::Button) {
settings.backups.get_mut(&backup_id).unwrap().last_run = run_info.clone()
});
ui::write_config();
- ui::utils::dialog_catch_errb(&result_string_err, gettext("Backup failed"));
+
+ if !user_aborted {
+ ui::utils::dialog_catch_errb(&result_string_err, gettext("Backup failed"));
+ }
},
);
}
@@ -222,25 +227,6 @@ pub fn refresh() {
}
let backup = SETTINGS.load().backups.get_active().unwrap().clone();
- // messages
- main_ui().detail_device_not_connected().hide();
- //let repo = backup.repo.clone();
- if let shared::BackupRepo::Local { path, .. } = backup.repo.clone() {
- ui::utils::async_react(
- "check_repo_available",
- move || ui::new_backup::is_backup_repo(&path),
- |available| {
- if !available {
- gtk::timeout_add(250, || {
- main_ui().detail_device_not_connected().show();
- // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=710888
- main_ui().detail_device_not_connected().queue_resize();
- Continue(false)
- });
- }
- },
- );
- }
// backup target ui
let repo_ui = main_ui().target_listbox();
@@ -393,7 +379,6 @@ pub fn refresh_statusx() {
pub fn refresh_offline(run_info_opt: &Option) {
let stack = main_ui().stack();
main_ui().stop_backup_create().hide();
- main_ui().backup_run().set_sensitive(true);
if let Some(ref run_info) = run_info_opt {
main_ui().status_button().show();
@@ -442,7 +427,6 @@ pub fn refresh_offline(run_info_opt: &Option) {
}
pub fn refresh_status(communication: &borg::Communication) -> Continue {
- main_ui().backup_run().set_sensitive(false);
main_ui().stop_backup_create().show();
let stack = main_ui().stack();
diff --git a/src/ui/device_missing.rs b/src/ui/device_missing.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6ac4669f081f23418fac5f356de191e3e12d17d2
--- /dev/null
+++ b/src/ui/device_missing.rs
@@ -0,0 +1,65 @@
+use gio;
+use gio::prelude::*;
+use gtk::prelude::*;
+use std::rc::Rc;
+
+use crate::shared;
+use crate::ui;
+use crate::ui::globals::*;
+
+pub fn main(config: shared::BackupConfig, f: F) {
+ if let shared::BackupRepo::Local {
+ device,
+ label,
+ icon,
+ removable: true,
+ path,
+ ..
+ } = config.repo
+ {
+ if ui::new_backup::is_backup_repo(&path) {
+ f();
+ return;
+ }
+
+ let dialog = Rc::new(ui::builder::DeviceMissing::new());
+
+ let volume_monitor = gio::VolumeMonitor::get();
+
+ volume_monitor.connect_mount_added(
+ enclose!((dialog, path) move |_, new_mount| mount_added(&path, new_mount, dialog.clone(), &f)),
+ );
+
+ dialog.cancel().connect_clicked(enclose!((dialog) move |_| {
+ dialog.window().close();
+ // this line triggers a move of volume_monior
+ volume_monitor.is::();
+ }));
+
+ dialog.window().set_transient_for(Some(&main_ui().window()));
+ dialog.device().set_label(&device.unwrap_or_default());
+ dialog.mount().set_label(&label.unwrap_or_default());
+ if let Some(g_icon) = icon.and_then(|x| gio::Icon::new_for_string(&x).ok()) {
+ let img = gtk::Image::from_gicon(&g_icon, gtk::IconSize::Dialog);
+ img.set_pixel_size(128);
+ dialog.icon().add(&img);
+ }
+ dialog.window().show_all();
+ } else {
+ f();
+ }
+}
+
+fn mount_added(
+ repo_path: &std::path::Path,
+ new_mount: &gio::Mount,
+ dialog: Rc,
+ f: &F,
+) {
+ debug!("Mount added");
+ if repo_path.starts_with(new_mount.get_root().unwrap().get_path().unwrap()) {
+ debug!("Looks like the correct mount");
+ dialog.window().close();
+ f();
+ }
+}