Skip to content
API Documentation
BastionGuard™
BackupDeviceBox.vala
Go to the documentation of this file.
1/*
2 * BackupDeviceBox.vala
3 */
4
5using Gtk;
6using Gee;
7
13using TeeJee.System;
14using TeeJee.Misc;
15
16class BackupDeviceBox : Gtk.Box{
17
18 private Gtk.ListBox device_list;
19 private Gtk.Box infobar_location;
20 private Gtk.Label lbl_infobar_location;
21 private Gtk.Label lbl_common;
22 private Gtk.Window parent_window;
23
24 public BackupDeviceBox (Gtk.Window _parent_window) {
25 log_debug("BackupDeviceBox: BackupDeviceBox()");
26 GLib.Object(orientation: Gtk.Orientation.VERTICAL, spacing: 6);
27 parent_window = _parent_window;
28 margin_top = 12;
29 margin_bottom = 12;
30 margin_start = 12;
31 margin_end = 12;
32
33 var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
34 append(hbox);
35 add_label_header(hbox, _("Select Snapshot Location"), true);
36
37 // Spacer that pushes the toolbar buttons to the right.
38 var spacer = add_label(hbox, "");
39 spacer.hexpand = true;
40
41 // Toolbar buttons live in their own little hbox with NO size
42 // group: each one sizes naturally to its label so the long
43 // "Add Network Share…" doesn't drag the short "Refresh" out.
44 var toolbar = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
45 toolbar.halign = Gtk.Align.END;
46 toolbar.valign = Gtk.Align.CENTER;
47 hbox.append(toolbar);
48
49 // "Add Network Share" — only shown for the rsync mode because BTRFS
50 // snapshots cannot live on a non-BTRFS network share.
51 if (!App.btrfs_mode){
52 var btn_smb = add_button(toolbar, _("Add Network Share…"),
53 _("Mount a Samba/CIFS share (NAS, shared folder) and use it as a snapshot location"),
54 null, null);
55 btn_smb.add_css_class("btn-toolbar");
56 btn_smb.hexpand = false;
57 var lbl = btn_smb.get_child();
58 if (lbl is Gtk.Label){
59 ((Gtk.Label) lbl).wrap = false;
60 ((Gtk.Label) lbl).ellipsize = Pango.EllipsizeMode.NONE;
61 }
62 btn_smb.clicked.connect(() => {
64 });
65 }
66
67 var btn_refresh = add_button(toolbar, _("Refresh"), "", null, null);
68 btn_refresh.add_css_class("btn-toolbar");
69 btn_refresh.hexpand = false;
70 {
71 var lbl = btn_refresh.get_child();
72 if (lbl is Gtk.Label){
73 ((Gtk.Label) lbl).wrap = false;
74 ((Gtk.Label) lbl).ellipsize = Pango.EllipsizeMode.NONE;
75 }
76 }
77 btn_refresh.clicked.connect(()=>{
79 refresh();
80 });
81
84 log_debug("BackupDeviceBox: BackupDeviceBox(): exit");
85 }
86
87 public void refresh(){
90
91 if (App.btrfs_mode){
92 lbl_common.label = "<i>• %s\n• %s\n• %s</i>".printf(
93 _("Devices displayed above have BTRFS file systems."),
94 _("BTRFS snapshots are saved on system partition. Other partitions are not supported."),
95 _("Snapshots are saved to /bastionguard-backup-btrfs on selected partition. Other locations are not supported.")
96 );
97 }
98 else {
99 lbl_common.label = "<i>• %s\n• %s\n• %s\n• %s</i>".printf(
100 _("Devices displayed above have Linux file systems."),
101 _("Devices with Windows file systems are not supported (NTFS, FAT, etc)."),
102 _("Network shares (SMB / NAS / shared folders) can be added with the button above."),
103 _("On a local device snapshots are saved to /bastionguard-backup; on a network share they are saved to a bastionguard-backup folder inside the share.")
104 );
105 }
106 }
107
108 private void init_device_list(){
109 device_list = new Gtk.ListBox();
110 device_list.selection_mode = Gtk.SelectionMode.SINGLE;
111 device_list.activate_on_single_click = true;
112 device_list.add_css_class("boxed-list");
113 device_list.row_selected.connect((row) => {
114 if (row == null){
115 return;
116 }
117 SmbShare? smb = row.get_data<SmbShare>("smb_share");
118 if (smb != null){
121 return;
122 }
123 Device? dev = row.get_data<Device>("device");
124 if (dev != null){
127 }
128 });
129
130 var scrolled = new Gtk.ScrolledWindow();
131 scrolled.hexpand = true;
132 scrolled.vexpand = true;
133 scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
134 scrolled.set_child(device_list);
135 append(scrolled);
136 }
137
138 private void add_device_row(Device dev, string kind){
139 var row = new Gtk.ListBoxRow();
140 row.set_data<Device>("device", dev);
141
142 var outer = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
143 outer.margin_top = 6;
144 outer.margin_bottom = 6;
145 outer.margin_start = 6;
146 outer.margin_end = 6;
147
148 var icon = IconManager.lookup_image((dev.type == "disk") ? IconManager.ICON_HARDDRIVE : "drive-harddisk-symbolic", 16);
149 outer.append(icon);
150
151 var primary = new Gtk.Box(Gtk.Orientation.VERTICAL, 2);
152 primary.hexpand = true;
153 string title = (dev.type == "disk") ? "%s %s".printf(dev.model, dev.vendor).strip() : dev.kname;
154 if (title.length == 0){
155 title = dev.description();
156 }
157 var title_label = new Gtk.Label(title);
158 title_label.xalign = 0.0f;
159 title_label.add_css_class("heading");
160 primary.append(title_label);
161
162 string details = "%s • %s".printf((kind.length > 0 ? kind : dev.fstype), format_file_size(dev.size_bytes, false, "", true, 0));
163 if ((dev.type != "disk") && (dev.free_bytes > 0)){
164 details += " • " + _("Free") + ": " + format_file_size(dev.free_bytes, false, "", true, 0);
165 }
166 if ((dev.partlabel != null) && (dev.partlabel.length > 0)){
167 details += " • " + dev.partlabel;
168 }
169 var subtitle = new Gtk.Label(details);
170 subtitle.xalign = 0.0f;
171 subtitle.wrap = true;
172 primary.append(subtitle);
173 outer.append(primary);
174
175 var status = new Gtk.Label("");
176 status.xalign = 1.0f;
177 status.set_data<string>("uuid", dev.uuid);
178 outer.append(status);
179
180 row.set_child(outer);
181 device_list.append(row);
182 }
183
184 private void update_selected_rows(){
185 Gtk.Widget? child = device_list.get_first_child();
186 while (child != null){
187 var next = child.get_next_sibling();
188 if (child is Gtk.ListBoxRow){
189 var row = (Gtk.ListBoxRow) child;
190 Gtk.Widget? boxw = row.get_child();
191 if (boxw is Gtk.Box){
192 Gtk.Widget? last = ((Gtk.Box) boxw).get_last_child();
193 if (last is Gtk.Label){
194 bool selected = false;
195
196 SmbShare? smb = row.get_data<SmbShare>("smb_share");
197 if (smb != null){
198 // SMB row: selected when repo is path-based and points
199 // at this share's mount point.
200 selected = (App.repo != null)
201 && (App.repo.device == null)
202 && (App.repo.mount_path == smb.mount_point + (smb.subpath.length > 0 ? "/" + smb.subpath : ""));
203 }
204 else {
205 Device? dev = row.get_data<Device>("device");
206 if (dev != null){
207 selected = (App.repo.device != null) && (App.repo.device.uuid == dev.uuid);
208 }
209 }
210
211 ((Gtk.Label) last).label = selected ? "✓" : "";
212 }
213 }
214 }
215 child = next;
216 }
217 }
218
219 private void init_infobar_location(){
220 var infobar = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
221 infobar.visible = false;
222 append(infobar);
223 infobar_location = infobar;
224
225 var content = infobar;
226 var label = add_label(content, "");
227 lbl_infobar_location = label;
228
229 var scrolled = new Gtk.ScrolledWindow();
230 scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
231 scrolled.set_size_request(-1, 100);
232 append(scrolled);
233
234 label = new Gtk.Label("");
235 label.set_use_markup(true);
236 label.xalign = 0.0f;
237 label.wrap = true;
238 label.wrap_mode = Pango.WrapMode.WORD;
239 label.margin_top = 6;
240 label.margin_bottom = 6;
241 label.margin_start = 6;
242 label.margin_end = 6;
243 scrolled.set_child(label);
244 lbl_common = label;
245 }
246
247 private void try_change_device(Device dev){
248 log_debug("try_change_device: %s".printf(dev.device));
249 if (dev.type == "disk"){
250 bool found_child = false;
251 if ((App.btrfs_mode && (dev.fstype == "btrfs")) || (!App.btrfs_mode && dev.has_linux_filesystem())){
253 found_child = true;
254 }
255 if (!found_child){
256 foreach (var child in dev.children){
257 if ((App.btrfs_mode && (child.fstype == "btrfs")) || (!App.btrfs_mode && child.has_linux_filesystem())){
259 found_child = true;
260 break;
261 }
262 }
263 }
264 if (!found_child){
265 string msg = App.btrfs_mode ? _("Selected device does not have BTRFS partition") : _("Selected device does not have Linux partition");
266 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(msg);
267 infobar_location.show();
268 }
269 }
270 else if (dev.has_children()){
272 }
273 else if (!dev.has_children()){
275 }
276 else {
277 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(_("Select a partition on this disk"));
278 infobar_location.show();
279 }
280 }
281
282 private void change_backup_device(Device pi){
283 if ((App.repo.device != null) && (pi.uuid == App.repo.device.uuid)){ return; }
284
286 log_msg("selected device: %s".printf(pi.device));
288
289 if (pi.fstype == "luks"){
292 if (dev.has_children() && dev.children[0].has_linux_filesystem()){
293 App.repo = new SnapshotRepo.from_device(dev.children[0], parent_window, App.btrfs_mode);
295 }
296 }
297
301 }
302
303 private bool check_backup_location(){
304 bool ok = true;
306 string message = escape_html(App.repo.status_message);
307 string details = escape_html(App.repo.status_details);
308 int status_code = App.repo.status_code;
309
310 if (App.live_system()){
311 switch (status_code){
312 case SnapshotLocationStatus.NOT_SELECTED:
313 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
314 infobar_location.show();
315 ok = false;
316 break;
317 case SnapshotLocationStatus.NOT_AVAILABLE:
318 case SnapshotLocationStatus.READ_ONLY_FS:
319 case SnapshotLocationStatus.HARDLINKS_NOT_SUPPORTED:
320 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(message);
321 infobar_location.show();
322 ok = false;
323 break;
324 case SnapshotLocationStatus.NO_BTRFS_SYSTEM:
325 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
326 infobar_location.show();
327 ok = false;
328 break;
329 case SnapshotLocationStatus.NO_SNAPSHOTS_HAS_SPACE:
330 case SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE:
331 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(_("There are no snapshots on this device"));
332 infobar_location.show();
333 break;
334 case SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE:
335 case SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE:
336 infobar_location.hide();
337 break;
338 }
339 }
340 else{
341 switch (status_code){
342 case SnapshotLocationStatus.NOT_SELECTED:
343 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
344 infobar_location.show();
345 ok = false;
346 break;
347 case SnapshotLocationStatus.NOT_AVAILABLE:
348 case SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE:
349 case SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE:
350 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(message.replace("<","&lt;"));
351 infobar_location.show();
352 ok = false;
353 break;
354 case SnapshotLocationStatus.READ_ONLY_FS:
355 case SnapshotLocationStatus.HARDLINKS_NOT_SUPPORTED:
356 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(message);
357 infobar_location.show();
358 ok = false;
359 break;
360 case SnapshotLocationStatus.NO_BTRFS_SYSTEM:
361 lbl_infobar_location.label = "<span weight=\"bold\">%s</span>".printf(details);
362 infobar_location.show();
363 ok = false;
364 break;
365 case 3:
366 case 0:
367 infobar_location.hide();
368 break;
369 }
370 }
371 return ok;
372 }
373
374 private void device_list_refresh(){
376 while (device_list.get_first_child() != null){
377 device_list.remove(device_list.get_first_child());
378 }
379
380 foreach(var disk in App.partitions) {
381 if (disk.type != "disk") { continue; }
382 add_device_row(disk, _("Disk"));
384 }
385
386 // Append currently-mounted SMB/CIFS shares as selectable
387 // destinations (only meaningful in rsync mode).
388 if (!App.btrfs_mode){
389 var smb_mounts = SmbMount.list_active_mounts();
390 foreach (var s in smb_mounts){
391 add_smb_row(s);
392 }
393 }
394
396 }
397
398 private void append_child_volumes(Device parent){
399 foreach(var part in App.partitions) {
400 if (!part.has_linux_filesystem()){ continue; }
401
402 if (App.btrfs_mode){
403 if (part.is_encrypted_partition() && (!part.has_children() || (part.children[0].fstype == "btrfs"))){
404 }
405 else if (part.is_lvm_partition() && (!part.has_children() || (part.children[0].fstype == "btrfs"))){
406 }
407 else if (part.fstype == "btrfs"){
408 }
409 else{
410 continue;
411 }
412 }
413
414 if (part.pkname == parent.kname) {
415 add_device_row(part, part.fstype);
417 }
418 else if ((part.kname == parent.kname) && (part.type == "disk") && part.has_linux_filesystem() && !part.has_children()){
419 var part2 = new Device();
420 part2.copy_fields_from(part);
421 part2.type = "part";
422 part2.pkname = part.device.replace("/dev/","");
423 part2.parent = part;
424 add_device_row(part2, part2.fstype);
425 }
426 }
427 }
428
429 // ─── SMB / NAS support ──────────────────────────────────────────────
430
431 private void add_smb_row(SmbShare share){
432 var row = new Gtk.ListBoxRow();
433 row.set_data<SmbShare>("smb_share", share);
434
435 var outer = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
436 outer.margin_top = 6;
437 outer.margin_bottom = 6;
438 outer.margin_start = 6;
439 outer.margin_end = 6;
440
441 var icon = IconManager.lookup_image("folder-remote-symbolic", 16);
442 if (icon == null){
443 icon = IconManager.lookup_image("network-server-symbolic", 16);
444 }
445 outer.append(icon);
446
447 var primary = new Gtk.Box(Gtk.Orientation.VERTICAL, 2);
448 primary.hexpand = true;
449
450 var title_label = new Gtk.Label(share.display_name);
451 title_label.xalign = 0.0f;
452 title_label.add_css_class("heading");
453 primary.append(title_label);
454
455 // Try to report free space on the mount.
456 string details = _("Network share (SMB / CIFS)");
457 if (share.mount_point.length > 0){
459 if (df.size > 0 && df[0].size_bytes > 0){
460 details += " • " + format_file_size(df[0].size_bytes, false, "", true, 0);
461 if (df[0].free_bytes > 0){
462 details += " • " + _("Free") + ": "
463 + format_file_size(df[0].free_bytes, false, "", true, 0);
464 }
465 }
466 else {
467 details += " • " + share.mount_point;
468 }
469 }
470 var subtitle = new Gtk.Label(details);
471 subtitle.xalign = 0.0f;
472 subtitle.wrap = true;
473 primary.append(subtitle);
474 outer.append(primary);
475
476 var status = new Gtk.Label("");
477 status.xalign = 1.0f;
478 outer.append(status);
479
480 row.set_child(outer);
481 device_list.append(row);
482 }
483
485 string err;
486 if (!SmbMount.is_available(out err)){
487 gtk_messagebox(_("Network shares not available"), err, parent_window, true);
488 return;
489 }
490
492 if (share == null){
493 return; // user cancelled or mount failed (dialog already showed err)
494 }
495
496 // Refresh and switch to the new share.
497 refresh();
500 }
501
502 private void try_change_smb_share(SmbShare share){
503 log_debug("try_change_smb_share: %s".printf(share.unc_path));
504
506
507 // If the user supplied a sub-folder we point the repo at it.
508 string repo_path = share.mount_point;
509 if (share.subpath.length > 0){
510 repo_path = path_combine(repo_path, share.subpath);
511 dir_create(repo_path);
512 }
513
514 App.repo = new SnapshotRepo.from_path(repo_path, parent_window, App.btrfs_mode);
515
518 }
519}
Main App
#define _(s)
SnapshotLocationStatus
Gtk.Window parent_window
void add_device_row(Device dev, string kind)
void try_change_smb_share(SmbShare share)
void append_child_volumes(Device parent)
void change_backup_device(Device pi)
Gtk.Label lbl_infobar_location
Gtk.ListBox device_list
void add_smb_row(SmbShare share)
void try_change_device(Device dev)
BackupDeviceBox(Gtk.Window _parent_window)
static Gee.ArrayList< Device > get_disk_space_using_df(string dev_name_or_mount_point="")
Definition Device.vala:822
string type
Definition Device.vala:64
Device()
Definition Device.vala:94
string model
Definition Device.vala:70
bool has_linux_filesystem()
Definition Device.vala:191
static ? Device find_device_in_list(Gee.ArrayList< Device > list, string _dev_alias)
Definition Device.vala:1177
string uuid
Definition Device.vala:50
uint64 size_bytes
Definition Device.vala:77
uint64 free_bytes
Definition Device.vala:120
string description()
Definition Device.vala:1825
Gee.ArrayList< Device > children
Definition Device.vala:87
string vendor
Definition Device.vala:69
bool has_children()
Definition Device.vala:228
string kname
Definition Device.vala:46
string fstype
Definition Device.vala:65
string device
Definition Device.vala:44
string partlabel
Definition Device.vala:53
const string ICON_HARDDRIVE
static Gtk.Image lookup_image(string icon_name, int icon_size, bool symbolic=false, bool use_hardcoded=false)
SnapshotRepo repo
Definition Main.vala:70
bool btrfs_mode
Definition Main.vala:52
Gee.ArrayList< Device > partitions
Definition Main.vala:59
void update_partitions()
Definition Main.vala:3819
bool live_system()
Definition Main.vala:1058
static bool is_available(out string error_message)
static Gee.ArrayList< SmbShare > list_active_mounts()
static ? SmbShare run_dialog(Gtk.Window? parent, SmbShare? prefill=null)
string subpath
Definition SmbMount.vala:31
string display_name
Definition SmbMount.vala:52
string mount_point
Definition SmbMount.vala:36
string unc_path
Definition SmbMount.vala:42
SnapshotRepo. from_device(Device dev, Gtk.Window? parent_win, bool btrfs_repo)
string status_details
SnapshotRepo. from_path(string path, Gtk.Window? parent_win, bool _btrfs_mode)
string status_message
SnapshotLocationStatus status_code
string path_combine(string path1, string path2)
string format_file_size(uint64 size, bool binary_units=false, string unit="", bool show_units=true, int decimals=1)
bool dir_create(string dir_path, bool show_message=false)
void gtk_set_busy(bool busy, Gtk.Window win)
Gtk.Label add_label(Gtk.Box box, string text, bool bold=false, bool italic=false, bool large=false)
void log_debug(string message)
void log_msg(string message, bool highlight=false)
string escape_html(string html)