~teejee2008/timeshift/trunk

« back to all changes in this revision

Viewing changes to src/Core/SnapshotRepo.vala

  • Committer: Tony George
  • Date: 2016-08-24 17:02:17 UTC
  • Revision ID: tony.george.kol@gmail.com-20160824170217-evbrjfbzrkjbt4x5
Moved classes to separate files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
using TeeJee.Logging;
 
2
using TeeJee.FileSystem;
 
3
using TeeJee.Devices;
 
4
using TeeJee.JsonHelper;
 
5
using TeeJee.ProcessHelper;
 
6
using TeeJee.GtkHelper;
 
7
using TeeJee.System;
 
8
using TeeJee.Misc;
 
9
 
 
10
public class SnapshotRepo : GLib.Object{
 
11
        public Device device = null;
 
12
        public string snapshot_path_user = "";
 
13
        public string snapshot_path_mount = "";
 
14
        public bool use_snapshot_path_custom = false;
 
15
 
 
16
        public Gee.ArrayList<Snapshot?> snapshots;
 
17
 
 
18
        public string status_message = "";
 
19
        public string status_details = "";
 
20
        public SnapshotLocationStatus status_code;
 
21
 
 
22
        // private
 
23
        private Gtk.Window? parent_window = null;
 
24
        private bool thr_success = false;
 
25
        private bool thr_running = false;
 
26
        //private int thr_retval = -1;
 
27
        private string thr_args1 = "";
 
28
 
 
29
        public SnapshotRepo.from_path(string path, Gtk.Window? parent_win){
 
30
                this.snapshot_path_user = path;
 
31
                this.use_snapshot_path_custom = true;
 
32
                this.parent_window = parent_win;
 
33
                
 
34
                snapshots = new Gee.ArrayList<Snapshot>();
 
35
 
 
36
                log_msg(_("Selected snapshot path") + ": %s".printf(path));
 
37
                
 
38
                var list = Device.get_disk_space_using_df(path);
 
39
                if (list.size > 0){
 
40
                        device = list[0];
 
41
                        
 
42
                        log_msg(_("Device") + ": %s".printf(device.device));
 
43
                        log_msg(_("Free space") + ": %s".printf(format_file_size(device.free_bytes)));
 
44
                }
 
45
        }
 
46
 
 
47
        public SnapshotRepo.from_device(Device dev, Gtk.Window? parent_win){
 
48
                this.device = dev;
 
49
                this.use_snapshot_path_custom = false;
 
50
                this.parent_window = parent_win;
 
51
                
 
52
                snapshots = new Gee.ArrayList<Snapshot>();
 
53
 
 
54
                if ((dev != null) && (dev.uuid.length > 0)){
 
55
                        log_msg("");
 
56
                        unlock_and_mount_device();
 
57
 
 
58
                        log_msg(_("Selected snapshot device") + ": %s".printf(device.device));
 
59
                        log_msg(_("Free space") + ": %s".printf(format_file_size(device.free_bytes)));;
 
60
                }
 
61
        }
 
62
 
 
63
        public string snapshot_location {
 
64
                owned get{
 
65
                        if (use_snapshot_path_custom && dir_exists(snapshot_path_user)){
 
66
                                return snapshot_path_user;
 
67
                        }
 
68
                        else{
 
69
                                return snapshot_path_mount;
 
70
                        }
 
71
                }
 
72
        }
 
73
 
 
74
        // load
 
75
 
 
76
        public bool unlock_and_mount_device(){
 
77
                
 
78
                // unlock encrypted device
 
79
                if (device.is_encrypted()){
 
80
 
 
81
                        device = unlock_encrypted_device(device);
 
82
                        
 
83
                        if (device == null){
 
84
                                return false;
 
85
                        }
 
86
                }
 
87
 
 
88
                if (device.fstype == "btrfs"){
 
89
 
 
90
                        snapshot_path_mount = "/mnt/timeshift/backup";
 
91
                        
 
92
                        Device.unmount(snapshot_path_mount);
 
93
                        
 
94
                        // mount
 
95
                        
 
96
                        bool ok = Device.mount(device.uuid, snapshot_path_mount, "");
 
97
                        if (!ok){
 
98
                                snapshot_path_mount = "";
 
99
                        }
 
100
                }
 
101
                else{
 
102
                        var mps = Device.get_device_mount_points(device.uuid);
 
103
 
 
104
                        if (mps.size > 0){
 
105
                                snapshot_path_mount = mps[0].mount_point;
 
106
                        }
 
107
                        else{
 
108
                                Device.automount_udisks(device.device);
 
109
 
 
110
                                mps = Device.get_device_mount_points(device.uuid);
 
111
                                if (mps.size > 0){
 
112
                                        snapshot_path_mount = mps[0].mount_point;
 
113
                                }
 
114
                                else{
 
115
                                        snapshot_path_mount = "";
 
116
                                }
 
117
                        }
 
118
                }
 
119
                
 
120
                return false;
 
121
        }
 
122
 
 
123
        public Device unlock_encrypted_device(Device luks_device){
 
124
                Device luks_unlocked = null;
 
125
 
 
126
                string mapped_name = "%s_unlocked".printf(luks_device.name);
 
127
 
 
128
                var partitions = Device.get_block_devices_using_lsblk();
 
129
                
 
130
                // check if already unlocked
 
131
                foreach(var part in partitions){
 
132
                        if (part.pkname == luks_device.kname){
 
133
                                log_msg(_("Unlocked device is mapped to '%s'").printf(part.device));
 
134
                                log_msg("");
 
135
                                return part;
 
136
                        }
 
137
                }
 
138
                        
 
139
                if (parent_window == null){
 
140
 
 
141
                        var counter = new TimeoutCounter();
 
142
                        counter.kill_process_on_timeout("cryptsetup", 20, true);
 
143
 
 
144
                        // prompt user to unlock
 
145
                        string cmd = "cryptsetup luksOpen '%s' '%s'".printf(luks_device.device, mapped_name);
 
146
                        Posix.system(cmd);
 
147
                        counter.stop();
 
148
                        log_msg("");
 
149
 
 
150
                        partitions = Device.get_block_devices_using_lsblk();
 
151
 
 
152
                        // check if unlocked
 
153
                        foreach(var part in partitions){
 
154
                                if (part.pkname == luks_device.kname){
 
155
                                        log_msg(_("Unlocked device is mapped to '%s'").printf(part.name));
 
156
                                        log_msg("");
 
157
                                        return part;
 
158
                                }
 
159
                        }
 
160
                }
 
161
                else{
 
162
                        // prompt user for password
 
163
                        string passphrase = gtk_inputbox(
 
164
                                _("Encrypted Device"),
 
165
                                _("Enter passphrase to unlock '%s'").printf(luks_device.name),
 
166
                                parent_window, true);
 
167
 
 
168
                        string message, details;
 
169
                        luks_unlocked = Device.luks_unlock(luks_device, mapped_name, passphrase,
 
170
                                out message, out details);
 
171
 
 
172
                        bool is_error = (luks_unlocked == null);
 
173
                        
 
174
                        gtk_messagebox(message, details, null, is_error);
 
175
                }
 
176
 
 
177
                return luks_unlocked;
 
178
        }
 
179
        
 
180
        public bool load_snapshots(){
 
181
 
 
182
                snapshots.clear();
 
183
 
 
184
                string path = snapshot_location + "/timeshift/snapshots";
 
185
 
 
186
                if (!dir_exists(path)){
 
187
                        log_error("Path not found: %s".printf(path));
 
188
                        return false;
 
189
                }
 
190
 
 
191
                try{
 
192
                        var dir = File.new_for_path (path);
 
193
                        var enumerator = dir.enumerate_children ("*", 0);
 
194
 
 
195
                        var info = enumerator.next_file ();
 
196
                        while (info != null) {
 
197
                                if (info.get_file_type() == FileType.DIRECTORY) {
 
198
                                        if (info.get_name() != ".sync") {
 
199
                                                Snapshot bak = new Snapshot(path + "/" + info.get_name());
 
200
                                                if (bak.is_valid){
 
201
                                                        snapshots.add(bak);
 
202
                                                }
 
203
                                        }
 
204
                                }
 
205
                                info = enumerator.next_file ();
 
206
                        }
 
207
                }
 
208
                catch(Error e){
 
209
                        log_error (e.message);
 
210
                        return false;
 
211
                }
 
212
 
 
213
                snapshots.sort((a,b) => {
 
214
                        Snapshot t1 = (Snapshot) a;
 
215
                        Snapshot t2 = (Snapshot) b;
 
216
                        return t1.date.compare(t2.date);
 
217
                });
 
218
 
 
219
                return true;
 
220
        }
 
221
 
 
222
        // get tagged snapshots
 
223
        
 
224
        public Gee.ArrayList<Snapshot?> get_snapshots_by_tag(string tag = ""){
 
225
                var list = new Gee.ArrayList<Snapshot?>();
 
226
 
 
227
                foreach(Snapshot bak in snapshots){
 
228
                        if (tag == "" || bak.has_tag(tag)){
 
229
                                list.add(bak);
 
230
                        }
 
231
                }
 
232
                list.sort((a,b) => {
 
233
                        Snapshot t1 = (Snapshot) a;
 
234
                        Snapshot t2 = (Snapshot) b;
 
235
                        return (t1.date.compare(t2.date));
 
236
                });
 
237
 
 
238
                return list;
 
239
        }
 
240
 
 
241
        public Snapshot? get_latest_snapshot(string tag = ""){
 
242
                var list = get_snapshots_by_tag(tag);
 
243
 
 
244
                if (list.size > 0)
 
245
                        return list[list.size - 1];
 
246
                else
 
247
                        return null;
 
248
        }
 
249
 
 
250
        public Snapshot? get_oldest_snapshot(string tag = ""){
 
251
                var list = get_snapshots_by_tag(tag);
 
252
 
 
253
                if (list.size > 0)
 
254
                        return list[0];
 
255
                else
 
256
                        return null;
 
257
        }
 
258
 
 
259
        // status check
 
260
 
 
261
        public void check_status(){
 
262
 
 
263
                status_code = SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE;
 
264
                status_message = "";
 
265
                status_details = "";
 
266
 
 
267
                log_msg("");
 
268
                log_msg("Config: Free space limit is %s".printf(
 
269
                        format_file_size(App.minimum_free_disk_space)));
 
270
 
 
271
                if (is_available()){
 
272
                        has_snapshots();
 
273
                        has_space();
 
274
                }
 
275
 
 
276
                if (use_snapshot_path_custom){
 
277
                        log_msg("Custom path is selected for snapshot location");
 
278
                }
 
279
                
 
280
                log_msg(_("Snapshot device") + ": '%s'".printf(
 
281
                        (device == null) ? " UNKNOWN" : device.device));
 
282
                        
 
283
                log_msg(_("Snapshot location") + ": '%s'".printf(snapshot_location));
 
284
 
 
285
                log_msg(status_message);
 
286
                log_msg(status_details);
 
287
                
 
288
                log_msg("Status: %s".printf(
 
289
                        status_code.to_string().replace("SNAPSHOT_LOCATION_STATUS_","")));
 
290
        }
 
291
 
 
292
        public bool is_available(){
 
293
                if (use_snapshot_path_custom){
 
294
                        if (snapshot_path_user.strip().length == 0){
 
295
                                status_message = _("Snapshot location not selected");
 
296
                                status_details = _("Select the location for saving snapshots");
 
297
                                status_code = SnapshotLocationStatus.NOT_SELECTED;
 
298
                                return false;
 
299
                        }
 
300
                        else{
 
301
                                if (!dir_exists(snapshot_path_user)){
 
302
                                        status_message = _("Snapshot location not available!");
 
303
                                        status_details = _("Path not found") + ": '%s'".printf(snapshot_path_user);
 
304
                                        status_code = SnapshotLocationStatus.NOT_AVAILABLE;
 
305
                                        return false;
 
306
                                }
 
307
                                else{
 
308
                                        bool is_readonly;
 
309
                                        bool hardlink_supported =
 
310
                                                filesystem_supports_hardlinks(snapshot_path_user, out is_readonly);
 
311
 
 
312
                                        if (is_readonly){
 
313
                                                status_message = _("File system is read-only!");
 
314
                                                status_details = _("Select another location for saving snapshots");
 
315
                                                status_code = SnapshotLocationStatus.READ_ONLY_FS;
 
316
                                                return false;
 
317
                                        }
 
318
                                        else if (!hardlink_supported){
 
319
                                                status_message = _("File system does not support hard-links!");
 
320
                                                status_details = _("Select another location for saving snapshots");
 
321
                                                status_code = SnapshotLocationStatus.HARDLINKS_NOT_SUPPORTED;
 
322
                                                return false;
 
323
                                        }
 
324
                                        else{
 
325
                                                // ok
 
326
                                                return true;
 
327
                                        }
 
328
                                }
 
329
                        }
 
330
                }
 
331
                else{
 
332
                        if (device == null){
 
333
                                status_message = _("Snapshot location not selected");
 
334
                                status_details = _("Select the location for saving snapshots");
 
335
                                status_code = SnapshotLocationStatus.NOT_SELECTED;
 
336
                                return false;
 
337
                        }
 
338
                        else if (device.device.length == 0){
 
339
                                status_message = _("Snapshot location not available!");
 
340
                                status_details = _("Device not found") + ": UUID='%s'".printf(device.uuid);
 
341
                                status_code = SnapshotLocationStatus.NOT_AVAILABLE;
 
342
                                return false;
 
343
                        }
 
344
                        else{
 
345
                                // ok
 
346
                                return true;
 
347
                        }
 
348
                }
 
349
        }
 
350
        
 
351
        public bool has_snapshots(){
 
352
                load_snapshots();
 
353
                return (snapshots.size > 0);
 
354
        }
 
355
 
 
356
        public bool has_space(){
 
357
 
 
358
                if (device != null){
 
359
                        device.query_disk_space();
 
360
                }
 
361
                
 
362
                if (snapshots.size > 0){
 
363
                        // has snapshots, check minimum space
 
364
 
 
365
                        var min_free = App.minimum_free_disk_space;
 
366
                        
 
367
                        if (device.free_bytes < min_free){
 
368
                                status_message = _("Not enough disk space");
 
369
                                status_message += " (< %s)".printf(format_file_size(min_free));
 
370
                                        
 
371
                                status_details = _("Select another device or free up some space");
 
372
                                
 
373
                                status_code = SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE;
 
374
                                return false;
 
375
                        }
 
376
                        else{
 
377
                                //ok
 
378
                                status_message = "ok";
 
379
                                
 
380
                                status_details = _("%d snapshots, %s free").printf(
 
381
                                        snapshots.size, format_file_size(device.free_bytes));
 
382
                                        
 
383
                                status_code = SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE;
 
384
                                return true;
 
385
                        }
 
386
                }
 
387
                else {
 
388
 
 
389
                        // no snapshots, check estimated space
 
390
                        
 
391
                        var required_space = App.first_snapshot_size;
 
392
 
 
393
                        if (device.free_bytes < required_space){
 
394
                                status_message = _("Not enough disk space");
 
395
                                status_message += " (< %s)".printf(format_file_size(required_space));
 
396
                                
 
397
                                status_details = _("Select another device or free up some space");
 
398
                                
 
399
                                status_code = SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE;
 
400
                                return false;
 
401
                        }
 
402
                        else{
 
403
                                status_message = _("No snapshots on this device");
 
404
                                
 
405
                                status_details = _("First snapshot requires:");
 
406
                                status_details += " %s".printf(format_file_size(required_space));
 
407
                                
 
408
                                status_code = SnapshotLocationStatus.NO_SNAPSHOTS_HAS_SPACE;
 
409
                                return true;
 
410
                        }
 
411
                }
 
412
        }
 
413
 
 
414
        // actions
 
415
 
 
416
        public void auto_remove(){
 
417
                DateTime now = new DateTime.now_local();
 
418
                int count = 0;
 
419
                bool show_msg = false;
 
420
                DateTime dt_limit;
 
421
 
 
422
                // delete older backups - boot ---------------
 
423
 
 
424
                var list = get_snapshots_by_tag("boot");
 
425
 
 
426
                if (list.size > App.count_boot){
 
427
                        log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf("boot"));
 
428
                        while (list.size > App.count_boot){
 
429
                                list[0].remove_tag("boot");
 
430
                                log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf("boot"));
 
431
                                list = get_snapshots_by_tag("boot");
 
432
                        }
 
433
                }
 
