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 @@ + + + + + + False + True + dialog + + + False + 32 + 32 + 32 + 32 + vertical + + + False + + + + + + False + False + 0 + + + + + True + False + vertical + 12 + + + True + False + Please insert the backup medium + True + 0 + + + False + True + 0 + + + + + True + False + <device name> + + + + + + False + True + 1 + + + + + True + False + <mount name> + + + + + + False + True + 2 + + + + + True + False + vertical + + + + + + False + True + 3 + + + + + False + True + 1 + + + + + + + True + False + + + Cancel + True + True + True + + + + + + 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(); + } +}