4
* Copyright 2013 Tony George <teejee2008@gmail.com>
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.
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.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
28
using TeeJee.FileSystem;
30
using TeeJee.JsonHelper;
31
using TeeJee.ProcessHelper;
32
using TeeJee.GtkHelper;
36
public class RestoreWindow : Gtk.Dialog{
37
private Box vbox_main;
38
private Box hbox_action;
39
private Notebook notebook;
42
private Label lbl_header_partitions;
43
private RadioButton radio_sys;
44
private RadioButton radio_other;
45
private TreeView tv_partitions;
46
private ScrolledWindow sw_partitions;
47
private TreeViewColumn col_device_target;
48
private TreeViewColumn col_mount;
49
private TreeViewColumn col_fs;
50
private TreeViewColumn col_size;
51
private TreeViewColumn col_dist;
52
private CellRendererCombo cell_mount;
55
private Label lbl_header_bootloader;
56
private ComboBox cmb_boot_device;
57
private CheckButton chk_skip_grub_install;
60
private Label lbl_app;
62
private Label lbl_app_message;
63
private TreeView tv_app;
64
private ScrolledWindow sw_app;
65
private TreeViewColumn col_app;
68
private Label lbl_exclude;
69
private Box vbox_exclude;
70
private LinkButton lnk_default_list;
71
private TreeView tv_exclude;
72
private ScrolledWindow sw_exclude;
73
private TreeViewColumn col_exclude;
74
private Toolbar toolbar_exclude;
75
private ToolButton btn_remove;
76
private ToolButton btn_warning;
77
private ToolButton btn_reset_exclude_list;
79
private MenuToolButton btn_exclude;
80
private Gtk.Menu menu_exclude;
81
private ImageMenuItem menu_exclude_add_file;
82
private ImageMenuItem menu_exclude_add_folder;
83
private ImageMenuItem menu_exclude_add_folder_contents;
85
private MenuToolButton btn_include;
86
private Gtk.Menu menu_include;
87
private ImageMenuItem menu_include_add_file;
88
private ImageMenuItem menu_include_add_folder;
90
private Gee.ArrayList<string> temp_exclude_list;
93
private Button btn_cancel;
94
private Button btn_restore;
96
private Device selected_target = null;
98
public RestoreWindow () {
99
this.title = _("Restore");
100
this.window_position = WindowPosition.CENTER_ON_PARENT;
101
this.set_destroy_with_parent (true);
102
this.set_modal (true);
103
this.set_default_size (550, 500);
104
this.skip_taskbar_hint = true;
105
this.icon = get_app_icon(16);
108
vbox_main = get_content_area ();
111
notebook = new Notebook ();
113
vbox_main.pack_start (notebook, true, true, 0);
115
//target device tab -------------------------------------------------
118
lbl_exclude = new Label (_("Target"));
121
Box vbox_target = new Box (Orientation.VERTICAL, 6);
122
vbox_target.margin = 6;
123
notebook.append_page (vbox_target, lbl_exclude);
126
Box hbox_device = new Box (Orientation.HORIZONTAL, 6);
127
//hbox_device.margin = 6;
128
vbox_target.add(hbox_device);
130
//lbl_header_partitions
131
lbl_header_partitions = new Gtk.Label((App.mirror_system ? _("Device for Cloning System") : _("Device for Restoring Snapshot")) + ":");
132
lbl_header_partitions.xalign = (float) 0.0;
133
lbl_header_partitions.set_use_markup(true);
134
hbox_device.add(lbl_header_partitions);
136
radio_sys = new RadioButton(null);
137
hbox_device.add(radio_sys);
138
radio_sys.label = "Current System";
140
radio_other = new RadioButton.from_widget(radio_sys);
141
hbox_device.add(radio_other);
142
radio_other.label = "Other Device";
144
if (App.live_system() || App.mirror_system){
145
radio_other.active = true;
146
radio_sys.sensitive = false;
149
radio_sys.sensitive = true;
150
radio_sys.active = true;
153
radio_sys.toggled.connect(() => {
154
sw_partitions.sensitive = radio_other.active;
156
refresh_tv_partitions();
158
if (radio_sys.active){
159
App.restore_target = App.root_device;
165
//tv_partitions_select_target();
166
cmb_boot_device_select_default();
169
init_device_list(vbox_target);
171
//bootloader options -------------------------------------------
173
//lbl_header_bootloader
174
lbl_header_bootloader = new Gtk.Label(_("Device for Bootloader Installation") + ":");
175
lbl_header_bootloader.set_use_markup(true);
176
lbl_header_bootloader.xalign = (float) 0.0;
177
vbox_target.add(lbl_header_bootloader);
180
Box hbox_grub = new Box (Orientation.HORIZONTAL, 6);
181
hbox_grub.margin_right = 6;
182
//hbox_grub.margin_bottom = 6;
183
vbox_target.add (hbox_grub);
186
cmb_boot_device = new ComboBox ();
187
cmb_boot_device.hexpand = true;
188
hbox_grub.add(cmb_boot_device);
190
CellRendererText cell_dev_margin = new CellRendererText ();
191
cell_dev_margin.text = "";
192
cmb_boot_device.pack_start (cell_dev_margin, false);
194
CellRendererPixbuf cell_dev_icon = new CellRendererPixbuf ();
195
cell_dev_icon.stock_id = "gtk-harddisk";
196
cmb_boot_device.pack_start (cell_dev_icon, false);
198
CellRendererText cell_device_grub = new CellRendererText();
199
cmb_boot_device.pack_start(cell_device_grub, false );
200
cmb_boot_device.set_cell_data_func (cell_device_grub, cell_device_grub_render);
202
string tt = "<b>" + _("** Advanced Users **") + "</b>\n\n"+ _("Skips bootloader (re)installation on target device.\nFiles in /boot directory on target partition will remain untouched.\n\nIf you are restoring a system that was bootable previously then it should boot successfully.\nOtherwise the system may fail to boot.");
204
//chk_skip_grub_install
205
chk_skip_grub_install = new CheckButton.with_label(_("Skip bootloader installation (not recommended)"));
206
chk_skip_grub_install.active = false;
207
chk_skip_grub_install.set_tooltip_markup(tt);
208
vbox_target.add (chk_skip_grub_install);
210
chk_skip_grub_install.toggled.connect(()=>{
211
cmb_boot_device.sensitive = !chk_skip_grub_install.active;
214
//Exclude Apps tab ---------------------------------------------
217
lbl_app = new Label (_("Exclude"));
220
vbox_app = new Box(Gtk.Orientation.VERTICAL, 6);
222
notebook.append_page (vbox_app, lbl_app);
225
string msg = _("Select the applications for which current settings should be kept.") + "\n";
226
msg += _("For all other applications, settings will be restored from selected snapshot.");
227
lbl_app_message = new Label (msg);
228
lbl_app_message.xalign = (float) 0.0;
229
vbox_app.add(lbl_app_message);
231
//tv_app-----------------------------------------------
234
tv_app = new TreeView();
235
tv_app.get_selection().mode = SelectionMode.MULTIPLE;
236
tv_app.headers_visible = false;
237
tv_app.set_rules_hint (true);
240
sw_app = new ScrolledWindow(null, null);
241
sw_app.set_shadow_type (ShadowType.ETCHED_IN);
243
sw_app.expand = true;
244
vbox_app.add(sw_app);
247
col_app = new TreeViewColumn();
248
col_app.title = _("Application");
249
col_app.expand = true;
250
tv_app.append_column(col_app);
252
CellRendererText cell_app_margin = new CellRendererText ();
253
cell_app_margin.text = "";
254
col_app.pack_start (cell_app_margin, false);
256
CellRendererToggle cell_app_enabled = new CellRendererToggle ();
257
cell_app_enabled.radio = false;
258
cell_app_enabled.activatable = true;
259
col_app.pack_start (cell_app_enabled, false);
260
col_app.set_cell_data_func (cell_app_enabled, cell_app_enabled_render);
262
cell_app_enabled.toggled.connect (cell_app_enabled_toggled);
264
CellRendererText cell_app_text = new CellRendererText ();
265
col_app.pack_start (cell_app_text, false);
266
col_app.set_cell_data_func (cell_app_text, cell_app_text_render);
268
//Advanced tab ---------------------------------------------
271
lbl_exclude = new Label (_("Advanced"));
274
vbox_exclude = new Box(Gtk.Orientation.VERTICAL, 6);
275
vbox_exclude.margin = 6;
276
notebook.append_page (vbox_exclude, lbl_exclude);
278
//toolbar_exclude ---------------------------------------------------
281
toolbar_exclude = new Gtk.Toolbar ();
282
toolbar_exclude.toolbar_style = ToolbarStyle.BOTH_HORIZ;
283
vbox_exclude.add(toolbar_exclude);
285
string png_exclude = App.share_folder + "/timeshift/images/item-gray.png";
286
string png_include = App.share_folder + "/timeshift/images/item-blue.png";
289
btn_exclude = new Gtk.MenuToolButton(null,"");
290
toolbar_exclude.add(btn_exclude);
292
btn_exclude.is_important = true;
293
btn_exclude.label = _("Exclude");
294
btn_exclude.set_tooltip_text (_("Exclude"));
295
btn_exclude.set_icon_widget(new Gtk.Image.from_file (png_exclude));
298
btn_include = new Gtk.MenuToolButton(null,"");
299
toolbar_exclude.add(btn_include);
301
btn_include.is_important = true;
302
btn_include.label = _("Include");
303
btn_include.set_tooltip_text (_("Include"));
304
btn_include.set_icon_widget(new Gtk.Image.from_file (png_include));
307
btn_remove = new Gtk.ToolButton.from_stock("gtk-remove");
308
toolbar_exclude.add(btn_remove);
310
btn_remove.is_important = true;
311
btn_remove.label = _("Remove");
312
btn_remove.set_tooltip_text (_("Remove selected items"));
314
btn_remove.clicked.connect (btn_remove_clicked);
317
btn_warning = new Gtk.ToolButton.from_stock("gtk-dialog-warning");
318
toolbar_exclude.add(btn_warning);
320
btn_warning.is_important = true;
321
btn_warning.label = _("Warning");
322
btn_warning.set_tooltip_text (_("Warning"));
324
btn_warning.clicked.connect (btn_warning_clicked);
327
var separator = new Gtk.SeparatorToolItem();
328
separator.set_draw (false);
329
separator.set_expand (true);
330
toolbar_exclude.add(separator);
332
//btn_reset_exclude_list
333
btn_reset_exclude_list = new Gtk.ToolButton.from_stock("gtk-refresh");
334
toolbar_exclude.add(btn_reset_exclude_list);
336
btn_reset_exclude_list.is_important = false;
337
btn_reset_exclude_list.label = _("Reset");
338
btn_reset_exclude_list.set_tooltip_text (_("Reset this list to default state"));
340
btn_reset_exclude_list.clicked.connect (btn_reset_exclude_list_clicked);
342
//menu --------------------------------------------------
345
menu_exclude = new Gtk.Menu();
346
btn_exclude.set_menu(menu_exclude);
348
//menu_exclude_add_file
349
menu_exclude_add_file = new ImageMenuItem.with_label ("");
350
menu_exclude_add_file.label = _("Exclude File(s)");
351
menu_exclude_add_file.set_image(new Gtk.Image.from_file (png_exclude));
352
menu_exclude.append(menu_exclude_add_file);
354
menu_exclude_add_file.activate.connect (menu_exclude_add_files_clicked);
356
//menu_exclude_add_folder
357
menu_exclude_add_folder = new ImageMenuItem.with_label ("");
358
menu_exclude_add_folder.label = _("Exclude Directory");
359
menu_exclude_add_folder.set_image(new Gtk.Image.from_file (png_exclude));
360
menu_exclude.append(menu_exclude_add_folder);
362
menu_exclude_add_folder.activate.connect (menu_exclude_add_folder_clicked);
364
//menu_exclude_add_folder_contents
365
menu_exclude_add_folder_contents = new ImageMenuItem.with_label ("");
366
menu_exclude_add_folder_contents.label = _("Exclude Directory Contents");
367
menu_exclude_add_folder_contents.set_image(new Gtk.Image.from_file (png_exclude));
368
menu_exclude.append(menu_exclude_add_folder_contents);
370
menu_exclude_add_folder_contents.activate.connect (menu_exclude_add_folder_contents_clicked);
373
menu_include = new Gtk.Menu();
374
btn_include.set_menu(menu_include);
376
//menu_include_add_file
377
menu_include_add_file = new ImageMenuItem.with_label ("");
378
menu_include_add_file.label = _("Include File(s)");
379
menu_include_add_file.set_image(new Gtk.Image.from_file (png_include));
380
menu_include.append(menu_include_add_file);
382
menu_include_add_file.activate.connect (menu_include_add_files_clicked);
384
//menu_include_add_folder
385
menu_include_add_folder = new ImageMenuItem.with_label ("");
386
menu_include_add_folder.label = _("Include Directory");
387
menu_include_add_folder.set_image(new Gtk.Image.from_file (png_include));
388
menu_include.append(menu_include_add_folder);
390
menu_include_add_folder.activate.connect (menu_include_add_folder_clicked);
392
menu_exclude.show_all();
393
menu_include.show_all();
395
//tv_exclude-----------------------------------------------
398
tv_exclude = new TreeView();
399
tv_exclude.get_selection().mode = SelectionMode.MULTIPLE;
400
tv_exclude.headers_visible = true;
401
tv_exclude.set_rules_hint (true);
402
//tv_exclude.row_activated.connect(tv_exclude_row_activated);
405
sw_exclude = new ScrolledWindow(null, null);
406
sw_exclude.set_shadow_type (ShadowType.ETCHED_IN);
407
sw_exclude.add (tv_exclude);
408
sw_exclude.expand = true;
409
vbox_exclude.add(sw_exclude);
412
col_exclude = new TreeViewColumn();
413
col_exclude.title = _("File Pattern");
414
col_exclude.expand = true;
416
CellRendererText cell_exclude_margin = new CellRendererText ();
417
cell_exclude_margin.text = "";
418
col_exclude.pack_start (cell_exclude_margin, false);
420
CellRendererPixbuf cell_exclude_icon = new CellRendererPixbuf ();
421
col_exclude.pack_start (cell_exclude_icon, false);
422
col_exclude.set_attributes(cell_exclude_icon, "pixbuf", 1);
424
CellRendererText cell_exclude_text = new CellRendererText ();
425
col_exclude.pack_start (cell_exclude_text, false);
426
col_exclude.set_cell_data_func (cell_exclude_text, cell_exclude_text_render);
427
cell_exclude_text.editable = true;
428
tv_exclude.append_column(col_exclude);
430
cell_exclude_text.edited.connect (cell_exclude_text_edited);
433
lnk_default_list = new LinkButton.with_label("",_("Some locations are excluded by default"));
434
lnk_default_list.xalign = (float) 0.0;
435
lnk_default_list.activate_link.connect(lnk_default_list_activate);
436
vbox_exclude.add(lnk_default_list);
438
//Actions ----------------------------------------------
441
hbox_action = (Box) get_action_area ();
444
btn_restore = new Button();
445
hbox_action.add(btn_restore);
447
btn_restore.set_label (" " + _("Restore"));
448
btn_restore.set_tooltip_text (_("Restore"));
449
Gtk.Image img_restore = new Image.from_stock("gtk-go-forward", Gtk.IconSize.BUTTON);
450
btn_restore.set_image(img_restore);
451
btn_restore.clicked.connect (btn_restore_clicked);
454
btn_cancel = new Button();
455
hbox_action.add(btn_cancel);
457
btn_cancel.set_label (" " + _("Cancel"));
458
btn_cancel.set_tooltip_text (_("Cancel"));
459
Gtk.Image img_cancel = new Image.from_stock("gtk-cancel", Gtk.IconSize.BUTTON);
460
btn_cancel.set_image(img_cancel);
461
btn_cancel.clicked.connect (btn_cancel_clicked);
463
//initialize -----------------------------------------
465
btn_reset_exclude_list_clicked();
467
refresh_tv_partitions();
468
refresh_cmb_boot_device();
469
//refresh_tv_exclude(); //called by btn_reset_exclude_list_clicked()
472
sw_partitions.sensitive = radio_other.active;
474
notebook.switch_page.connect_after((page, new_page_index) => {
475
if (new_page_index == 1){
476
bool ok = check_and_mount_devices();
478
notebook.set_current_page(0);
483
//save current app selections
484
Gee.ArrayList<string> selected_app_list = new Gee.ArrayList<string>();
485
foreach(AppExcludeEntry entry in App.exclude_list_apps){
487
selected_app_list.add(entry.relpath);
492
App.add_app_exclude_entries();
494
//restore app selections
495
foreach(AppExcludeEntry entry in App.exclude_list_apps){
496
if (selected_app_list.contains(entry.relpath)){
497
entry.enabled = true;
506
set_app_page_state();
512
private void init_device_list(Gtk.Box box){
514
tv_partitions = new TreeView();
515
tv_partitions.get_selection().mode = SelectionMode.SINGLE;
516
tv_partitions.set_rules_hint (true);
517
tv_partitions.button_release_event.connect(tv_partitions_button_press_event);
520
sw_partitions = new ScrolledWindow(null, null);
521
sw_partitions.set_shadow_type (ShadowType.ETCHED_IN);
522
sw_partitions.add (tv_partitions);
523
sw_partitions.expand = true;
524
box.add(sw_partitions);
527
col_device_target = new TreeViewColumn();
528
col_device_target.title = _("Device");
529
col_device_target.spacing = 1;
530
tv_partitions.append_column(col_device_target);
532
CellRendererPixbuf cell_device_icon = new CellRendererPixbuf();
533
cell_device_icon.xpad = 1;
534
col_device_target.pack_start (cell_device_icon, false);
535
col_device_target.set_attributes(cell_device_icon, "pixbuf", 3);
537
CellRendererText cell_device_target = new CellRendererText ();
538
col_device_target.pack_start (cell_device_target, false);
539
col_device_target.set_cell_data_func (cell_device_target, cell_device_target_render);
542
col_fs = new TreeViewColumn();
543
col_fs.title = _("Type");
544
CellRendererText cell_fs = new CellRendererText ();
545
cell_fs.xalign = (float) 0.5;
546
col_fs.pack_start (cell_fs, false);
547
col_fs.set_cell_data_func (cell_fs, cell_fs_render);
548
tv_partitions.append_column(col_fs);
551
col_mount = new TreeViewColumn();
552
col_mount.title = _("Mount");
553
cell_mount = new CellRendererCombo();
554
cell_mount.xalign = (float) 0.0;
555
cell_mount.editable = true;
556
cell_mount.width = 70;
557
col_mount.pack_start (cell_mount, false);
558
col_mount.set_cell_data_func (cell_mount, cell_mount_render);
559
tv_partitions.append_column(col_mount);
561
cell_mount.set_property ("text-column", 0);
562
col_mount.add_attribute (cell_mount, "text", 1);
565
var model = new Gtk.ListStore(1, typeof(string));
566
cell_mount.model = model;
569
model.append(out iter);
570
model.set (iter, 0, "/");
571
model.append(out iter);
572
model.set (iter, 0, "/home");
573
model.append(out iter);
574
model.set (iter, 0, "/boot");
576
cell_mount.changed.connect((path, iter_new) => {
578
cell_mount.model.get (iter_new, 0, out val);
579
model = (Gtk.ListStore) tv_partitions.model;
580
model.get_iter_from_string (out iter, path);
581
model.set (iter, 1, val);
584
cell_mount.edited.connect((path, new_text) => {
585
model = (Gtk.ListStore) tv_partitions.model;
586
model.get_iter_from_string (out iter, path);
587
model.set (iter, 1, new_text);
591
col_size = new TreeViewColumn();
592
col_size.title = _("Size");
593
CellRendererText cell_size = new CellRendererText ();
594
cell_size.xalign = (float) 1.0;
595
col_size.pack_start (cell_size, false);
596
col_size.set_cell_data_func (cell_size, cell_size_render);
597
tv_partitions.append_column(col_size);
600
col_dist = new TreeViewColumn();
601
col_dist.title = _("System");
602
CellRendererText cell_dist = new CellRendererText ();
603
col_dist.pack_start (cell_dist, false);
604
col_dist.set_cell_data_func (cell_dist, cell_dist_render);
605
tv_partitions.append_column(col_dist);
607
tv_partitions.set_tooltip_column(2);
610
private void init_mounts(){
614
App.init_mount_list();
616
if (App.mirror_system){
617
//default all mount points to root device except /boot
618
for(int i = App.mount_list.size - 1; i >= 0; i--){
619
MountEntry mnt = App.mount_list[i];
620
if (mnt.mount_point != "/boot"){
621
App.mount_list.remove_at(i);
626
* While cloning the system, /boot is the only mount point that we will leave unchanged (to avoid encrypted systems from breaking)
627
* All other mounts like /home will be defaulted to target device (to prevent the "cloned" system from using the original device)
631
//find the root mount point set by user
632
store = (Gtk.ListStore) tv_partitions.model;
633
for (bool next = store.get_iter_first (out iter); next; next = store.iter_next (ref iter)) {
636
store.get(iter, 0, out pi);
637
store.get(iter, 1, out mount_point);
639
foreach(MountEntry mnt in App.mount_list){
640
if (mnt.device.device == pi.device){
641
store.set(iter, 1, mnt.mount_point, -1);
647
private void set_app_page_state(){
648
if (App.restore_target == null){
649
lbl_app.sensitive = false;
650
vbox_app.sensitive = false;
653
lbl_app.sensitive = true;
654
vbox_app.sensitive = true;
658
private void cell_device_target_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
660
model.get (iter, 0, out pi, -1);
663
foreach(string sym in pi.symlinks){
664
if (sym.has_prefix("/dev/mapper/")){
665
symlink = sym.replace("/dev/mapper/","");
669
string txt = pi.device;
671
if ((App.root_device != null) && (pi.device == App.root_device.device)){
672
txt += " (" + _("sys") + ")";
675
if (symlink.length > 0){
677
if (symlink.length > 10){
678
txt += symlink[0:10] + "...";
685
Gtk.CellRendererText ctxt = (cell as Gtk.CellRendererText);
687
set_cell_text_color(ref ctxt);
690
private void set_cell_text_color(ref CellRendererText cell){
691
string span = "<span>";
692
if (!sw_partitions.sensitive){
693
span = "<span foreground=\"#585858\">";
695
cell.markup = span + cell.text + "</span>";
698
private void cell_fs_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
700
model.get (iter, 0, out pi, -1);
701
(cell as Gtk.CellRendererText).text = pi.type;
702
Gtk.CellRendererText ctxt = (cell as Gtk.CellRendererText);
703
set_cell_text_color(ref ctxt);
706
private void cell_size_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
708
model.get (iter, 0, out pi, -1);
709
(cell as Gtk.CellRendererText).text = (pi.size_bytes > 0) ? "%s GB".printf(pi.size) : "";
710
Gtk.CellRendererText ctxt = (cell as Gtk.CellRendererText);
711
set_cell_text_color(ref ctxt);
714
private void cell_mount_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
715
(cell as Gtk.CellRendererCombo).background = "#F2F5A9";
718
private void cell_dist_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
720
model.get (iter, 0, out pi, -1);
721
(cell as Gtk.CellRendererText).text = pi.dist_info;
722
Gtk.CellRendererText ctxt = (cell as Gtk.CellRendererText);
723
set_cell_text_color(ref ctxt);
727
private void cell_device_grub_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
729
model.get (iter, 0, out dev, -1);
730
if (dev.devtype == "disk"){
731
(cell as Gtk.CellRendererText).markup = "<b>" + dev.description() + " (MBR)</b>";
734
(cell as Gtk.CellRendererText).markup = dev.description();
738
private void cell_exclude_text_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
740
model.get (iter, 0, out pattern, -1);
741
(cell as Gtk.CellRendererText).text = pattern.has_prefix("+ ") ? pattern[2:pattern.length] : pattern;
744
private void cell_exclude_text_edited (string path, string new_text) {
749
var model = (Gtk.ListStore) tv_exclude.model;
750
model.get_iter_from_string (out iter, path);
751
model.get (iter, 0, out old_pattern, -1);
753
if (old_pattern.has_prefix("+ ")){
754
new_pattern = "+ " + new_text;
757
new_pattern = new_text;
759
model.set (iter, 0, new_pattern);
761
int index = temp_exclude_list.index_of(old_pattern);
762
temp_exclude_list.insert(index, new_pattern);
763
temp_exclude_list.remove(old_pattern);
766
private void cell_app_enabled_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
767
AppExcludeEntry entry;
768
model.get (iter, 0, out entry, -1);
769
(cell as Gtk.CellRendererToggle).active = entry.enabled;
772
private void cell_app_text_render (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter){
773
AppExcludeEntry entry;
774
model.get (iter, 0, out entry, -1);
775
(cell as Gtk.CellRendererText).text = entry.relpath;
778
private void cell_app_enabled_toggled (string path){
779
AppExcludeEntry entry;
781
var model = (Gtk.ListStore) tv_app.model; //get model
782
model.get_iter_from_string (out iter, path); //get selected iter
783
model.get (iter, 0, out entry, -1); //get entry
784
entry.enabled = !entry.enabled;
787
private void refresh_cmb_boot_device(){
788
var store = new Gtk.ListStore(1, typeof(Device));
791
Gee.ArrayList<Device> device_list = new Gee.ArrayList<Device>();
792
foreach(Device di in get_block_devices()) {
797
foreach(Device pi in App.partitions) {
798
if (!pi.has_linux_filesystem()) { continue; }
799
if (pi.device.has_prefix("/dev/dm-")) { continue; }
804
device_list.sort((a,b) => {
805
Device p1 = (Device) a;
806
Device p2 = (Device) b;
808
return strcmp(p1.device,p2.device);
812
foreach(Device entry in device_list) {
813
store.append(out iter);
814
store.set (iter, 0, entry);
817
cmb_boot_device.set_model (store);
818
cmb_boot_device_select_default();
821
private void cmb_boot_device_select_default(){
822
if (App.restore_target == null){
823
cmb_boot_device.active = -1;
828
var store = (Gtk.ListStore) cmb_boot_device.model;
831
int first_mbr_device_index = -1;
832
for (bool next = store.get_iter_first (out iter); next; next = store.iter_next (ref iter)) {
834
store.get(iter, 0, out dev);
838
if (dev.device == App.restore_target.device[0:8]){
839
cmb_boot_device.active = index;
843
if ((first_mbr_device_index == -1) && (dev.device.length == "/dev/sdX".length)){
844
first_mbr_device_index = index;
848
//select first MBR device if not found
849
if (cmb_boot_device.active == -1){
850
cmb_boot_device.active = first_mbr_device_index;
854
private void refresh_tv_partitions(){
856
App.update_partitions();
858
var model = new Gtk.ListStore(4, typeof(Device), typeof(string), typeof(string), typeof(Gdk.Pixbuf));
859
tv_partitions.set_model (model);
862
foreach(Device pi in App.partitions) {
863
if (!pi.has_linux_filesystem()) { continue; }
864
if (!radio_sys.sensitive && (App.root_device != null) && ((pi.device == App.root_device.device)||(pi.uuid == App.root_device.uuid))) { continue; }
867
tt += "%-7s".printf(_("Device")) + "\t: %s\n".printf(pi.full_name_with_alias);
868
tt += "%-7s".printf(_("UUID")) + "\t: %s\n".printf(pi.uuid);
869
tt += "%-7s".printf(_("Type")) + "\t: %s\n".printf(pi.fstype);
870
tt += "%-7s".printf(_("Label")) + "\t: %s\n".printf(pi.label);
871
tt += "%-7s".printf(_("Size")) + "\t: %s\n".printf((pi.size_bytes > 0) ? "%s GB".printf(pi.size) : "");
872
tt += "%-7s".printf(_("Used")) + "\t: %s\n".printf((pi.used_bytes > 0) ? "%s GB".printf(pi.used) : "");
873
tt += "%-7s".printf(_("System")) + "\t: %s".printf(pi.dist_info);
875
model.append(out iter);
876
model.set (iter,0,pi,1,"",2,tt);
878
//set icon ----------------
880
Gdk.Pixbuf pix_selected = null;
881
Gdk.Pixbuf pix_device = get_shared_icon("disk","disk.png",16).pixbuf;
882
Gdk.Pixbuf pix_locked = get_shared_icon("locked","locked.svg",16).pixbuf;
884
if (pi.type == "luks"){
885
pix_selected = pix_locked;
888
pix_selected = pix_device;
891
model.set (iter, 3, pix_selected, -1);
894
tv_partitions_select_target();
897
private void tv_partitions_select_target(){
899
if (App.restore_target == null){
900
tv_partitions.get_selection().unselect_all();
905
var store = (Gtk.ListStore) tv_partitions.model;
907
for (bool next = store.get_iter_first (out iter); next; next = store.iter_next (ref iter)) {
910
store.get(iter, 0, out pi);
911
store.get(iter, 1, out mount_point);
912
if (pi.device == App.restore_target.device){
913
TreePath path = store.get_path(iter);
914
tv_partitions.get_selection().select_path(path);
919
private void refresh_tv_exclude(){
920
var model = new Gtk.ListStore(2, typeof(string), typeof(Gdk.Pixbuf));
921
tv_exclude.model = model;
923
foreach(string path in temp_exclude_list){
924
tv_exclude_add_item(path);
928
private void refresh_tv_apps(){
929
var model = new Gtk.ListStore(1, typeof(AppExcludeEntry));
930
tv_app.model = model;
932
foreach(AppExcludeEntry entry in App.exclude_list_apps){
934
model.append(out iter);
935
model.set (iter, 0, entry, -1);
939
private void tv_exclude_add_item(string path){
940
Gdk.Pixbuf pix_exclude = null;
941
Gdk.Pixbuf pix_include = null;
942
Gdk.Pixbuf pix_selected = null;
945
pix_exclude = new Gdk.Pixbuf.from_file (App.share_folder + "/timeshift/images/item-gray.png");
946
pix_include = new Gdk.Pixbuf.from_file (App.share_folder + "/timeshift/images/item-blue.png");
949
log_error (e.message);
953
var model = (Gtk.ListStore) tv_exclude.model;
954
model.append(out iter);
956
if (path.has_prefix("+ ")){
957
pix_selected = pix_include;
960
pix_selected = pix_exclude;
963
model.set (iter, 0, path, 1, pix_selected, -1);
965
Adjustment adj = tv_exclude.get_hadjustment();
966
adj.value = adj.upper;
969
private bool lnk_default_list_activate(){
970
//show message window -----------------
971
var dialog = new ExcludeMessageWindow();
972
dialog.set_transient_for (this);
979
private bool tv_partitions_button_press_event(Gdk.EventButton event){
985
//get selected target device
986
Device restore_target = null;
987
sel = tv_partitions.get_selection ();
988
store = (Gtk.ListStore) tv_partitions.model;
989
iterExists = store.get_iter_first (out iter);
991
if (sel.iter_is_selected (iter)){
992
store.get (iter, 0, out restore_target);
995
iterExists = store.iter_next (ref iter);
997
App.restore_target = restore_target;
1000
if (selected_target == null){
1001
cmb_boot_device_select_default();
1003
else if (selected_target.device != restore_target.device){
1004
cmb_boot_device_select_default();
1007
//target device has not changed - do not reset to default boot device
1009
selected_target = restore_target;
1011
set_app_page_state();
1017
private void menu_exclude_add_files_clicked(){
1019
var list = browse_files();
1021
if (list.length() > 0){
1022
foreach(string path in list){
1023
if (!temp_exclude_list.contains(path)){
1024
temp_exclude_list.add(path);
1025
tv_exclude_add_item(path);
1026
App.first_snapshot_size = 0; //re-calculate
1032
private void menu_exclude_add_folder_clicked(){
1034
var list = browse_folder();
1036
if (list.length() > 0){
1037
foreach(string path in list){
1041
if (!temp_exclude_list.contains(path)){
1042
temp_exclude_list.add(path);
1043
tv_exclude_add_item(path);
1044
App.first_snapshot_size = 0; //re-calculate
1050
private void menu_exclude_add_folder_contents_clicked(){
1052
var list = browse_folder();
1054
if (list.length() > 0){
1055
foreach(string path in list){
1059
if (!temp_exclude_list.contains(path)){
1060
temp_exclude_list.add(path);
1061
tv_exclude_add_item(path);
1062
App.first_snapshot_size = 0; //re-calculate
1068
private void menu_include_add_files_clicked(){
1070
var list = browse_files();
1072
if (list.length() > 0){
1073
foreach(string path in list){
1075
path = path.has_prefix("+ ") ? path : "+ " + path;
1077
if (!temp_exclude_list.contains(path)){
1078
temp_exclude_list.add(path);
1079
tv_exclude_add_item(path);
1080
App.first_snapshot_size = 0; //re-calculate
1086
private void menu_include_add_folder_clicked(){
1088
var list = browse_folder();
1090
if (list.length() > 0){
1091
foreach(string path in list){
1093
path = path.has_prefix("+ ") ? path : "+ " + path;
1094
path = path + "/***";
1096
if (!temp_exclude_list.contains(path)){
1097
temp_exclude_list.add(path);
1098
tv_exclude_add_item(path);
1099
App.first_snapshot_size = 0; //re-calculate
1105
private SList<string> browse_files(){
1106
var dialog = new Gtk.FileChooserDialog(_("Select file(s)"), this, Gtk.FileChooserAction.OPEN,
1107
"gtk-cancel", Gtk.ResponseType.CANCEL,
1108
"gtk-open", Gtk.ResponseType.ACCEPT);
1109
dialog.action = FileChooserAction.OPEN;
1110
dialog.set_transient_for(this);
1111
dialog.local_only = true;
1112
dialog.set_modal (true);
1113
dialog.set_select_multiple (true);
1116
var list = dialog.get_filenames();
1122
private SList<string> browse_folder(){
1123
var dialog = new Gtk.FileChooserDialog(_("Select directory"), this, Gtk.FileChooserAction.OPEN,
1124
"gtk-cancel", Gtk.ResponseType.CANCEL,
1125
"gtk-open", Gtk.ResponseType.ACCEPT);
1126
dialog.action = FileChooserAction.SELECT_FOLDER;
1127
dialog.local_only = true;
1128
dialog.set_transient_for(this);
1129
dialog.set_modal (true);
1130
dialog.set_select_multiple (false);
1133
var list = dialog.get_filenames();
1140
private void btn_remove_clicked(){
1141
TreeSelection sel = tv_exclude.get_selection ();
1143
bool iterExists = tv_exclude.model.get_iter_first (out iter);
1144
while (iterExists) {
1145
if (sel.iter_is_selected (iter)){
1147
tv_exclude.model.get (iter, 0, out path);
1148
temp_exclude_list.remove(path);
1149
App.first_snapshot_size = 0; //re-calculate
1151
iterExists = tv_exclude.model.iter_next (ref iter);
1154
refresh_tv_exclude();
1157
private void btn_warning_clicked(){
1159
msg += _("By default, any item that was included/excluded at the time of taking the snapshot will be included/excluded.") + " ";
1160
msg += _("Any exclude patterns in the current exclude list will also be excluded.") + " ";
1161
msg += _("To see which files are included in the snapshot use the 'Browse' button on the main window.");
1163
var dialog = new Gtk.MessageDialog.with_markup(null, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, msg);
1164
dialog.set_title("Warning");
1165
dialog.set_default_size (200, -1);
1166
dialog.set_transient_for(this);
1167
dialog.set_modal(true);
1172
private void btn_reset_exclude_list_clicked(){
1173
//create a temp exclude list
1174
temp_exclude_list = new Gee.ArrayList<string>();
1176
//add all include/exclude items from snapshot list
1177
if (App.snapshot_to_restore != null){
1178
foreach(string path in App.snapshot_to_restore.exclude_list){
1179
if (!temp_exclude_list.contains(path) && !App.exclude_list_default.contains(path) && !App.exclude_list_home.contains(path)){
1180
temp_exclude_list.add(path);
1185
//add all exclude items from current list
1186
foreach(string path in App.exclude_list_user){
1187
if (!temp_exclude_list.contains(path) && !App.exclude_list_default.contains(path) && !App.exclude_list_home.contains(path)){
1189
if (!path.has_prefix("+ ")){ //don't add include entries from current exclude list
1190
temp_exclude_list.add(path);
1196
refresh_tv_exclude();
1199
private void btn_restore_clicked(){
1200
//check if backup device is online
1201
if (!check_backup_device_online()) { return; }
1203
//Note: A successful restore will reboot the system if target device is same as system device
1205
bool ok = check_and_mount_devices();
1210
//save grub install options ----------------------
1212
App.reinstall_grub2 = !chk_skip_grub_install.active;
1215
if (App.reinstall_grub2){
1217
cmb_boot_device.get_active_iter (out iter);
1218
TreeModel model = (TreeModel) cmb_boot_device.model;
1219
model.get(iter, 0, out entry);
1220
App.grub_device = entry.device;
1223
App.grub_device = "";
1226
//save modified exclude list ----------------------
1228
App.exclude_list_restore.clear();
1230
//add default entries
1231
foreach(string path in App.exclude_list_default){
1232
if (!App.exclude_list_restore.contains(path)){
1233
App.exclude_list_restore.add(path);
1238
foreach(AppExcludeEntry entry in App.exclude_list_apps){
1240
string pattern = entry.pattern();
1241
if (!App.exclude_list_restore.contains(pattern)){
1242
App.exclude_list_restore.add(pattern);
1245
pattern = entry.pattern(true);
1246
if (!App.exclude_list_restore.contains(pattern)){
1247
App.exclude_list_restore.add(pattern);
1252
//add modified user entries
1253
foreach(string path in temp_exclude_list){
1254
if (!App.exclude_list_restore.contains(path) && !App.exclude_list_home.contains(path)){
1255
App.exclude_list_restore.add(path);
1260
foreach(string path in App.exclude_list_home){
1261
if (!App.exclude_list_restore.contains(path)){
1262
App.exclude_list_restore.add(path);
1266
//exclude timeshift backups
1267
string timeshift_path = "/timeshift/*";
1268
if (!App.exclude_list_restore.contains(timeshift_path)){
1269
App.exclude_list_restore.add(timeshift_path);
1272
//exclude boot directory if grub install is skipped
1273
if (!App.reinstall_grub2){
1274
App.exclude_list_restore.add("/boot/*");
1277
//display and confirm mount points ------------
1279
if (!radio_sys.active){
1280
if (show_mount_list() != Gtk.ResponseType.OK){
1285
//last option to quit - show disclaimer ------------
1287
if (show_disclaimer() == Gtk.ResponseType.YES){
1288
this.response(Gtk.ResponseType.OK);
1291
this.response(Gtk.ResponseType.CANCEL);
1295
private bool check_backup_device_online(){
1296
if (!App.backup_device_online()){
1297
gtk_messagebox(_("Device Offline"),_("Backup device is not available"), this, true);
1305
private bool check_and_mount_devices(){
1307
Gtk.ListStore store;
1310
//check if target device selected ---------------
1312
if (radio_sys.active){
1313
//we are restoring the current system - no need to mount devices
1314
App.restore_target = App.root_device;
1318
//we are restoring to another disk - mount selected devices
1320
App.restore_target = null;
1321
App.mount_list.clear();
1322
bool no_mount_points_set_by_user = true;
1324
//find the root mount point set by user
1325
store = (Gtk.ListStore) tv_partitions.model;
1326
for (bool next = store.get_iter_first (out iter); next; next = store.iter_next (ref iter)) {
1329
store.get(iter, 0, out pi);
1330
store.get(iter, 1, out mount_point);
1332
if ((mount_point != null) && (mount_point.length > 0)){
1333
mount_point = mount_point.strip();
1334
no_mount_points_set_by_user = false;
1336
App.mount_list.add(new MountEntry(pi,mount_point));
1338
if (mount_point == "/"){
1339
App.restore_target = pi;
1344
if (App.restore_target == null){
1345
//no root mount point was set by user
1347
if (no_mount_points_set_by_user){
1348
//user has not set any mount points
1350
//check if a device is selected in treeview
1351
sel = tv_partitions.get_selection ();
1352
if (sel.count_selected_rows() == 1){
1353
//use selected device as the root mount point
1354
for (bool next = store.get_iter_first (out iter); next; next = store.iter_next (ref iter)) {
1355
if (sel.iter_is_selected (iter)){
1357
store.get(iter, 0, out pi);
1358
App.restore_target = pi;
1359
App.mount_list.add(new MountEntry(pi,"/"));
1365
//no device selected and no mount points set by user
1366
string title = _("Select Target Device");
1367
string msg = _("Please select the target device from the list");
1368
gtk_messagebox(title, msg, this, true);
1373
//user has set some mount points but not set the root mount point
1374
string title = _("Select Root Device");
1375
string msg = _("Please select the root device (/)");
1376
gtk_messagebox(title, msg, this, true);
1381
//check BTRFS subvolume layout --------------
1383
if (App.restore_target.type == "btrfs"){
1384
if (App.check_btrfs_volume(App.restore_target) == false){
1385
string title = _("Unsupported Subvolume Layout");
1386
string msg = _("The target partition has an unsupported subvolume layout.") + " ";
1387
msg += _("Only ubuntu-type layouts with @ and @home subvolumes are currently supported.") + "\n\n";
1388
gtk_messagebox(title, msg, this, true);
1393
//mount target device -------------
1395
bool status = App.mount_target_device(this);
1396
if (status == false){
1397
string title = _("Error");
1398
string msg = _("Failed to mount device") + ": %s".printf(App.restore_target.device);
1399
gtk_messagebox(title, msg, this, true);
1404
//check if grub device selected ---------------
1406
if (!chk_skip_grub_install.active && cmb_boot_device.active < 0){
1407
string title =_("Boot device not selected");
1408
string msg = _("Please select the boot device");
1409
gtk_messagebox(title, msg, this, true);
1416
private int show_disclaimer(){
1417
string msg = App.disclaimer_pre_restore();
1419
msg += "<b>" + _("Continue with restore?") + "</b>\n";
1421
var dialog = new Gtk.MessageDialog.with_markup(null, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.YES_NO, msg);
1422
dialog.set_title(_("DISCLAIMER"));
1423
dialog.set_default_size (200, -1);
1424
dialog.set_transient_for(this);
1425
dialog.set_modal(true);
1426
int response = dialog.run();
1431
private int show_mount_list(){
1432
string msg = _("Following mounts will be used for restored system:") + "\n\n";
1434
int max_mount = _("Mount").length;
1435
int max_dev = _("Device").length;
1437
foreach(MountEntry mnt in App.mount_list){
1438
string dev_name = mnt.device.short_name_with_alias;
1439
if (dev_name.length > max_dev){ max_dev = dev_name.length; }
1440
if (mnt.mount_point.length > max_mount){ max_mount = mnt.mount_point.length; }
1445
msg += ("%%-%ds %%-%ds\n\n".printf(max_dev, max_mount)).printf(_("Device"),_("Mount"));
1447
foreach(MountEntry mnt in App.mount_list){
1448
msg += ("%%-%ds %%-%ds\n\n".printf(max_dev, max_mount)).printf(mnt.device.short_name_with_alias, mnt.mount_point);
1451
msg += "\n" + _("Click OK to continue") + "\n";
1453
var dialog = new Gtk.MessageDialog.with_markup(null, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK_CANCEL, msg);
1454
dialog.set_title(_("Confirm Mounts"));
1455
dialog.set_default_size (200, -1);
1456
dialog.set_transient_for(this);
1457
dialog.set_modal(true);
1458
int response = dialog.run();
1463
private void btn_cancel_clicked(){
1464
App.unmount_target_device();
1465
this.response(Gtk.ResponseType.CANCEL);