434
 
 
435
                // delete older backups - hourly, daily, weekly, monthly ---------
 
436
 
 
437
                string[] levels = { "hourly","daily","weekly","monthly" };
 
438
 
 
439
                foreach(string level in levels){
 
440
                        list = get_snapshots_by_tag(level);
 
441
 
 
442
                        if (list.size == 0) { continue; }
 
443
 
 
444
                        switch (level){
 
445
                                case "hourly":
 
446
                                        dt_limit = now.add_hours(-1 * App.count_hourly);
 
447
                                        break;
 
448
                                case "daily":
 
449
                                        dt_limit = now.add_days(-1 * App.count_daily);
 
450
                                        break;
 
451
                                case "weekly":
 
452
                                        dt_limit = now.add_weeks(-1 * App.count_weekly);
 
453
                                        break;
 
454
                                case "monthly":
 
455
                                        dt_limit = now.add_months(-1 * App.count_monthly);
 
456
                                        break;
 
457
                                default:
 
458
                                        dt_limit = now.add_years(-1 * 10);
 
459
                                        break;
 
460
                        }
 
461
 
 
462
                        if (list[0].date.compare(dt_limit) < 0){
 
463
 
 
464
                                log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf(level));
 
465
 
 
466
                                while (list[0].date.compare(dt_limit) < 0){
 
467
                                        list[0].remove_tag(level);
 
468
                                        log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf(level));
 
469
                                        list = get_snapshots_by_tag(level);
 
470
                                }
 
