Skip to content
API Documentation
BastionGuard™
ImportDialog.vala
Go to the documentation of this file.
1/*
2 * BastionGuard Backup — fork di linuxmint/bastionguard-backup
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 * GPL-2.0-or-later
5 *
6 * ImportDialog.vala
7 *
8 * Finestra GTK mostrata al primo avvio se viene rilevato BastionGuard Backup.
9 * Permette all'utente di:
10 * - Vedere quanti snapshot BastionGuard Backup esistono e su quale device
11 * - Scegliere se importarli (migrazione) o ignorarli
12 * - Scegliere se rimuovere la config BastionGuard Backup dopo la migrazione
13 */
14
15using GLib;
16using Gtk;
18
19public class ImportDialog : Gtk.Dialog {
20
22 private Gtk.CheckButton remove_config_check;
23 private Gtk.ListBox snapshot_list;
24 private Gtk.Label status_label;
25 private Gtk.Button import_btn;
26 private Gtk.Button skip_btn;
27 private Gtk.Spinner spinner;
28
29 public signal void import_completed(int count);
30
31 // ─────────────────────────────────────────────────────────────────────────
32 // Costruttore — riceve il risultato già pronto da TimeshiftImporter.detect()
33 // ─────────────────────────────────────────────────────────────────────────
34 public ImportDialog(Gtk.Window parent, TimeshiftDetectResult detected) {
35 Object(transient_for: parent,
36 modal: true,
37 title: _("BastionGuard Backup trovato — Importa snapshot"));
38
39 this.detected = detected;
40 set_default_size(620, 480);
41 build_ui();
43 }
44
45 private void build_ui() {
46
47 // ── Header informativo ────────────────────────────────────────────
48 var header_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
49 header_box.set_margin_top(16);
50 header_box.set_margin_start(16);
51 header_box.set_margin_end(16);
52
53 var icon = IconManager.lookup_image("drive-harddisk-symbolic", 32);
54 header_box.append(icon);
55
56 var info_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 4);
57 var title_lbl = new Gtk.Label(
58 "<b>%s</b>".printf(_("BastionGuard Backup ha trovato snapshot BastionGuard Backup")));
59 title_lbl.set_use_markup(true);
60 title_lbl.set_halign(Gtk.Align.START);
61
62 var sub_lbl = new Gtk.Label(
63 _("Puoi importarli e continuare a usarli con BastionGuard Backup."));
64 sub_lbl.set_halign(Gtk.Align.START);
65
66 var device_lbl = new Gtk.Label(
67 " " + _("Device") + ": <tt>%s</tt> (UUID: <tt>%s</tt>)".printf(
69 device_lbl.set_use_markup(true);
70 device_lbl.set_halign(Gtk.Align.START);
71
72 info_box.append(title_lbl);
73 info_box.append(sub_lbl);
74 info_box.append(device_lbl);
75 info_box.hexpand = true;
76 header_box.append(info_box);
77
78 // ── Lista snapshot trovati ────────────────────────────────────────
79 snapshot_list = new Gtk.ListBox();
80 snapshot_list.set_selection_mode(Gtk.SelectionMode.NONE);
81
82 var scroll = new Gtk.ScrolledWindow();
83 scroll.set_min_content_height(220);
84 scroll.set_margin_start(16);
85 scroll.set_margin_end(16);
86 scroll.set_child(snapshot_list);
87
88 var list_frame = new Gtk.Frame(
89 _("%d snapshot trovati").printf(detected.snapshots.size));
90 list_frame.set_margin_start(16);
91 list_frame.set_margin_end(16);
92 list_frame.set_child(scroll);
93
94 // ── Opzione: rimuovi config BastionGuard Backup ─────────────────────────────
95 remove_config_check = new Gtk.CheckButton.with_label(
96 _("Rimuovi la configurazione BastionGuard Backup dopo l'importazione"));
97 remove_config_check.set_margin_start(16);
98 remove_config_check.set_margin_end(16);
99 remove_config_check.set_active(false);
100
101 // ── Spinner + stato ───────────────────────────────────────────────
102 var progress_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
103 progress_box.set_margin_start(16);
104 progress_box.set_margin_end(16);
105
106 spinner = new Gtk.Spinner();
107 status_label = new Gtk.Label("");
108 status_label.set_halign(Gtk.Align.START);
109 progress_box.append(spinner);
110 status_label.hexpand = true;
111 progress_box.append(status_label);
112
113 // ── Pulsanti ──────────────────────────────────────────────────────
114 skip_btn = (Gtk.Button) add_button(_("Ignora"), Gtk.ResponseType.CANCEL);
115
116 import_btn = (Gtk.Button) add_button(
117 _("Importa %d snapshot").printf(detected.snapshots.size),
118 Gtk.ResponseType.ACCEPT);
119 import_btn.get_style_context().add_class("suggested-action");
120
121 // ── Layout ────────────────────────────────────────────────────────
122 var content = get_content_area();
123 content.set_spacing(8);
124 content.append(header_box);
125 list_frame.vexpand = true;
126 content.append(list_frame);
127 content.append(remove_config_check);
128 content.append(progress_box);
129 this.show();
130
131 // ── Segnali ───────────────────────────────────────────────────────
132 response.connect(on_response);
133 }
134
135 private void populate_snapshot_list() {
136 foreach (var snap in detected.snapshots) {
137 var row_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
138 row_box.set_margin_top(6);
139 row_box.set_margin_bottom(6);
140 row_box.set_margin_start(10);
141 row_box.set_margin_end(10);
142
143 // Tag badge
144 var tag_lbl = new Gtk.Label("<tt>%s</tt>".printf(
145 snap.tags.length > 0 ? snap.tags : "?"));
146 tag_lbl.set_use_markup(true);
147 tag_lbl.set_size_request(28, -1);
148
149 // Info principale
150 var info_lbl = new Gtk.Label(
151 "<b>%s</b> <small>%s</small>".printf(
152 snap.name, snap.date));
153 info_lbl.set_use_markup(true);
154 info_lbl.set_halign(Gtk.Align.START);
155 info_lbl.set_hexpand(true);
156
157 // Descrizione (se presente)
158 if (snap.description.length > 0) {
159 var desc_lbl = new Gtk.Label(
160 "<small><i>%s</i></small>".printf(
161 Markup.escape_text(snap.description)));
162 desc_lbl.set_use_markup(true);
163 desc_lbl.set_halign(Gtk.Align.END);
164 row_box.append(tag_lbl);
165 info_lbl.hexpand = true;
166 row_box.append(info_lbl);
167 row_box.append(desc_lbl);
168 } else {
169 row_box.append(tag_lbl);
170 info_lbl.hexpand = true;
171 row_box.append(info_lbl);
172 }
173
174 var row = new Gtk.ListBoxRow();
175 row.set_child(row_box);
176 snapshot_list.append(row);
177 }
178 snapshot_list.show();
179 }
180
181 private void on_response(int response_id) {
182 if (response_id != Gtk.ResponseType.ACCEPT) {
183 destroy();
184 return;
185 }
186
187 // Blocca UI durante la migrazione
188 import_btn.set_sensitive(false);
189 skip_btn.set_sensitive(false);
190 remove_config_check.set_sensitive(false);
191 spinner.start();
192 status_label.set_text(_("Migrazione in corso..."));
193
194 bool remove_cfg = remove_config_check.get_active();
195
196 // Esegui in thread per non bloccare la UI
197 new Thread<void*>("bastionguard-backup-import", () => {
198 var importer = new TimeshiftImporter();
199 bool ok = importer.migrate(detected, remove_cfg, this);
200 int count = detected.snapshots.size;
201
202 Idle.add(() => {
203 spinner.stop();
204 if (ok) {
205 status_label.set_text(
206 _("✅ Importati %d snapshot.").printf(count));
207 import_completed(count);
208 // Chiudi dopo 1.5s
209 Timeout.add(1500, () => { destroy(); return false; });
210 } else {
211 status_label.set_text(_("❌ Migrazione fallita. Controlla i log."));
212 import_btn.set_sensitive(true);
213 skip_btn.set_sensitive(true);
214 }
215 return false;
216 });
217 return null;
218 });
219 }
220
221 // ─────────────────────────────────────────────────────────────────────────
222 // Metodo statico: controlla se mostrare il dialog al primo avvio
223 // Da chiamare in AppGtk.vala → start_application() prima di MainWindow
224 // ─────────────────────────────────────────────────────────────────────────
225 public static void check_and_show_if_needed(Gtk.Window parent) {
226
227 // Non mostrare se abbiamo già fatto l'import
228 string bgbackup_conf = "/etc/bastionguard-backup/bastionguard-backup.json";
229 if (FileUtils.test(bgbackup_conf, FileTest.EXISTS)) {
230 string raw;
231 try {
232 FileUtils.get_contents(bgbackup_conf, out raw);
233 var parser = new Json.Parser();
234 parser.load_from_data(raw);
235 var config = parser.get_root().get_object();
236 // Se già importato in precedenza, non riproporre
237 if (config.has_member("imported_from_timeshift") &&
238 config.get_string_member("imported_from_timeshift") == "true")
239 return;
240 } catch {}
241 }
242
243 // Rileva BastionGuard Backup in background
244 new Thread<void*>("bastionguard-backup-detect", () => {
245 var importer = new TimeshiftImporter();
246 var detected = importer.detect();
247
248 if (detected.found && detected.snapshots.size > 0) {
249 Idle.add(() => {
250 var dialog = new ImportDialog(parent, detected);
251 dialog.import_completed.connect((count) => {
252 GLib.message("BastionGuard Backup: importati %d snapshot".printf(count));
253 });
254 dialog.show();
255 return false;
256 });
257 }
258 return null;
259 });
260 }
261}
#define _(s)
Gee.ArrayList< ImportedSnapshot > snapshots
static Gtk.Image lookup_image(string icon_name, int icon_size, bool symbolic=false, bool use_hardcoded=false)
Gtk.Spinner spinner
Gtk.Button skip_btn
void populate_snapshot_list()
Gtk.Button import_btn
static void check_and_show_if_needed(Gtk.Window parent)
void on_response(int response_id)
TimeshiftDetectResult detected
signal void import_completed(int count)
ImportDialog(Gtk.Window parent, TimeshiftDetectResult detected)
Gtk.Label status_label
Gtk.CheckButton remove_config_check
Gtk.ListBox snapshot_list