Skip to content
API Documentation
BastionGuard™
SetupWizardWindow.vala
Go to the documentation of this file.
1/*
2 * SetupWizardWindow.vala
3 *
4 * Copyright 2012-2018 Tony George <teejeetech@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 */
17
18using Gtk;
19using Gee;
20using Json;
21
22using TeeJee.Logging;
27using TeeJee.System;
28using TeeJee.Misc;
29
30class SetupWizardWindow : Gtk.Window {
31
32 // Layout
33 public signal void request_close();
34
35 private Gtk.Box root_box;
36 private Gtk.Box sidebar;
37 private Gtk.Box content_box;
38 private Gtk.Box header_box;
39 private Gtk.Box body_box;
40 private Gtk.Box actions_box;
41 private Gtk.Stack stack;
42
43 // Sidebar
44 private Gtk.Button btn_nav_import;
45 private Gtk.Button btn_nav_backend;
46 private Gtk.Button btn_nav_estimate;
47 private Gtk.Button btn_nav_location;
48 private Gtk.Button btn_nav_schedule;
49 private Gtk.Button btn_nav_users;
50 private Gtk.Button btn_nav_finish;
51 private Gtk.Button? current_nav_btn = null;
52
53 // Header titolo pagina
54 private Gtk.Label lbl_page_title;
55
56 // Pagina import Timeshift
57 private Gtk.Box timeshift_page_box;
58 private Gtk.Label lbl_timeshift_title;
59 private Gtk.Label lbl_timeshift_desc;
60 private Gtk.Label lbl_timeshift_found;
61 private Gtk.CheckButton radio_import_yes;
62 private Gtk.CheckButton radio_import_no;
63 private Gtk.Label lbl_timeshift_path;
64 private Gtk.Label lbl_timeshift_note;
65
66 private bool timeshift_available = false;
67 private bool timeshift_import_done = false;
68 private int timeshift_backup_count = 0;
69 private string timeshift_config_path = "";
70
71 // tabs
79
80 // actions
81 private Gtk.Button btn_prev;
82 private Gtk.Button btn_next;
83 private Gtk.Button btn_cancel;
84 private Gtk.Button btn_close;
85
86 private bool schedule_accepted = false;
87 private bool embedded_mode = false;
88
89 private uint tmr_init;
90 private int def_width = 980;
91 private int def_height = 620;
92
93 // Icon reload tracking for theme changes
94 private Gee.ArrayList<Gtk.Image> themed_img_list = new Gee.ArrayList<Gtk.Image>();
95 private Gee.ArrayList<string> themed_img_name = new Gee.ArrayList<string>();
96 private Gee.ArrayList<int> themed_img_size = new Gee.ArrayList<int>();
97
98 public SetupWizardWindow(bool embedded = false) {
99 embedded_mode = embedded;
100
101 log_debug("SetupWizardWindow: SetupWizardWindow()");
102
103 this.title = _("Setup Wizard");
104 if (!embedded_mode) {
105 this.modal = true;
106 }
107 this.set_icon_name("bastionguard-backup");
108 if (!embedded_mode)
109 this.close_request.connect(on_close_request);
110
111 this.set_default_size(def_width, def_height);
112 this.set_default_size(def_width, def_height);
113 this.set_titlebar(HeaderFactory.create_main_headerbar(this, 220));
114
116 App.schedule_boot = false;
117 App.schedule_hourly = false;
118 App.schedule_daily = true;
119 log_debug("Setting schedule_daily for first run");
120 App.schedule_weekly = false;
121 App.schedule_monthly = false;
122 }
123
124 build_ui();
125 build_pages();
127
128 if (!embedded_mode)
129 show();
130
131 tmr_init = Timeout.add(100, init_delayed);
132
133 log_debug("SetupWizardWindow: SetupWizardWindow(): exit");
134 }
135
136 // ─────────────────────────────────────────────────────────────────────
137 // UI
138 // ─────────────────────────────────────────────────────────────────────
139 private void build_ui() {
140
141 root_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
142 root_box.hexpand = true;
143 root_box.vexpand = true;
144 root_box.halign = Gtk.Align.FILL;
145 root_box.valign = Gtk.Align.FILL;
146 set_child(root_box);
147
150
151 root_box.append(sidebar);
152 root_box.append(content_box);
153 }
154
155 private void build_sidebar() {
156
157 sidebar = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
158 sidebar.set_size_request(220, -1);
159 sidebar.hexpand = false;
160 sidebar.vexpand = true;
161 sidebar.halign = Gtk.Align.FILL;
162 sidebar.valign = Gtk.Align.FILL;
163 sidebar.get_style_context().add_class("sidebar");
164
165 var sidebar_header = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
166 sidebar_header.get_style_context().add_class("sidebar-header");
167 sidebar.append(sidebar_header);
168
169 Gtk.Widget logo_widget;
170 try {
171 logo_widget = new Gtk.Image.from_pixbuf(IconManager.lookup("bastionguard-backup", 48));
172 }
173 catch (Error e) {
174 logo_widget = themed_icon("emblem-default-symbolic", 48);
175 }
176
177 var title = new Gtk.Label("BastionGuard™\nWizard");
178 title.xalign = 0.0f;
179 title.yalign = 0.5f;
180 title.wrap = true;
181 title.max_width_chars = 14;
182 title.get_style_context().add_class("sidebar-title");
183
184 sidebar_header.append(logo_widget);
185 sidebar_header.append(title);
186
187 sidebar.append(new Gtk.Box(Gtk.Orientation.VERTICAL, 0));
188
189 btn_nav_import = make_nav_button("document-import-symbolic", _("Import"));
190 btn_nav_backend = make_nav_button("document-properties-symbolic", _("Backend"));
191 btn_nav_estimate = make_nav_button("system-search-symbolic", _("Estimate"));
192 btn_nav_location = make_nav_button("drive-harddisk-symbolic", _("Location"));
193 btn_nav_schedule = make_nav_button("x-office-calendar-symbolic", _("Schedule"));
194 btn_nav_users = make_nav_button("system-users-symbolic", _("Users"));
195 btn_nav_finish = make_nav_button("emblem-ok-symbolic", _("Finish"));
196
197 btn_nav_import.sensitive = false;
198 btn_nav_backend.sensitive = false;
199 btn_nav_estimate.sensitive = false;
200 btn_nav_location.sensitive = false;
201 btn_nav_schedule.sensitive = false;
202 btn_nav_users.sensitive = false;
203 btn_nav_finish.sensitive = false;
204
205 var spacer = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
206 spacer.hexpand = true;
207 spacer.vexpand = true;
208 spacer.halign = Gtk.Align.FILL;
209 spacer.valign = Gtk.Align.FILL;
210 sidebar.append(spacer);
211 }
212
213 private Gtk.Button make_nav_button(string icon_name, string label_text) {
214
215 var btn = new Gtk.Button();
216 btn.get_style_context().add_class("sidebar-button");
217 btn.hexpand = true;
218 btn.margin_start = 8;
219 btn.margin_end = 8;
220 btn.margin_top = 2;
221 btn.margin_bottom = 2;
222
223 var row = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 10);
224 row.margin_start = 8;
225 row.margin_end = 8;
226 row.margin_top = 8;
227 row.margin_bottom = 8;
228
229 var img = themed_icon(icon_name, 18);
230 var lbl = new Gtk.Label(label_text);
231 lbl.xalign = 0.0f;
232
233 row.append(img);
234 row.append(lbl);
235
236 btn.set_child(row);
237 sidebar.append(btn);
238
239 return btn;
240 }
241
242 private void build_content() {
243
244 content_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
245 content_box.get_style_context().add_class("content");
246 content_box.hexpand = true;
247 content_box.vexpand = true;
248 content_box.halign = Gtk.Align.FILL;
249 content_box.valign = Gtk.Align.FILL;
250
251 header_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
252 header_box.margin_start = 20;
253 header_box.margin_end = 20;
254 header_box.margin_top = 20;
255 header_box.margin_bottom = 10;
256
257 lbl_page_title = new Gtk.Label(_("Setup Wizard"));
258 lbl_page_title.xalign = 0.0f;
259 lbl_page_title.get_style_context().add_class("title");
261
262 body_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
263 body_box.margin_start = 14;
264 body_box.margin_end = 14;
265 body_box.margin_bottom = 12;
266 body_box.hexpand = true;
267 body_box.vexpand = true;
268 body_box.halign = Gtk.Align.FILL;
269 body_box.valign = Gtk.Align.FILL;
270
271 stack = new Gtk.Stack();
272 stack.transition_type = Gtk.StackTransitionType.CROSSFADE;
273 stack.transition_duration = 140;
274 stack.hexpand = true;
275 stack.vexpand = true;
276 stack.halign = Gtk.Align.FILL;
277 stack.valign = Gtk.Align.FILL;
278
279 body_box.append(stack);
280
281 content_box.append(header_box);
282 content_box.append(body_box);
283 }
284
285 private void build_pages() {
286
288
290 backend_box.margin_start = 12;
291 backend_box.margin_end = 12;
292 backend_box.margin_top = 12;
293 backend_box.margin_bottom = 12;
294
295 estimate_box = new EstimateBox(this);
296 estimate_box.margin_start = 12;
297 estimate_box.margin_end = 12;
298 estimate_box.margin_top = 12;
299 estimate_box.margin_bottom = 12;
300
302 backup_dev_box.margin_start = 12;
303 backup_dev_box.margin_end = 12;
304 backup_dev_box.margin_top = 12;
305 backup_dev_box.margin_bottom = 12;
306
307 schedule_box = new ScheduleBox(this);
308 schedule_box.margin_start = 12;
309 schedule_box.margin_end = 12;
310 schedule_box.margin_top = 12;
311 schedule_box.margin_bottom = 12;
312
313 exclude_box = new ExcludeBox(this);
314 users_box = new UsersBox(this, exclude_box, false);
315 users_box.margin_start = 12;
316 users_box.margin_end = 12;
317 users_box.margin_top = 12;
318 users_box.margin_bottom = 12;
319
320 finish_box = new FinishBox(this, false);
321 finish_box.margin_start = 12;
322 finish_box.margin_end = 12;
323 finish_box.margin_top = 12;
324 finish_box.margin_bottom = 12;
325
326 var page_import = wrap_page(timeshift_page_box);
327 var page_backend = wrap_page(backend_box);
328 var page_estimate = wrap_page(estimate_box);
329 var page_location = wrap_page(backup_dev_box);
330 var page_schedule = wrap_page(schedule_box);
331 var page_users = wrap_page(users_box);
332 var page_finish = wrap_page(finish_box);
333
334 stack.add_named(page_import, "import");
335 stack.add_named(page_backend, "backend");
336 stack.add_named(page_estimate, "estimate");
337 stack.add_named(page_location, "location");
338 stack.add_named(page_schedule, "schedule");
339 stack.add_named(page_users, "users");
340 stack.add_named(page_finish, "finish");
341 }
342
343 private void build_timeshift_page() {
344
345 timeshift_page_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
346 timeshift_page_box.margin_start = 18;
347 timeshift_page_box.margin_end = 18;
348 timeshift_page_box.margin_top = 18;
349 timeshift_page_box.margin_bottom = 18;
350 timeshift_page_box.hexpand = true;
351 timeshift_page_box.vexpand = true;
352
353 lbl_timeshift_title = new Gtk.Label(_("Do you want to import from Timeshift?"));
354 lbl_timeshift_title.xalign = 0.0f;
355 lbl_timeshift_title.get_style_context().add_class("h1");
356
357 lbl_timeshift_desc = new Gtk.Label(
358 _("If a Timeshift configuration is present on this system, BastionGuard can import compatible settings and detect existing backups without modifying or deleting the original data.")
359 );
360 lbl_timeshift_desc.xalign = 0.0f;
361 lbl_timeshift_desc.wrap = true;
362 lbl_timeshift_desc.max_width_chars = 80;
363
364 lbl_timeshift_found = new Gtk.Label("");
365 lbl_timeshift_found.xalign = 0.0f;
366 lbl_timeshift_found.wrap = true;
367
368 lbl_timeshift_path = new Gtk.Label("");
369 lbl_timeshift_path.xalign = 0.0f;
370 lbl_timeshift_path.wrap = true;
371 lbl_timeshift_path.selectable = true;
372
373 radio_import_yes = new Gtk.CheckButton.with_label(_("Yes, import from Timeshift"));
374 radio_import_no = new Gtk.CheckButton.with_label(_("No, configure manually"));
376
377 lbl_timeshift_note = new Gtk.Label(
378 _("Import copies only compatible settings into BastionGuard. Original Timeshift files are not modified.")
379 );
380 lbl_timeshift_note.xalign = 0.0f;
381 lbl_timeshift_note.wrap = true;
382 lbl_timeshift_note.max_width_chars = 80;
383
386 timeshift_page_box.append(new Gtk.Separator(Gtk.Orientation.HORIZONTAL));
389 timeshift_page_box.append(new Gtk.Box(Gtk.Orientation.VERTICAL, 0));
392 timeshift_page_box.append(new Gtk.Box(Gtk.Orientation.VERTICAL, 0));
394 }
395
396 private Gtk.Widget wrap_page(Gtk.Widget child) {
397 var card = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
398 card.get_style_context().add_class("card");
399 card.hexpand = true;
400 card.vexpand = true;
401 card.margin_start = 6;
402 card.margin_end = 6;
403 card.margin_top = 6;
404 card.margin_bottom = 6;
405 card.append(child);
406 return card;
407 }
408
409 // ─────────────────────────────────────────────────────────────────────
410 // TIMESHiFT DETECTION
411 // ─────────────────────────────────────────────────────────────────────
412 private void detect_timeshift_state() {
413
414 timeshift_available = false;
417
418 string[] candidate_paths = {
419 "/etc/timeshift/timeshift.json",
420 "/etc/timeshift.json"
421 };
422
423 foreach (string path in candidate_paths) {
424 if (file_exists(path)) {
425 timeshift_available = true;
427 break;
428 }
429 }
430
433 return;
434 }
435
437 if (timeshift_backup_count > 0) {
438 timeshift_available = true;
439 }
440 }
441
443
444 int count = 0;
445 var all_devices = Device.get_block_devices_using_lsblk();
446
447 foreach (var dev in all_devices) {
448
449 if (dev.uuid.length == 0) continue;
450 if (dev.fstype != "ext4" && dev.fstype != "ext3"
451 && dev.fstype != "btrfs" && dev.fstype != "xfs") continue;
452
453 var tmp_repo = new SnapshotRepo.from_uuid(dev.uuid, this, dev.fstype == "btrfs");
454 if (tmp_repo == null) continue;
455 if (tmp_repo.mount_path.length == 0) continue;
456
457 string[] snap_dirs = {
458 path_combine(tmp_repo.mount_path, "timeshift/snapshots"),
459 path_combine(tmp_repo.mount_path, "timeshift-btrfs/snapshots"),
460 path_combine(tmp_repo.mount_path, "bastionguard-backup/snapshots"),
461 path_combine(tmp_repo.mount_path, "bastionguard-backup-btrfs/snapshots")
462 };
463
464 foreach (var snap_dir in snap_dirs) {
465 if (!dir_exists(snap_dir)) continue;
466
467 try {
468 var dir = File.new_for_path(snap_dir);
469 var enumerator = dir.enumerate_children("standard::name,standard::type", 0);
470 var info = enumerator.next_file();
471
472 while (info != null) {
473 if (info.get_file_type() == FileType.DIRECTORY
474 && info.get_name() != ".sync") {
475 count++;
476 }
477 info = enumerator.next_file();
478 }
479 }
480 catch {}
481
482 if (count > 0) {
483 if (App.backup_uuid.length == 0)
484 App.backup_uuid = dev.uuid;
485 break;
486 }
487 }
488
489 if (count > 0) break;
490 }
491
492 return count;
493 }
494
495 private int probe_timeshift_snapshot_count(string config_path) {
496
497 try {
498 var parser = new Json.Parser();
499 parser.load_from_file(config_path);
500
501 Json.Node? node = parser.get_root();
502 Json.Object config = node?.get_object() ?? new Json.Object();
503
504 string backup_uuid = json_get_string(config, "backup_device_uuid", "");
505 string parent_uuid = json_get_string(config, "parent_device_uuid", "");
506 bool tmp_btrfs_mode = json_get_bool(config, "btrfs_mode", false);
507
508 SnapshotRepo tmp_repo = null;
509
510 if (backup_uuid.length > 0) {
511 tmp_repo = new SnapshotRepo.from_uuid(backup_uuid, this, tmp_btrfs_mode);
512 }
513 else if (parent_uuid.length > 0) {
514 tmp_repo = new SnapshotRepo.from_uuid(parent_uuid, this, tmp_btrfs_mode);
515 }
516
517 if (tmp_repo == null) {
518 return 0;
519 }
520
521 tmp_repo.load_snapshots();
522 return tmp_repo.snapshots.size;
523 }
524 catch (Error e) {
525 log_error("probe_timeshift_snapshot_count(): " + e.message);
526 return 0;
527 }
528 }
529
530 private void refresh_timeshift_page() {
531
532 if (!timeshift_available) {
533 lbl_timeshift_found.label = _("No Timeshift configuration or backup was detected.");
534 lbl_timeshift_path.label = "";
535 radio_import_yes.sensitive = false;
536 radio_import_no.active = true;
537 return;
538 }
539
540 if (timeshift_config_path.length > 0) {
541 lbl_timeshift_path.label = _("Configuration found") + ": " + timeshift_config_path;
542 }
543 else if (App.backup_uuid.length > 0) {
544 lbl_timeshift_path.label = _("Backups detected on device UUID") + ": " + App.backup_uuid;
545 }
546 else {
547 lbl_timeshift_path.label = _("Timeshift backups detected by scanning available devices.");
548 }
549
550 if (timeshift_config_path.length > 0) {
551 if (timeshift_backup_count == 0) {
552 lbl_timeshift_found.label = _("Timeshift configuration found, but no importable backup was detected.");
553 }
554 else if (timeshift_backup_count == 1) {
555 lbl_timeshift_found.label = _("Timeshift configuration found. 1 backup was detected.");
556 }
557 else {
558 lbl_timeshift_found.label = _("Timeshift configuration found. %d backups were detected.").printf(timeshift_backup_count);
559 }
560 }
561 else {
562 if (timeshift_backup_count == 1) {
563 lbl_timeshift_found.label = _("1 Timeshift backup was detected, but no Timeshift configuration file was found.");
564 }
565 else {
566 lbl_timeshift_found.label = _("%d Timeshift backups were detected, but no Timeshift configuration file was found.").printf(timeshift_backup_count);
567 }
568 }
569
570 radio_import_yes.sensitive = true;
571
573 radio_import_yes.active = true;
574 }
575 }
576
577 private bool should_import_timeshift() {
578 return timeshift_available && radio_import_yes.active;
579 }
580
581 private bool run_timeshift_import() {
582
584 return true;
585 }
586
588 return true;
589 }
590
591 if (timeshift_config_path.length == 0) {
593
595 if (App.repo != null) {
597 }
598
599 gtk_messagebox(
600 _("Backup repository detected"),
601 _("Existing Timeshift backups were detected and will be used, but no Timeshift configuration file was found to import settings from."),
602 this,
603 false
604 );
605
606 return true;
607 }
608
610 gtk_messagebox(
611 _("Import failed"),
612 _("The Timeshift configuration could not be imported. BastionGuard may already have its own configuration, or the Timeshift configuration file may be invalid."),
613 this,
614 true
615 );
616 return false;
617 }
618
620
622 if (App.repo != null) {
624 }
625
626 gtk_messagebox(
627 _("Import completed"),
628 _("Timeshift settings were imported successfully into BastionGuard."),
629 this,
630 false
631 );
632
633 return true;
634 }
635
636 // ─────────────────────────────────────────────────────────────────────
637 // ACTIONS
638 // ─────────────────────────────────────────────────────────────────────
639 private void create_actions() {
640
641 actions_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
642 actions_box.margin_start = 20;
643 actions_box.margin_end = 20;
644 actions_box.margin_bottom = 20;
645 actions_box.halign = Gtk.Align.END;
646
647 btn_prev = new Gtk.Button.with_label(_("Previous"));
648 btn_next = new Gtk.Button.with_label(_("Next"));
649 btn_close = new Gtk.Button.with_label(_("Finish"));
650 btn_cancel = new Gtk.Button.with_label(_("Cancel"));
651 btn_prev.set_size_request(110, -1);
652 btn_next.set_size_request(110, -1);
653 btn_close.set_size_request(110, -1);
654 btn_cancel.set_size_request(110, -1);
655
656 // CSS styling for nav buttons
657 btn_prev.get_style_context().add_class("btn-back");
658 btn_next.get_style_context().add_class("btn-next");
659 btn_close.get_style_context().add_class("btn-finish");
660 btn_close.get_style_context().add_class("suggested-action");
661 btn_cancel.get_style_context().add_class("btn-cancel");
662
663 btn_prev.clicked.connect(() => { go_prev(); });
664 btn_next.clicked.connect(() => { go_next(); });
665 btn_close.clicked.connect(() => {
666 save_changes();
668 else this.destroy();
669 });
670 btn_cancel.clicked.connect(() => {
671 if (App.task != null) {
672 App.task.stop(AppStatus.CANCELLED);
673 }
675 else this.destroy();
676 });
677
678 actions_box.append(btn_cancel);
679 actions_box.append(btn_prev);
680 actions_box.append(btn_next);
681 actions_box.append(btn_close);
682
683 content_box.append(actions_box);
684
686 }
687
688 private void action_buttons_set_initial_visibility(bool visible) {
689 btn_prev.visible = visible;
690 btn_next.visible = visible;
691 btn_close.visible = visible;
692 btn_cancel.visible = visible;
693 }
694
695 // ─────────────────────────────────────────────────────────────────────
696 // INIT
697 // ─────────────────────────────────────────────────────────────────────
698 private bool init_delayed() {
699
700 if (tmr_init > 0) {
701 Source.remove(tmr_init);
702 tmr_init = 0;
703 }
704
707
708 this.set_default_size(def_width, def_height);
709 go_first();
710
711 return false;
712 }
713
714 private bool on_close_request() {
715
717 App.schedule_boot = false;
718 App.schedule_hourly = false;
719 App.schedule_daily = false;
720 App.schedule_weekly = false;
721 App.schedule_monthly = false;
722 }
723
724 save_changes();
725 return false;
726 }
727
728 private void save_changes() {
730 App.first_run = false;
731 }
732
733 public Gtk.Widget detach_root_widget() {
734 set_child(null);
735 return root_box;
736 }
737
738 // ─────────────────────────────────────────────────────────────────────
739 // NAVIGATION
740 // ─────────────────────────────────────────────────────────────────────
741 private void go_first() {
742 set_page(Tabs.IMPORT_TIMESHIFT);
744 }
745
746 private void go_prev() {
747
748 switch (get_current_tab()) {
749 case Tabs.IMPORT_TIMESHIFT:
750 break;
751 case Tabs.SNAPSHOT_BACKEND:
752 set_page(Tabs.IMPORT_TIMESHIFT);
753 break;
754 case Tabs.ESTIMATE:
755 set_page(Tabs.SNAPSHOT_BACKEND);
756 break;
757 case Tabs.BACKUP_DEVICE:
758 if (App.btrfs_mode) {
759 set_page(Tabs.SNAPSHOT_BACKEND);
760 }
761 else {
762 set_page(Tabs.ESTIMATE);
763 }
764 break;
765 case Tabs.SCHEDULE:
766 set_page(Tabs.BACKUP_DEVICE);
767 break;
768 case Tabs.USERS:
769 set_page(Tabs.SCHEDULE);
770 break;
771 case Tabs.FINISH:
772 set_page(Tabs.USERS);
773 break;
774 }
775
777 }
778
779 private void go_next() {
780
781 if (!validate_current_tab()) {
782 return;
783 }
784
785 switch (get_current_tab()) {
786
787 case Tabs.IMPORT_TIMESHIFT:
788 if (!run_timeshift_import()) {
789 return;
790 }
791 set_page(Tabs.SNAPSHOT_BACKEND);
792 break;
793
794 case Tabs.SNAPSHOT_BACKEND:
795 if (App.btrfs_mode) {
796 set_page(Tabs.BACKUP_DEVICE);
797 }
798 else {
799 set_page(Tabs.ESTIMATE);
800 }
801 break;
802
803 case Tabs.ESTIMATE:
804 set_page(Tabs.BACKUP_DEVICE);
805 break;
806
807 case Tabs.BACKUP_DEVICE:
808 if (App.live_system()) {
809 destroy();
810 return;
811 }
812 else {
813 set_page(Tabs.SCHEDULE);
814 }
815 break;
816
817 case Tabs.SCHEDULE:
818 set_page(Tabs.USERS);
819 schedule_accepted = true;
820 break;
821
822 case Tabs.USERS:
823 set_page(Tabs.FINISH);
824 break;
825
826 case Tabs.FINISH:
827 break;
828 }
829
831 }
832
833 private void initialize_tab() {
834
835 switch (get_current_tab()) {
836
837 case Tabs.IMPORT_TIMESHIFT:
838 btn_prev.show();
839 btn_next.show();
840 btn_close.show();
841 btn_cancel.hide();
842
843 btn_prev.sensitive = false;
844 btn_next.sensitive = true;
845 btn_close.sensitive = true;
846
848 break;
849
850 case Tabs.SNAPSHOT_BACKEND:
851 btn_prev.show();
852 btn_next.show();
853 btn_close.show();
854 btn_cancel.hide();
855
856 btn_prev.sensitive = true;
857 btn_next.sensitive = true;
858 btn_close.sensitive = true;
859
861 break;
862
863 case Tabs.ESTIMATE:
864 btn_prev.show();
865 btn_next.show();
866 btn_close.show();
867 btn_cancel.hide();
868
869 btn_prev.sensitive = false;
870 btn_next.sensitive = false;
871 btn_close.sensitive = false;
872
873 if (App.btrfs_mode) {
874 go_next();
875 return;
876 }
877 else {
879 go_next();
880 return;
881 }
882
883 case Tabs.BACKUP_DEVICE:
884 btn_prev.show();
885 btn_next.show();
886 btn_close.show();
887 btn_cancel.hide();
888
889 btn_prev.sensitive = true;
890 btn_next.sensitive = true;
891 btn_close.sensitive = true;
892
894 break;
895
896 case Tabs.SCHEDULE:
897 btn_prev.show();
898 btn_next.show();
899 btn_close.show();
900 btn_cancel.hide();
901
902 btn_prev.sensitive = true;
903 btn_next.sensitive = true;
904 btn_close.sensitive = true;
905
907 break;
908
909 case Tabs.USERS:
910 btn_prev.show();
911 btn_next.show();
912 btn_close.show();
913 btn_cancel.hide();
914
915 btn_prev.sensitive = true;
916 btn_next.sensitive = true;
917 btn_close.sensitive = true;
918
920 break;
921
922 case Tabs.FINISH:
923 btn_prev.show();
924 btn_next.show();
925 btn_close.show();
926 btn_cancel.hide();
927
928 btn_prev.sensitive = false;
929 btn_next.sensitive = false;
930 btn_close.sensitive = true;
931
933 break;
934 }
935
938 }
939
940 private bool validate_current_tab() {
941
942 if (get_current_tab() == Tabs.IMPORT_TIMESHIFT) {
943 return true;
944 }
945 else if (get_current_tab() == Tabs.SNAPSHOT_BACKEND) {
946 return true;
947 }
948 else if (get_current_tab() == Tabs.BACKUP_DEVICE) {
949 if (!App.repo.available() || !App.repo.has_space()) {
950 gtk_messagebox(
953 this,
954 true
955 );
956 return false;
957 }
958 }
959
960 return true;
961 }
962
963 // ─────────────────────────────────────────────────────────────────────
964 // PAGE STATE
965 // ─────────────────────────────────────────────────────────────────────
966 private Tabs current_tab = Tabs.IMPORT_TIMESHIFT;
967
968 private void set_page(Tabs tab) {
969 current_tab = tab;
970
971 switch (tab) {
972 case Tabs.IMPORT_TIMESHIFT:
973 stack.set_visible_child_name("import");
974 break;
975 case Tabs.SNAPSHOT_BACKEND:
976 stack.set_visible_child_name("backend");
977 break;
978 case Tabs.ESTIMATE:
979 stack.set_visible_child_name("estimate");
980 break;
981 case Tabs.BACKUP_DEVICE:
982 stack.set_visible_child_name("location");
983 break;
984 case Tabs.SCHEDULE:
985 stack.set_visible_child_name("schedule");
986 break;
987 case Tabs.USERS:
988 stack.set_visible_child_name("users");
989 break;
990 case Tabs.FINISH:
991 stack.set_visible_child_name("finish");
992 break;
993 }
994 }
995
997 return current_tab;
998 }
999
1000 private void update_sidebar_state() {
1001
1003
1004 switch (current_tab) {
1005 case Tabs.IMPORT_TIMESHIFT:
1007 break;
1008 case Tabs.SNAPSHOT_BACKEND:
1010 break;
1011 case Tabs.ESTIMATE:
1013 break;
1014 case Tabs.BACKUP_DEVICE:
1016 break;
1017 case Tabs.SCHEDULE:
1019 break;
1020 case Tabs.USERS:
1022 break;
1023 case Tabs.FINISH:
1025 break;
1026 }
1027 }
1028
1029 private void update_page_title() {
1030 switch (current_tab) {
1031 case Tabs.IMPORT_TIMESHIFT:
1032 lbl_page_title.label = _("Import from Timeshift");
1033 break;
1034 case Tabs.SNAPSHOT_BACKEND:
1035 lbl_page_title.label = _("Backend");
1036 break;
1037 case Tabs.ESTIMATE:
1038 lbl_page_title.label = _("Estimate");
1039 break;
1040 case Tabs.BACKUP_DEVICE:
1041 lbl_page_title.label = _("Location");
1042 break;
1043 case Tabs.SCHEDULE:
1044 lbl_page_title.label = _("Schedule");
1045 break;
1046 case Tabs.USERS:
1047 lbl_page_title.label = _("Users");
1048 break;
1049 case Tabs.FINISH:
1050 lbl_page_title.label = _("Finish");
1051 break;
1052 }
1053 }
1054
1056 btn_nav_import.get_style_context().remove_class("active-sidebar-button");
1057 btn_nav_backend.get_style_context().remove_class("active-sidebar-button");
1058 btn_nav_estimate.get_style_context().remove_class("active-sidebar-button");
1059 btn_nav_location.get_style_context().remove_class("active-sidebar-button");
1060 btn_nav_schedule.get_style_context().remove_class("active-sidebar-button");
1061 btn_nav_users.get_style_context().remove_class("active-sidebar-button");
1062 btn_nav_finish.get_style_context().remove_class("active-sidebar-button");
1063 }
1064
1065 private void set_sidebar_active(Gtk.Button btn) {
1066 btn.get_style_context().add_class("active-sidebar-button");
1067 current_nav_btn = btn;
1068 }
1069
1070 public enum Tabs {
1071 IMPORT_TIMESHIFT = 0,
1072 SNAPSHOT_BACKEND = 1,
1073 ESTIMATE = 2,
1074 BACKUP_DEVICE = 3,
1075 SCHEDULE = 4,
1076 USERS = 5,
1077 FINISH = 6
1078 }
1079
1080 // ── Icon theme support ──────────────────────────────────────────────
1081 private Gtk.Image themed_icon(string icon_name, int size) {
1082 var img = IconManager.lookup_image(icon_name, size);
1083 themed_img_list.add(img);
1084 themed_img_name.add(icon_name);
1085 themed_img_size.add(size);
1086 return img;
1087 }
1088
1089 public void reload_themed_icons() {
1090 for (int i = 0; i < themed_img_list.size; i++) {
1091 var img = themed_img_list[i];
1092 var name = themed_img_name[i];
1093 var sz = themed_img_size[i];
1094 var pix = IconManager.lookup(name, sz);
1095 if (pix != null) {
1096 img.set_from_pixbuf(pix);
1097 } else {
1098 img.set_from_icon_name(name);
1099 }
1100 }
1101 }
1102
1103
1104}
Main App
AppStatus
#define _(s)
void stop(AppStatus status_to_update=AppStatus.CANCELLED)
BackupDeviceBox(Gtk.Window _parent_window)
static Gee.ArrayList< Device > get_block_devices_using_lsblk(string dev_name="")
Definition Device.vala:420
void estimate_system_size()
EstimateBox(Gtk.Window _parent_window)
ExcludeBox(Gtk.Window _parent_window)
FinishBox(Gtk.Window _parent_window, bool _show_notes)
void refresh()
static Gtk.HeaderBar create_main_headerbar(Gtk.Window win, int left_width=215)
static Gdk.? Pixbuf lookup(string icon_name, int icon_size, bool symbolic=false, bool use_hardcoded=false, int scale=1)
static Gtk.Image lookup_image(string icon_name, int icon_size, bool symbolic=false, bool use_hardcoded=false)
bool first_run
Definition Main.vala:47
void initialize_repo()
Definition Main.vala:3727
SnapshotRepo repo
Definition Main.vala:70
RsyncTask task
Definition Main.vala:160
bool btrfs_mode
Definition Main.vala:52
bool import_timeshift_config_if_available()
Definition Main.vala:3421
string backup_uuid
Definition Main.vala:49
void cron_job_update()
Definition Main.vala:4463
bool live_system()
Definition Main.vala:1058
ScheduleBox(Gtk.Window _parent_window)
void update_statusbar()
BackupDeviceBox backup_dev_box
Gee.ArrayList< string > themed_img_name
Gtk.? Button current_nav_btn
void set_sidebar_active(Gtk.Button btn)
SetupWizardWindow(bool embedded=false)
Gtk.CheckButton radio_import_yes
Gee.ArrayList< Gtk.Image > themed_img_list
Gtk.Widget detach_root_widget()
Gtk.Image themed_icon(string icon_name, int size)
int probe_timeshift_snapshots_from_mounts()
void action_buttons_set_initial_visibility(bool visible)
Gtk.CheckButton radio_import_no
SnapshotBackendBox backend_box
Gtk.Button make_nav_button(string icon_name, string label_text)
Gtk.Widget wrap_page(Gtk.Widget child)
Gee.ArrayList< int > themed_img_size
signal void request_close()
int probe_timeshift_snapshot_count(string config_path)
SnapshotBackendBox(Gtk.Window _parent_window)
Gee.ArrayList< Snapshot?> snapshots
bool load_snapshots()
string status_details
bool has_space(uint64 needed=0)
string status_message
SnapshotRepo. from_uuid(string uuid, Gtk.Window? parent_win, bool btrfs_repo)
UsersBox(Gtk.Window _parent_window, ExcludeBox _exclude_box, bool _restore_mode)
Definition UsersBox.vala:26
void refresh()
bool file_exists(string file_path)
string path_combine(string path1, string path2)
bool dir_exists(string dir_path)
string json_get_string(Json.Object jobj, string member, string def_value)
bool json_get_bool(Json.Object jobj, string member, bool def_value)
void log_debug(string message)
void log_error(string message, bool highlight=false, bool is_warning=false)