471
                        }
 
472
                }
 
473
 
 
474
                // delete older backups - max days -------
 
475
 
 
476
                show_msg = true;
 
477
                count = 0;
 
478
                foreach(var bak in snapshots){
 
479
                        if (bak.date.compare(now.add_days(-1 * App.retain_snapshots_max_days)) < 0){
 
480
                                if (!bak.has_tag("ondemand")){
 
481
 
 
482
                                        if (show_msg){
 
483
                                                log_msg(_("Removing backups older than") + " %d ".printf(
 
484
                                                        App.retain_snapshots_max_days) + _("days..."));
 
485
                                                show_msg = false;
 
486
                                        }
 
487
 
 
488
                                        log_msg(_("Snapshot") + " '%s' ".printf(bak.name) + _("un-tagged"));
 
489
                                        bak.tags.clear();
 
490
                                        count++;
 
491
                                }
 
492
                        }
 
493
                }
 
494
 
 
495
                remove_untagged();
 
496
 
 
497
                // delete older backups - minimum space -------
 
498
 
 
499
                device.query_disk_space();
 
500
 
 
501
                show_msg = true;
 
502
                count = 0;
 
503
                while ((device.size_bytes - device.used_bytes) < App.minimum_free_disk_space){
 
504
                        
 
505
                        load_snapshots();
 
506
                        
 
507
                        if (snapshots.size > 0){
 
508
                                if (!snapshots[0].has_tag("ondemand")){
 
509
 
 
510
                                        if (show_msg){
 
511
                                                log_msg(_("Free space is less than") + " %lld GB".printf(
 
512
                                                        App.minimum_free_disk_space / GB));
 
513
                                                log_msg(_("Removing older backups to free disk space"));
 
514
                                                show_msg = false;
 
515
                                        }
 
516
 
 
517
                                        snapshots[0].remove();
 
518
                                }
 
519
                        }
 
520
                        
 
521
                        device.query_disk_space();
 
522
                }
 
