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"] }
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"
......
......@@ -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()
......
......@@ -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;
......
<?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 @@
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=5 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
......@@ -175,6 +176,21 @@
<property name="top-attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......@@ -318,6 +334,7 @@
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid">
<property name="width-request">500</property>
<property name="visible">True</property>
......@@ -411,6 +428,15 @@
<property name="top-attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......@@ -746,7 +772,7 @@ This might take a while.</property>
</object>
</child>
<style>
<class name="settings"/>
<class name="configs"/>
</style>
</object>
<packing>
......@@ -769,58 +795,6 @@ This might take a while.</property>
<property name="visible">True</property>
<property name="can-focus">False</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>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
......
......@@ -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);
......
......@@ -16,6 +16,7 @@ mod archives;
#[allow(dead_code)]
mod builder;
mod detail;
mod device_missing;
mod encryption_password;
mod globals;
mod main_pending;
......
......@@ -154,6 +154,8 @@ fn show_archives(archive_list: Result<Vec<borg::ListArchive>, BorgErr>) {
pub fn show() {
let backup = SETTINGS.load().backups.get_active().unwrap().clone();
ui::device_missing::main(backup.clone(), move || {
ui::utils::clear(&main_ui().archive_list());
main_ui().main_stack().set_visible_child_name("archives");
......@@ -164,10 +166,11 @@ pub fn show() {
ui::utils::Async::borg(
"list_archives",
borg::Borg::new(backup),
borg::Borg::new(backup.clone()),
|borg| borg.list(),
show_archives,
);
});
}
fn find_first_populated_dir(dir: &std::path::Path) -> std::path::PathBuf {
......
......@@ -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 {
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")
}
......
......@@ -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: &gtk::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: &gtk::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: &gtk::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: &gtk::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: &gtk::Button) {
settings.backups.get_mut(&backup_id).unwrap().last_run = run_info.clone()
});
ui::write_config();
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<shared::RunInfo>) {
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<shared::RunInfo>) {
}
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();
......
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<F: Fn() + 'static>(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::<bool>();
}));
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<F: Fn()>(
repo_path: &std::path::Path,
new_mount: &gio::Mount,
dialog: Rc<ui::builder::DeviceMissing>,
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();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment