Commit 2ac8055e authored by Sophie Herold's avatar Sophie Herold

Merge branch 'device_missing' into 'main'

Add "connect device" dialog for backups

See merge request sophie-h/pika-backup!6
parents 7aa6be93 c0b22e37
Pipeline #204716 canceled with stage
in 5 minutes and 11 seconds
...@@ -13,7 +13,7 @@ ctrlc = { version = "3.1", features = ["termination"] } ...@@ -13,7 +13,7 @@ ctrlc = { version = "3.1", features = ["termination"] }
gettext-rs = { version = "0.4", features = ["gettext-system"] } gettext-rs = { version = "0.4", features = ["gettext-system"] }
humansize = "1.1" humansize = "1.1"
matches = "0.1" matches = "0.1"
nix = "0.17" nix = "0.18"
secret-service = "1.1" secret-service = "1.1"
walkdir = "2.3" walkdir = "2.3"
zxcvbn = "2.0" zxcvbn = "2.0"
......
...@@ -5,8 +5,6 @@ import glob ...@@ -5,8 +5,6 @@ import glob
import os.path as path import os.path as path
import os import os
os.chdir("data")
UI_PATH = "ui/*.ui" UI_PATH = "ui/*.ui"
SRC_PATH = "../src/ui/builder.rs" SRC_PATH = "../src/ui/builder.rs"
...@@ -15,6 +13,7 @@ SRC_PATH = "../src/ui/builder.rs" ...@@ -15,6 +13,7 @@ SRC_PATH = "../src/ui/builder.rs"
def main(): def main():
global UI_PATH, SRC_PATH global UI_PATH, SRC_PATH
os.chdir("data")
ui_files = glob.glob(UI_PATH) ui_files = glob.glob(UI_PATH)
ui_files.sort() ui_files.sort()
......
...@@ -20,6 +20,19 @@ list.settings.custom { ...@@ -20,6 +20,19 @@ list.settings.custom {
border: none; 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 { levelbar .full {
background-color: #e01b24; background-color: #e01b24;
border-color: #c01c27; border-color: #c01c27;
...@@ -66,4 +79,4 @@ list.settings row.activatable:hover image { ...@@ -66,4 +79,4 @@ list.settings row.activatable:hover image {
padding: 0; padding: 0;
min-width: 32px; min-width: 32px;
min-height: 32px; min-height: 32px;
} }
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.37.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkDialog" id="window">
<property name="can-focus">False</property>
<property name="modal">True</property>
<property name="type-hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="margin-start">32</property>
<property name="margin-end">32</property>
<property name="margin-top">32</property>
<property name="margin-bottom">32</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Please insert the backup medium</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="device">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;device name&gt;</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="mount">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;mount name&gt;</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButton" id="cancel">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
</child>
</object>
</child>
</object>
</interface>
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<!-- n-columns=3 n-rows=5 -->
<object class="GtkGrid"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
...@@ -175,6 +176,21 @@ ...@@ -175,6 +176,21 @@
<property name="top-attach">2</property> <property name="top-attach">2</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
...@@ -318,6 +334,7 @@ ...@@ -318,6 +334,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid"> <object class="GtkGrid">
<property name="width-request">500</property> <property name="width-request">500</property>
<property name="visible">True</property> <property name="visible">True</property>
...@@ -411,6 +428,15 @@ ...@@ -411,6 +428,15 @@
<property name="top-attach">2</property> <property name="top-attach">2</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
...@@ -746,7 +772,7 @@ This might take a while.</property> ...@@ -746,7 +772,7 @@ This might take a while.</property>
</object> </object>
</child> </child>
<style> <style>
<class name="settings"/> <class name="configs"/>
</style> </style>
</object> </object>
<packing> <packing>
...@@ -769,58 +795,6 @@ This might take a while.</property> ...@@ -769,58 +795,6 @@ This might take a while.</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child>
<object class="GtkInfoBar" id="detail_device_not_connected">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">The device containing this backup is not connected.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
......
...@@ -92,6 +92,12 @@ impl BackupConfig { ...@@ -92,6 +92,12 @@ impl BackupConfig {
let repo_file = gio::File::new_for_path(&repo); let repo_file = gio::File::new_for_path(&repo);
let none: Option<&gio::Cancellable> = None; let none: Option<&gio::Cancellable> = None;
let mount = repo_file.find_enclosing_mount(none).ok(); 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 drive = mount.as_ref().and_then(gio::Mount::get_drive);
let volume_uuid = mount.as_ref().and_then(get_mount_uuid); let volume_uuid = mount.as_ref().and_then(get_mount_uuid);
......
...@@ -16,6 +16,7 @@ mod archives; ...@@ -16,6 +16,7 @@ mod archives;
#[allow(dead_code)] #[allow(dead_code)]
mod builder; mod builder;
mod detail; mod detail;
mod device_missing;
mod encryption_password; mod encryption_password;
mod globals; mod globals;
mod main_pending; mod main_pending;
......
...@@ -154,20 +154,23 @@ fn show_archives(archive_list: Result<Vec<borg::ListArchive>, BorgErr>) { ...@@ -154,20 +154,23 @@ fn show_archives(archive_list: Result<Vec<borg::ListArchive>, BorgErr>) {
pub fn show() { pub fn show() {
let backup = SETTINGS.load().backups.get_active().unwrap().clone(); 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"); ui::device_missing::main(backup.clone(), move || {
ui::utils::clear(&main_ui().archive_list());
let label = gtk::Spinner::new(); main_ui().main_stack().set_visible_child_name("archives");
main_ui().archive_list().set_placeholder(Some(&label));
label.start(); let label = gtk::Spinner::new();
label.show(); main_ui().archive_list().set_placeholder(Some(&label));
label.start();
ui::utils::Async::borg( label.show();
"list_archives",
borg::Borg::new(backup), ui::utils::Async::borg(
|borg| borg.list(), "list_archives",
show_archives, borg::Borg::new(backup.clone()),
); |borg| borg.list(),
show_archives,
);
});
} }
fn find_first_populated_dir(dir: &std::path::Path) -> std::path::PathBuf { fn find_first_populated_dir(dir: &std::path::Path) -> std::path::PathBuf {
......
...@@ -19,6 +19,50 @@ impl About { ...@@ -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<T: glib::IsA<glib::object::Object>>(&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 { pub struct EncryptionPassword {
builder: gtk::Builder, builder: gtk::Builder,
} }
...@@ -155,10 +199,6 @@ impl Main { ...@@ -155,10 +199,6 @@ impl Main {
self.get("delete_backup_conf") 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 { pub fn detail_menu(&self) -> gtk::MenuButton {
self.get("detail_menu") self.get("detail_menu")
} }
......
...@@ -12,7 +12,7 @@ use crate::ui::prelude::*; ...@@ -12,7 +12,7 @@ use crate::ui::prelude::*;
use crate::ui::utils::{self, WidgetEnh}; use crate::ui::utils::{self, WidgetEnh};
pub fn init() { pub fn init() {
main_ui().backup_run().connect_clicked(on_backup_run); main_ui().backup_run().connect_clicked(|_| on_backup_run());
main_ui() main_ui()
.target_listbox() .target_listbox()
...@@ -115,9 +115,7 @@ fn stop_backup_create() { ...@@ -115,9 +115,7 @@ fn stop_backup_create() {
} }
} }
fn on_backup_run(button: &gtk::Button) { fn on_backup_run() {
button.set_sensitive(false);
let config = SETTINGS.load().backups.get_active().unwrap().clone(); let config = SETTINGS.load().backups.get_active().unwrap().clone();
let backup_id = ACTIVE_BACKUP_ID.get().unwrap(); let backup_id = ACTIVE_BACKUP_ID.get().unwrap();
...@@ -131,7 +129,7 @@ fn on_backup_run(button: &gtk::Button) { ...@@ -131,7 +129,7 @@ fn on_backup_run(button: &gtk::Button) {
if unmount { if unmount {
trace!("User decided to unmount repo."); trace!("User decided to unmount repo.");
if !ui::utils::dialog_catch_err( if !ui::utils::dialog_catch_err(
borg::Borg::new(config).umount(), borg::Borg::new(config.clone()).umount(),
gettext("Failed to unmount repository."), gettext("Failed to unmount repository."),
) { ) {
ACTIVE_MOUNTS.update(|mounts| { ACTIVE_MOUNTS.update(|mounts| {
...@@ -144,8 +142,11 @@ fn on_backup_run(button: &gtk::Button) { ...@@ -144,8 +142,11 @@ fn on_backup_run(button: &gtk::Button) {
} }
} }
let backups = &SETTINGS.load().backups; ui::device_missing::main(config.clone(), move || run_backup(config.clone()));
let backup = backups.get(&backup_id).unwrap().clone(); }
pub fn run_backup(backup: shared::BackupConfig) {
let backup_id = backup.id.clone();
let communication: borg::Communication = Default::default(); let communication: borg::Communication = Default::default();
...@@ -162,6 +163,7 @@ fn on_backup_run(button: &gtk::Button) { ...@@ -162,6 +163,7 @@ fn on_backup_run(button: &gtk::Button) {
BACKUP_COMMUNICATION.update(|c| { BACKUP_COMMUNICATION.update(|c| {
c.remove(&backup_id); c.remove(&backup_id);
}); });
let user_aborted = matches!(result, Err(shared::BorgErr::UserAborted));
let result_string_err = result.map_err(|err| format!("{}", err)); let result_string_err = result.map_err(|err| format!("{}", err));
let run_info = Some(shared::RunInfo::new(result_string_err.clone())); let run_info = Some(shared::RunInfo::new(result_string_err.clone()));
refresh_offline(&run_info); refresh_offline(&run_info);
...@@ -169,7 +171,10 @@ fn on_backup_run(button: &gtk::Button) { ...@@ -169,7 +171,10 @@ fn on_backup_run(button: &gtk::Button) {
settings.backups.get_mut(&backup_id).unwrap().last_run = run_info.clone() settings.backups.get_mut(&backup_id).unwrap().last_run = run_info.clone()
}); });
ui::write_config(); 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() { ...@@ -222,25 +227,6 @@ pub fn refresh() {
} }
let backup = SETTINGS.load().backups.get_active().unwrap().clone(); 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 // backup target ui
let repo_ui = main_ui().target_listbox(); let repo_ui = main_ui().target_listbox();
...@@ -393,7 +379,6 @@ pub fn refresh_statusx() { ...@@ -393,7 +379,6 @@ pub fn refresh_statusx() {
pub fn refresh_offline(run_info_opt: &Option<shared::RunInfo>) { pub fn refresh_offline(run_info_opt: &Option<shared::RunInfo>) {
let stack = main_ui().stack(); let stack = main_ui().stack();
main_ui().stop_backup_create().hide(); main_ui().stop_backup_create().hide();
main_ui().backup_run().set_sensitive(true);
if let Some(ref run_info) = run_info_opt { if let Some(ref run_info) = run_info_opt {
main_ui().status_button().show(); main_ui().status_button().show();
...@@ -442,7 +427,6 @@ pub fn refresh_offline(run_info_opt: &Option<shared::RunInfo>) { ...@@ -442,7 +427,6 @@ pub fn refresh_offline(run_info_opt: &Option<shared::RunInfo>) {
} }
pub fn refresh_status(communication: &borg::Communication) -> Continue { pub fn refresh_status(communication: &borg::Communication) -> Continue {
main_ui().backup_run().set_sensitive(false);
main_ui().stop_backup_create()