523
        }
 
524
 
 
525
        public void remove_untagged(){
 
526
                bool show_msg = true;
 
527
 
 
528
                foreach(Snapshot bak in snapshots){
 
529
                        if (bak.tags.size == 0){
 
530
 
 
531
                                if (show_msg){
 
532
                                        log_msg(_("Removing un-tagged snapshots..."));
 
533
                                        show_msg = false;
 
534
                                }
 
535
 
 
536
                                bak.remove();
 
537
                        }
 
538
                }
 
539
        }
 
540
 
 
541
        public bool remove_all(){
 
542
                string timeshift_dir = snapshot_location + "/timeshift";
 
543
                string sync_dir = snapshot_location + "/timeshift/snapshots/.sync";
 
544
 
 
545
                if (dir_exists(timeshift_dir)){
 
546
                        //delete snapshots
 
547
                        foreach(var bak in snapshots){
 
548
                                if (!bak.remove()){
 
549
                                        return false;
 
550
                                }
 
551
                        }
 
552
 
 
553
                        //delete .sync
 
554
                        if (dir_exists(sync_dir)){
 
555
                                if (!delete_directory(sync_dir)){
 
556
                                        return false;
 
557
                                }
 
558
                        }
 
559
 
 
560
                        //delete /timeshift
 
561
                        return delete_directory(timeshift_dir);
 
562
                }
 
563
                else{
 
564
                        log_msg(_("No snapshots found") + " '%s'".printf(snapshot_location));
 
565
                        return true;
 
566
                }
 
567
        }
 
568
 
 
569
        public bool remove_sync_dir(){
 
570
                string sync_dir = snapshot_location + "/timeshift/snapshots/.sync";
 
571
                //delete .sync
 
572
                if (dir_exists(sync_dir)){
 
573
                        if (!delete_directory(sync_dir)){
 
574
                                return false;
 
575
                        }
 
576
                }
 
577
                
 
578
                return true;
 
579
        }
 
580
        
 
581
        // private
 
582
        
 
583
        private bool delete_directory(string dir_path){
 
584
                thr_args1 = dir_path;
 
585
 
 
586
                try {
 
587
                        thr_running = true;
 
588
                        thr_success = false;
 
589
                        Thread.create<void> (delete_directory_thread, true);
 
590
                } catch (ThreadError e) {
 
591
                        thr_running = false;
 
592
                        thr_success = false;
 
593
                        log_error (e.message);
 
594
                }
 
595
 
 
596
                while (thr_running){
 
597
                        gtk_do_events ();
 
598
                        Thread.usleep((ulong) GLib.TimeSpan.MILLISECOND * 100);
 
599
                }
 
600
 
 
601
                thr_args1 = null;
 
602
 
 
603
                return thr_success;
 
604
        }
 
605
 
 
606
        private void delete_directory_thread(){
 
607
                string cmd = "";
 
608
                string std_out;
 
609
                string std_err;
 
610
                int ret_val;
 
611
 
 
612
                try{
 
613
                        var f = File.new_for_path(thr_args1);
 
614
                        if(f.query_exists()){
 
615
                                cmd = "rm -rf \"%s\"".printf(thr_args1);
 
616
 
 
617
                                if (LOG_COMMANDS) { log_debug(cmd); }
 
618
 
 
619
                                Process.spawn_command_line_sync(cmd, out std_out, out std_err, out ret_val);
 
620
 
 
621
                                if (ret_val != 0){
 
622
                                        log_error(_("Failed to remove") + ": '%s'".printf(thr_args1));
 
623
                                        thr_success = false;
 
624
                                        thr_running = false;
 
625
                                        return;
 
626
                                }
 
627
                                else{
 
628
                                        log_msg(_("Removed") + ": '%s'".printf(thr_args1));
 
629
                                        thr_success = true;
 
630
                                        thr_running = false;
 
631
                                        return;
 
632
                                }
 
633
                        }
 
634
                        else{
 
635
                                log_error(_("Directory not found") + ": '%s'".printf(thr_args1));
 
636
                                thr_success = true;
 
637
                                thr_running = false;
 
638
                        }
 
639
                }
 
640
                catch(Error e){
 
641
                        log_error (e.message);
 
642
                        thr_success = false;
 
643
                        thr_running = false;
 
644
                        return;
 
645
                }
 
646
        }
 
647
 
 
648
        // symlinks
 
649
        
 
650
        public void create_symlinks(){
 
651
                string cmd = "";
 
652
                string std_out;
 
653
                string std_err;
 
654
                int ret_val;
 
655
 
 
656
                cleanup_symlink_dir("boot");
 
657
                cleanup_symlink_dir("hourly");
 
658
                cleanup_symlink_dir("daily");
 
659
                cleanup_symlink_dir("weekly");
 
660
                cleanup_symlink_dir("monthly");
 
661
                cleanup_symlink_dir("ondemand");
 
662
 
 
663
                string path;
 
664
 
 
665
                foreach(var bak in snapshots){
 
666
                        foreach(string tag in bak.tags){
 
667
                                
 
668
                                path = snapshot_location + "/timeshift/snapshots-%s".printf(tag);
 
669
                                cmd = "ln --symbolic \"../snapshots/%s\" -t \"%s\"".printf(bak.name, path);
 
670
 
 
671
                                if (LOG_COMMANDS) { log_debug(cmd); }
 
672
 
 
673
                                ret_val = exec_sync(cmd, out std_out, out std_err);
 
674
                                if (ret_val != 0){
 
675
                                        log_error (std_err);
 
676
                                        log_error(_("Failed to create symlinks") + ": snapshots-%s".printf(tag));
 
677
                                        return;
 
678
                                }
 
679
                        }
 
680
                }
 
681
 
 
682
                log_debug (_("Symlinks updated"));
 
683
        }
 
684
 
 
685
        public void cleanup_symlink_dir(string tag){
 
686
                string cmd = "";
 
687
                string std_out;
 
688
                string std_err;
 
689
                int ret_val;
 
690
 
 
691
                try{
 
692
                        string path = snapshot_location + "/timeshift/snapshots-%s".printf(tag);
 
693
                        var f = File.new_for_path(path);
 
694
                        if (f.query_exists()){
 
695
                                cmd = "rm -rf \"%s\"".printf(path + "/");
 
696
 
 
697
                                if (LOG_COMMANDS) { log_debug(cmd); }
 
698
 
 
699
                                Process.spawn_command_line_sync(cmd, out std_out, out std_err, out ret_val);
 
700
                                if (ret_val != 0){
 
701
                                        log_error (std_err);
 
702
                                        log_error(_("Failed to delete symlinks") + ": 'snapshots-%s'".printf(tag));
 
703
                                        return;
 
704
                                }
 
705
                        }
 
706
 
 
707
                        f.make_directory_with_parents();
 
708
                }
 
709
                catch (Error e) {
 
710
                log_error (e.message);
 
711
            }
 
712
        }
 
713
 
 
714
}
 
715
 
 
716
public enum SnapshotLocationStatus{
 
717
        /*
 
718
        -1 - device un-available, path does not exist
 
719
         0 - first snapshot taken, disk space sufficient
 
720
         1 - first snapshot taken, disk space not sufficient
 
721
         2 - first snapshot not taken, disk space not sufficient
 
722
         3 - first snapshot not taken, disk space sufficient
 
723
         4 - path is readonly
 
724
     5 - hardlinks not supported
 
725
        */
 
726
        NOT_SELECTED = -2,
 
727
        NOT_AVAILABLE = -1,
 
728
        HAS_SNAPSHOTS_HAS_SPACE = 0,
 
729
        HAS_SNAPSHOTS_NO_SPACE = 1,
 
730
        NO_SNAPSHOTS_NO_SPACE = 2,
 
731
        NO_SNAPSHOTS_HAS_SPACE = 3,
 
732
        READ_ONLY_FS = 4,
 
733
        HARDLINKS_NOT_SUPPORTED = 5
 
734
}