diff --git a/src/AppConsole.vala b/src/AppConsole.vala index e0983ccc..1b03fbfa 100644 --- a/src/AppConsole.vala +++ b/src/AppConsole.vala @@ -210,8 +210,7 @@ public class AppConsole : GLib.Object { break; case "--tags": - App.cmd_tags = args[++k]; - App.validate_cmd_tags(); + App.parse_cmd_tags(args[++k]); break; case "--debug": @@ -450,7 +449,7 @@ public class AppConsole : GLib.Object { grid[row, ++col] = "%d".printf(index); grid[row, ++col] = ">"; grid[row, ++col] = "%s".printf(bak.name); - grid[row, ++col] = "%s".printf(bak.taglist_short); + grid[row, ++col] = "%s".printf(bak.tags.as_short_list()); grid[row, ++col] = "%s".printf(bak.description); row++; } diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 52c4650e..9fad17f2 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -84,11 +84,10 @@ public class Main : GLib.Object{ public LinuxDistro current_distro; public bool mirror_system = false; - public bool schedule_monthly = false; - public bool schedule_weekly = false; - public bool schedule_daily = false; - public bool schedule_hourly = false; - public bool schedule_boot = false; + // tags that are scheduled + public Tags schedule = 0; + + // planned snapshots per tag public int count_monthly = 2; public int count_weekly = 3; public int count_daily = 5; @@ -155,7 +154,7 @@ public class Main : GLib.Object{ public bool cmd_verbose = true; public bool cmd_scripted = false; public string cmd_comments = ""; - public string cmd_tags = ""; + public Tags cmd_tags = 0; public bool? cmd_btrfs_mode = null; public string progress_text = ""; @@ -189,7 +188,7 @@ public class Main : GLib.Object{ } public Main(string[] args, bool gui_mode){ - + this.mount_point_app = "/run/timeshift/%d".printf(Posix.getpid()); dir_create(this.mount_point_app); @@ -1061,9 +1060,7 @@ public class Main : GLib.Object{ public bool scheduled{ get{ - return !live_system() - && (schedule_boot || schedule_hourly || schedule_daily || - schedule_weekly || schedule_monthly); + return !live_system() && (schedule > 0); } } @@ -1138,7 +1135,7 @@ public class Main : GLib.Object{ // ondemand if (is_ondemand){ - bool ok = create_snapshot_for_tag ("ondemand",now); + bool ok = create_snapshot_for_tags(App.cmd_tags, now); if(!ok){ return false; } @@ -1147,16 +1144,16 @@ public class Main : GLib.Object{ } } else if (scheduled){ - Snapshot last_snapshot_boot = repo.get_latest_snapshot("boot", sys_uuid); - Snapshot last_snapshot_hourly = repo.get_latest_snapshot("hourly", sys_uuid); - Snapshot last_snapshot_daily = repo.get_latest_snapshot("daily", sys_uuid); - Snapshot last_snapshot_weekly = repo.get_latest_snapshot("weekly", sys_uuid); - Snapshot last_snapshot_monthly = repo.get_latest_snapshot("monthly", sys_uuid); + Snapshot last_snapshot_boot = repo.get_latest_snapshot(Tags.Boot, sys_uuid); + Snapshot last_snapshot_hourly = repo.get_latest_snapshot(Tags.Hourly, sys_uuid); + Snapshot last_snapshot_daily = repo.get_latest_snapshot(Tags.Daily, sys_uuid); + Snapshot last_snapshot_weekly = repo.get_latest_snapshot(Tags.Weekly, sys_uuid); + Snapshot last_snapshot_monthly = repo.get_latest_snapshot(Tags.Monthly, sys_uuid); DateTime dt_sys_boot = now.add_seconds((-1) * get_system_uptime_seconds()); bool take_new = false; - if (schedule_boot){ + if (Tags.Boot in App.schedule){ log_msg(_("Boot snapshots are enabled")); @@ -1175,7 +1172,7 @@ public class Main : GLib.Object{ } if (take_new){ - status = create_snapshot_for_tag ("boot",now); + status = create_snapshot_for_tags(Tags.Boot, now); if(!status){ log_error(_("Boot snapshot failed!")); return false; @@ -1187,7 +1184,7 @@ public class Main : GLib.Object{ } } - if (schedule_hourly){ + if (Tags.Hourly in App.schedule){ log_msg(_("Hourly snapshots are enabled")); @@ -1206,7 +1203,7 @@ public class Main : GLib.Object{ } if (take_new){ - status = create_snapshot_for_tag ("hourly",now); + status = create_snapshot_for_tags(Tags.Hourly, now); if(!status){ log_error(_("Hourly snapshot failed!")); return false; @@ -1218,7 +1215,7 @@ public class Main : GLib.Object{ } } - if (schedule_daily){ + if (Tags.Daily in App.schedule){ log_msg(_("Daily snapshots are enabled")); @@ -1237,7 +1234,7 @@ public class Main : GLib.Object{ } if (take_new){ - status = create_snapshot_for_tag ("daily",now); + status = create_snapshot_for_tags(Tags.Daily, now); if(!status){ log_error(_("Daily snapshot failed!")); return false; @@ -1249,7 +1246,7 @@ public class Main : GLib.Object{ } } - if (schedule_weekly){ + if (Tags.Weekly in App.schedule){ log_msg(_("Weekly snapshots are enabled")); @@ -1268,7 +1265,7 @@ public class Main : GLib.Object{ } if (take_new){ - status = create_snapshot_for_tag ("weekly",now); + status = create_snapshot_for_tags(Tags.Weekly, now); if(!status){ log_error(_("Weekly snapshot failed!")); return false; @@ -1280,7 +1277,7 @@ public class Main : GLib.Object{ } } - if (schedule_monthly){ + if (Tags.Monthly in App.schedule){ log_msg(_("Monthly snapshot are enabled")); @@ -1299,7 +1296,7 @@ public class Main : GLib.Object{ } if (take_new){ - status = create_snapshot_for_tag ("monthly",now); + status = create_snapshot_for_tags(Tags.Monthly, now); if(!status){ log_error(_("Monthly snapshot failed!")); return false; @@ -1339,9 +1336,14 @@ public class Main : GLib.Object{ return status; } - private bool create_snapshot_for_tag(string tag, DateTime dt_created){ + private bool create_snapshot_for_tags(Tags tags, DateTime dt_created){ log_debug("Main: backup_and_rotate()"); + + if (tags == 0) { + // at least on tag need to be set + tags |= Tags.OnDemand; + } // save start time var dt_begin = new DateTime.now_local(); @@ -1355,20 +1357,14 @@ public class Main : GLib.Object{ DateTime dt_filter = null; - if (tag != "ondemand"){ - switch(tag){ - case "boot": - dt_filter = dt_sys_boot; - break; - case "hourly": - case "daily": - case "weekly": - case "monthly": - dt_filter = now.add_hours(-1).add_seconds(59); - break; - default: - log_error(_("Unknown snapshot type") + ": %s".printf(tag)); - return false; + // if this is not a "ondemand" snapshot, we can check if there is another recent snapshot + // that we can repurpose/retag + if (!(Tags.OnDemand in tags) && App.app_mode != "ondemand"){ + if(Tags.Boot in tags) { + dt_filter = dt_sys_boot; + } + else if(tags.contains_any(Tags.Timed)) { + dt_filter = now.add_hours(-1).add_seconds(59); } // find a recent backup that can be used @@ -1383,9 +1379,9 @@ public class Main : GLib.Object{ if (backup_to_rotate != null){ // tag the backup - backup_to_rotate.add_tag(tag); + backup_to_rotate.add_tag(tags); - var message = _("Tagged snapshot") + " '%s': %s".printf(backup_to_rotate.name, tag); + var message = _("Tagged snapshot") + " '%s': %s".printf(backup_to_rotate.name, tags.as_short_list()); log_msg(message); return true; @@ -1408,7 +1404,7 @@ public class Main : GLib.Object{ Snapshot new_snapshot = null; if (btrfs_mode) { - new_snapshot = create_snapshot_with_btrfs(tag, dt_created); + new_snapshot = create_snapshot_with_btrfs(tags, dt_created); } else if (first_snapshot_size > 0 && repo.device.free_bytes < first_snapshot_size) @@ -1428,7 +1424,7 @@ public class Main : GLib.Object{ if (enough) { - new_snapshot = create_snapshot_with_rsync(tag, dt_created); + new_snapshot = create_snapshot_with_rsync(tags, dt_created); } else { @@ -1439,7 +1435,7 @@ public class Main : GLib.Object{ { // this is the initial snapshot (which means a size check has already been made) or the target // drive's available space is more than the the original (full) snapshot's size. - new_snapshot = create_snapshot_with_rsync(tag, dt_created); + new_snapshot = create_snapshot_with_rsync(tags, dt_created); } // finish ------------------------------ @@ -1459,7 +1455,7 @@ public class Main : GLib.Object{ OSDNotify.notify_send("TimeShift", message, 10000, "low"); if (new_snapshot != null){ - message = _("Tagged snapshot") + " '%s': %s".printf(new_snapshot.name, tag); + message = _("Tagged snapshot") + " '%s': %s".printf(new_snapshot.name, tags.as_short_list()); log_msg(message); } @@ -1517,7 +1513,7 @@ public class Main : GLib.Object{ // get latest snapshot to link if not set ------- if (snapshot_to_link == null){ - snapshot_to_link = repo.get_latest_snapshot("", sys_uuid); + snapshot_to_link = repo.get_latest_snapshot(0, sys_uuid); } string link_from_path = ""; @@ -1566,7 +1562,7 @@ public class Main : GLib.Object{ return total_size; } - private Snapshot? create_snapshot_with_rsync(string tag, DateTime dt_created){ + private Snapshot? create_snapshot_with_rsync(Tags tags, DateTime dt_created){ log_msg(string.nfill(78, '-')); if (first_snapshot_size == 0){ @@ -1630,7 +1626,7 @@ public class Main : GLib.Object{ // get latest snapshot to link if not set ------- if (snapshot_to_link == null){ - snapshot_to_link = repo.get_latest_snapshot("", sys_uuid); + snapshot_to_link = repo.get_latest_snapshot(0, sys_uuid); } string link_from_path = ""; @@ -1693,8 +1689,6 @@ public class Main : GLib.Object{ log_error(_("Failed to create new snapshot")); return null; } - - string initial_tags = (tag == "ondemand") ? "" : tag; // write control file // this step is redundant - just in case if app crashes while parsing log file in next step @@ -1713,9 +1707,7 @@ public class Main : GLib.Object{ // write control file (final - with file count after parsing log) var snapshot = Snapshot.write_control_file( snapshot_path, dt_created, sys_uuid, current_distro.full_name(), - initial_tags, cmd_comments, fcount, false, false, repo); - - set_tags(snapshot); // set_tags() will update the control file + tags, cmd_comments, fcount, false, false, repo); // Perform any post-backup actions this.run_post_backup_hooks(snapshot_path); @@ -1723,7 +1715,7 @@ public class Main : GLib.Object{ return snapshot; } - private Snapshot? create_snapshot_with_btrfs(string tag, DateTime dt_created){ + private Snapshot? create_snapshot_with_btrfs(Tags tags, DateTime dt_created){ log_msg(_("Creating new backup...") + "(BTRFS)"); @@ -1794,21 +1786,17 @@ public class Main : GLib.Object{ //log_msg(_("Writing control file...")); snapshot_path = path_combine(repo.mount_paths["@"], "timeshift-btrfs/snapshots/%s".printf(snapshot_name)); - - string initial_tags = (tag == "ondemand") ? "" : tag; // write control file var snapshot = Snapshot.write_control_file( snapshot_path, dt_created, sys_uuid, current_distro.full_name(), - initial_tags, cmd_comments, 0, true, false, repo); + tags, cmd_comments, 0, true, false, repo); // write subvolume info foreach(var subvol in sys_subvolumes.values){ snapshot.subvolumes.set(subvol.name, subvol); } snapshot.update_control_file(); // save subvolume info - - set_tags(snapshot); // set_tags() will update the control file // Perform any post-backup actions this.run_post_backup_hooks(snapshot_path); @@ -1832,55 +1820,16 @@ public class Main : GLib.Object{ } } - private void set_tags(Snapshot snapshot){ - - // add tags passed on commandline for both --check and --create - - foreach(string tag in cmd_tags.split(",")){ - switch(tag.strip().up()){ - case "O": - snapshot.add_tag("ondemand"); - break; - case "B": - snapshot.add_tag("boot"); - break; - case "H": - snapshot.add_tag("hourly"); - break; - case "D": - snapshot.add_tag("daily"); - break; - case "W": - snapshot.add_tag("weekly"); - break; - case "M": - snapshot.add_tag("monthly"); - break; - } - } - - // add tag as ondemand if no other tag is specified - - if (snapshot.tags.size == 0){ - snapshot.add_tag("ondemand"); - } - } - - public void validate_cmd_tags(){ - foreach(string tag in cmd_tags.split(",")){ - switch(tag.strip().up()){ - case "O": - case "B": - case "H": - case "D": - case "W": - case "M": - break; - default: + public void parse_cmd_tags(string input_tags){ + App.cmd_tags = 0; + foreach(string tag in input_tags.split(",")){ + Tags? parsed_tag = Tags.parse(tag); + if(parsed_tag == null) { log_error(_("Unknown value specified for option --tags") + " (%s).".printf(tag)); log_error(_("Expected values: O, B, H, D, W, M")); exit_app(1); - break; + } else { + App.cmd_tags |= parsed_tag; } } } @@ -3320,7 +3269,7 @@ public class Main : GLib.Object{ var snap = Snapshot.write_control_file( snapshot_path, dt_created, repo.device.uuid, LinuxDistro.get_dist_info(path_combine(snapshot_path,"@")).full_name(), - "ondemand", "", 0, true, false, repo); + Tags.OnDemand, "", 0, true, false, repo); snap.description = "Before restoring '%s'".printf(snapshot_to_restore.date_formatted); snap.live = true; @@ -3370,11 +3319,11 @@ public class Main : GLib.Object{ config.set_string_member("include_btrfs_home_for_restore", include_btrfs_home_for_restore.to_string()); config.set_string_member("stop_cron_emails", stop_cron_emails.to_string()); - config.set_string_member("schedule_monthly", schedule_monthly.to_string()); - config.set_string_member("schedule_weekly", schedule_weekly.to_string()); - config.set_string_member("schedule_daily", schedule_daily.to_string()); - config.set_string_member("schedule_hourly", schedule_hourly.to_string()); - config.set_string_member("schedule_boot", schedule_boot.to_string()); + config.set_string_member("schedule_monthly", (Tags.Monthly in App.schedule).to_string()); + config.set_string_member("schedule_weekly", (Tags.Weekly in App.schedule).to_string()); + config.set_string_member("schedule_daily", (Tags.Daily in App.schedule).to_string()); + config.set_string_member("schedule_hourly", (Tags.Hourly in App.schedule).to_string()); + config.set_string_member("schedule_boot", (Tags.Boot in App.schedule).to_string()); config.set_string_member("count_monthly", count_monthly.to_string()); config.set_string_member("count_weekly", count_weekly.to_string()); @@ -3493,11 +3442,11 @@ public class Main : GLib.Object{ backup_uuid = json_get_string(config,"backup_device_uuid", backup_uuid); backup_parent_uuid = json_get_string(config,"parent_device_uuid", backup_parent_uuid); - this.schedule_monthly = json_get_bool(config,"schedule_monthly",schedule_monthly); - this.schedule_weekly = json_get_bool(config,"schedule_weekly",schedule_weekly); - this.schedule_daily = json_get_bool(config,"schedule_daily",schedule_daily); - this.schedule_hourly = json_get_bool(config,"schedule_hourly",schedule_hourly); - this.schedule_boot = json_get_bool(config,"schedule_boot",schedule_boot); + Tags.set_value(ref this.schedule, Tags.Monthly, json_get_bool(config,"schedule_monthly", Tags.Monthly in schedule)); + Tags.set_value(ref this.schedule, Tags.Weekly, json_get_bool(config,"schedule_weekly", Tags.Weekly in schedule)); + Tags.set_value(ref this.schedule, Tags.Daily, json_get_bool(config,"schedule_daily", Tags.Daily in schedule)); + Tags.set_value(ref this.schedule, Tags.Hourly, json_get_bool(config,"schedule_hourly", Tags.Hourly in schedule)); + Tags.set_value(ref this.schedule, Tags.Boot, json_get_bool(config,"schedule_boot", Tags.Boot in schedule)); this.count_monthly = json_get_int(config,"count_monthly",count_monthly); this.count_weekly = json_get_int(config,"count_weekly",count_weekly); @@ -4393,7 +4342,7 @@ public class Main : GLib.Object{ CronTab.add_script_file("timeshift-hourly", "d", "0 * * * * root timeshift --check --scripted", stop_cron_emails); //boot - if (schedule_boot){ + if (Tags.Boot in App.schedule){ CronTab.add_script_file("timeshift-boot", "d", "@reboot root sleep 10m && timeshift --create --scripted --tags B", stop_cron_emails); } else{ diff --git a/src/Core/Snapshot.vala b/src/Core/Snapshot.vala index 4898925c..bdd7e557 100644 --- a/src/Core/Snapshot.vala +++ b/src/Core/Snapshot.vala @@ -41,7 +41,7 @@ public class Snapshot : GLib.Object{ public string app_version = ""; public string description = ""; public int64 file_count = 0; - public Gee.ArrayList tags; + public Tags tags; public Gee.ArrayList exclude_list; public Gee.HashMap subvolumes; public Gee.ArrayList fstab_list; @@ -73,7 +73,7 @@ public class Snapshot : GLib.Object{ repo = _repo; date = new DateTime.from_unix_utc(0); - tags = new Gee.ArrayList(); + tags = 0; exclude_list = new Gee.ArrayList(); fstab_list = new Gee.ArrayList(); delete_file_task = new DeleteFileTask(); @@ -138,53 +138,34 @@ public class Snapshot : GLib.Object{ public string taglist{ owned get{ - string str = ""; - foreach(string tag in tags){ - str += " " + tag; - } - return str.strip(); + return this.tags.as_config_string(); } set{ - tags.clear(); + this.tags = 0; foreach(string tag in value.split(" ")){ - if (!tags.contains(tag.strip())){ - tags.add(tag.strip()); - } + this.tags |= Tags.parse(tag); } } } - public string taglist_short{ - owned get{ - string str = ""; - foreach(string tag in tags){ - str += " " + tag.replace("ondemand","O").replace("boot","B").replace("hourly","H").replace("daily","D").replace("weekly","W").replace("monthly","M"); + /* if at_least_on_demand is true and no tags are set, set Tags.OnDemand */ + public void add_tag(Tags tag, bool at_least_on_demand = false){ + if (!(tag in this.tags) || at_least_on_demand){ + this.tags |= tag; + if (at_least_on_demand && this.tags == 0){ + this.tags |= Tags.OnDemand; } - return str.strip(); - } - } - - public void add_tag(string tag){ - - if (!tags.contains(tag.strip())){ - tags.add(tag.strip()); update_control_file(); } } - public void remove_tag(string tag){ - - if (tags.contains(tag.strip())){ - tags.remove(tag.strip()); + public void remove_tag(Tags tag){ + if (tag in this.tags){ + this.tags ^= tag; update_control_file(); } } - public bool has_tag(string tag){ - - return tags.contains(tag.strip()); - } - // control files public void read_control_file(){ @@ -395,7 +376,7 @@ public class Snapshot : GLib.Object{ public static Snapshot write_control_file( string snapshot_path, DateTime dt_created, string root_uuid, string distro_full_name, - string tag, string comments, int64 item_count, bool is_btrfs, bool is_live, SnapshotRepo repo, bool silent = false){ + Tags tags, string comments, int64 item_count, bool is_btrfs, bool is_live, SnapshotRepo repo, bool silent = false){ var ctl_path = snapshot_path + "/info.json"; var config = new Json.Object(); @@ -405,7 +386,7 @@ public class Snapshot : GLib.Object{ config.set_string_member("sys-distro", distro_full_name); config.set_string_member("app-version", AppVersion); config.set_string_member("file_count", item_count.to_string()); - config.set_string_member("tags", tag); + config.set_string_member("tags", tags.as_config_string()); config.set_string_member("comments", comments); config.set_string_member("live", is_live.to_string()); config.set_string_member("type", (is_btrfs ? "btrfs" : "rsync")); diff --git a/src/Core/SnapshotRepo.vala b/src/Core/SnapshotRepo.vala index 5be72936..db550ab5 100644 --- a/src/Core/SnapshotRepo.vala +++ b/src/Core/SnapshotRepo.vala @@ -373,30 +373,27 @@ public class SnapshotRepo : GLib.Object{ // get tagged snapshots ---------------------------------- - public Gee.ArrayList get_snapshots_by_tag(string tag = ""){ + public Gee.ArrayList get_snapshots_by_tag(Tags tag = 0){ - var list = new Gee.ArrayList(); + var list = new Gee.ArrayList(); foreach(Snapshot bak in snapshots){ - if (bak.valid && (tag.length == 0) || bak.has_tag(tag)){ + if (bak.valid && (tag == 0) || (tag in bak.tags)){ list.add(bak); } } - list.sort((a,b) => { - Snapshot t1 = (Snapshot) a; - Snapshot t2 = (Snapshot) b; - return (t1.date.compare(t2.date)); - }); + list.sort((a, b) => + a.date.compare(b.date) + ); return list; } - public Snapshot? get_latest_snapshot(string tag, string sys_uuid){ - - var list = get_snapshots_by_tag(tag); + public Snapshot? get_latest_snapshot(Tags tag, string sys_uuid){ + Gee.ArrayList list = get_snapshots_by_tag(tag); for(int i = list.size - 1; i >= 0; i--){ - var bak = list[i]; + Snapshot bak = list[i]; if (bak.sys_uuid == sys_uuid){ return bak; } @@ -405,12 +402,11 @@ public class SnapshotRepo : GLib.Object{ return null; } - public Snapshot? get_oldest_snapshot(string tag, string sys_uuid){ - - var list = get_snapshots_by_tag(tag); + public Snapshot? get_oldest_snapshot(Tags tag, string sys_uuid){ + Gee.ArrayList list = get_snapshots_by_tag(tag); for(int i = 0; i < list.size; i++){ - var bak = list[i]; + Snapshot bak = list[i]; if (bak.sys_uuid == sys_uuid){ return bak; } @@ -622,45 +618,46 @@ public class SnapshotRepo : GLib.Object{ last_snapshot_failed_space = false; DateTime now = new DateTime.now_local(); DateTime dt_limit; - int count_limit; // remove tags from older backups - boot --------------- - var list = get_snapshots_by_tag("boot"); + Gee.ArrayList list = get_snapshots_by_tag(Tags.Boot); if (list.size > App.count_boot){ log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf("boot")); while (list.size > App.count_boot){ - list[0].remove_tag("boot"); + list[0].remove_tag(Tags.Boot); log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf("boot")); - list = get_snapshots_by_tag("boot"); + list = get_snapshots_by_tag(Tags.Boot); } } // remove tags from older backups - hourly, daily, weekly, monthly --------- - string[] levels = { "hourly","daily","weekly","monthly" }; + Tags levels = Tags.Hourly | Tags.Daily | Tags.Weekly | Tags.Monthly; - foreach(string level in levels){ + foreach(Tags? level in levels){ list = get_snapshots_by_tag(level); if (list.size == 0) { continue; } + int count_limit; + switch (level){ - case "hourly": + case Tags.Hourly: dt_limit = now.add_hours(-1 * App.count_hourly); count_limit = App.count_hourly; break; - case "daily": + case Tags.Daily: dt_limit = now.add_days(-1 * App.count_daily); count_limit = App.count_daily; break; - case "weekly": + case Tags.Weekly: dt_limit = now.add_weeks(-1 * App.count_weekly); count_limit = App.count_weekly; break; - case "monthly": + case Tags.Monthly: dt_limit = now.add_months(-1 * App.count_monthly); count_limit = App.count_monthly; break; @@ -670,12 +667,11 @@ public class SnapshotRepo : GLib.Object{ break; } - if (list.size > count_limit){ + int snaps_count = list.size; + if (snaps_count > count_limit){ - log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf(level)); + log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf(level.name())); - int snaps_count = list.size; - foreach(var snap in list){ if (snap.description.strip().length > 0){ continue; } // don't delete snapshots with comments @@ -685,7 +681,7 @@ public class SnapshotRepo : GLib.Object{ snap.remove_tag(level); snaps_count--; - log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf(level)); + log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf(level.name())); } } } @@ -697,7 +693,7 @@ public class SnapshotRepo : GLib.Object{ count = 0; foreach(var bak in snapshots){ if (bak.date.compare(now.add_days(-1 * App.retain_snapshots_max_days)) < 0){ - if (!bak.has_tag("ondemand")){ + if (!(Tags.OnDemand in bak.tags)){ if (show_msg){ log_msg(_("Removing backups older than") + " %d ".printf( @@ -728,7 +724,7 @@ public class SnapshotRepo : GLib.Object{ load_snapshots(); if (snapshots.size > 0){ - if (!snapshots[0].has_tag("ondemand")){ + if (!(Tags.OnDemand in snapshots[0].tags)){ if (show_msg){ log_msg(_("Free space is less than") + " %lld GB".printf( @@ -760,8 +756,9 @@ public class SnapshotRepo : GLib.Object{ bool show_msg = true; + // delete untagged snapshots foreach(Snapshot bak in snapshots){ - if (bak.tags.size == 0){ + if (bak.tags == 0){ if (show_msg){ log_msg("%s (%s):".printf(_("Removing snapshots"), _("un-tagged"))); @@ -901,17 +898,13 @@ public class SnapshotRepo : GLib.Object{ // symlinks ---------------------------------------- public void create_symlinks(){ - cleanup_symlink_dir("boot"); - cleanup_symlink_dir("hourly"); - cleanup_symlink_dir("daily"); - cleanup_symlink_dir("weekly"); - cleanup_symlink_dir("monthly"); - cleanup_symlink_dir("ondemand"); - + foreach(Tags? tag in Tags.All) { + cleanup_symlink_dir(tag.name()); + } foreach(Snapshot bak in snapshots){ - foreach(string tag in bak.tags) { - string linkTarget = "%s-%s/%s".printf(snapshots_path, tag, bak.name); + foreach(Tags? tag in bak.tags) { + string linkTarget = "%s-%s/%s".printf(snapshots_path, tag.name(), bak.name); string linkValue = "../snapshots/" + bak.name; try { File f = File.new_for_path(linkTarget); diff --git a/src/Core/Tags.vala b/src/Core/Tags.vala new file mode 100644 index 00000000..d7fbb4e1 --- /dev/null +++ b/src/Core/Tags.vala @@ -0,0 +1,141 @@ + +[Flags] +public enum Tags { + + Boot, + Hourly, + Daily, + Weekly, + Monthly, + OnDemand, + + First = Boot, + Last = OnDemand, + Timed = Boot | Hourly | Daily | Weekly | Monthly, + All = Timed | OnDemand; + + /** + A lowercase short name + */ + public string name() { + switch(this) { + case Boot: return "boot"; + case Hourly: return "hourly"; + case Daily: return "daily"; + case Weekly: return "weekly"; + case Monthly: return "monthly"; + case OnDemand: return "ondemand"; + } + + assert_not_reached(); + } + + /** + A lowercase short name + */ + public string localized_name() { + switch(this) { + case Boot: return _("Boot"); + case Hourly: return _("Hourly"); + case Daily: return _("Daily"); + case Weekly: return _("Weekly"); + case Monthly: return _("Monthly"); + case OnDemand: return _("Ondemand"); + } + + assert_not_reached(); + } + + /** + A single uppercase letter + */ + public char letter() { + switch(this) { + case Boot: return 'B'; + case Hourly: return 'H'; + case Daily: return 'D'; + case Weekly: return 'W'; + case Monthly: return 'M'; + case OnDemand: return 'O'; + } + + assert_not_reached(); + } + + public static Tags? parse(string input) { + switch(input.strip().down().get(0)) { + case 'b': return Boot; + case 'h': return Hourly; + case 'd': return Daily; + case 'w': return Weekly; + case 'm': return Monthly; + case 'o': return OnDemand; + } + return null; + } + + public static void set_value(ref Tags tags, Tags value, bool setit) { + if(setit) { + tags |= value; + } else { + tags &= ~ value; + } + } + + // does this contain any of tags? + // not the same as `(tags in this)` ! + public bool contains_any(Tags tags) { + return (this & tags) > 0; + } + + // convert a Tags object into something that can be stored in the config. example: "boot daily weekly" + public string as_config_string() { + string str = ""; + foreach(Tags? tag in this) { + str += " " + tag.name(); + } + return str.strip(); + } + + // returns something like "B D W" + public string as_short_list() { + string str = ""; + foreach(Tags? tag in this) { + str += " " + tag.letter().to_string(); + } + return str.strip(); + } + + public TagsIterator iterator() { + return new TagsIterator(this); + } +} + +public class TagsIterator { + private Tags value; + private Tags pos = Tags.First; + + public TagsIterator(Tags value) { + this.value = value; + } + + public bool next() { + for(Tags t = this.pos; t <= Tags.Last; t <<= 1) { + if(t in this.value) { + return true; + } + } + return false; + } + + public Tags? next_value() { + for(; this.pos <= Tags.Last; this.pos <<= 1) { + if(this.pos in this.value) { + Tags copy = this.pos; + this.pos <<= 1; + return copy; + } + } + return null; + } +} diff --git a/src/Gtk/MainWindow.vala b/src/Gtk/MainWindow.vala index ee9f87d6..15b4cee6 100644 --- a/src/Gtk/MainWindow.vala +++ b/src/Gtk/MainWindow.vala @@ -956,9 +956,9 @@ class MainWindow : Gtk.Window{ if (App.repo.has_snapshots()){ string sys_uuid = (App.sys_root == null) ? "" : App.sys_root.uuid; - var last_snapshot = App.repo.get_latest_snapshot("", sys_uuid); + var last_snapshot = App.repo.get_latest_snapshot(0, sys_uuid); last_snapshot_date = (last_snapshot == null) ? null : last_snapshot.date; - var oldest_snapshot = App.repo.get_oldest_snapshot("", sys_uuid); + var oldest_snapshot = App.repo.get_oldest_snapshot(0, sys_uuid); oldest_snapshot_date = (oldest_snapshot == null) ? null : oldest_snapshot.date; } diff --git a/src/Gtk/ScheduleBox.vala b/src/Gtk/ScheduleBox.vala index 39f33fcc..ea013591 100644 --- a/src/Gtk/ScheduleBox.vala +++ b/src/Gtk/ScheduleBox.vala @@ -61,15 +61,7 @@ class ScheduleBox : Gtk.Box{ // monthly - add_schedule_option(this, _("Monthly"), _("Create one per month"), out chk_m, out spin_m); - - chk_m.active = App.schedule_monthly; - chk_m.toggled.connect(()=>{ - App.schedule_monthly = chk_m.active; - //spin_m.sensitive = chk_m.active; - chk_cron.sensitive = App.scheduled; - update_statusbar(); - }); + add_schedule_option(this, Tags.Monthly, _("Create one per month"), out chk_m, out spin_m); spin_m.set_value(App.count_monthly); //spin_m.sensitive = chk_m.active; @@ -79,15 +71,7 @@ class ScheduleBox : Gtk.Box{ // weekly - add_schedule_option(this, _("Weekly"), _("Create one per week"), out chk_w, out spin_w); - - chk_w.active = App.schedule_weekly; - chk_w.toggled.connect(()=>{ - App.schedule_weekly = chk_w.active; - //spin_w.sensitive = chk_w.active; - chk_cron.sensitive = App.scheduled; - update_statusbar(); - }); + add_schedule_option(this, Tags.Weekly, _("Create one per week"), out chk_w, out spin_w); spin_w.set_value(App.count_weekly); //spin_w.sensitive = chk_w.active; @@ -97,15 +81,7 @@ class ScheduleBox : Gtk.Box{ // daily - add_schedule_option(this, _("Daily"), _("Create one per day"), out chk_d, out spin_d); - - chk_d.active = App.schedule_daily; - chk_d.toggled.connect(()=>{ - App.schedule_daily = chk_d.active; - //spin_d.sensitive = chk_d.active; - chk_cron.sensitive = App.scheduled; - update_statusbar(); - }); + add_schedule_option(this, Tags.Daily, _("Create one per day"), out chk_d, out spin_d); spin_d.set_value(App.count_daily); //spin_d.sensitive = chk_d.active; @@ -115,15 +91,7 @@ class ScheduleBox : Gtk.Box{ // hourly - add_schedule_option(this, _("Hourly"), _("Create one per hour"), out chk_h, out spin_h); - - chk_h.active = App.schedule_hourly; - chk_h.toggled.connect(()=>{ - App.schedule_hourly = chk_h.active; - //spin_h.sensitive = chk_h.active; - chk_cron.sensitive = App.scheduled; - update_statusbar(); - }); + add_schedule_option(this, Tags.Hourly, _("Create one per hour"), out chk_h, out spin_h); spin_h.set_value(App.count_hourly); //spin_h.sensitive = chk_h.active; @@ -133,15 +101,7 @@ class ScheduleBox : Gtk.Box{ // boot - add_schedule_option(this, _("Boot"), _("Create one per boot"), out chk_b, out spin_b); - - chk_b.active = App.schedule_boot; - chk_b.toggled.connect(()=>{ - App.schedule_boot = chk_b.active; - //spin_b.sensitive = chk_b.active; - chk_cron.sensitive = App.scheduled; - update_statusbar(); - }); + add_schedule_option(this, Tags.Boot, _("Create one per boot"), out chk_b, out spin_b); spin_b.set_value(App.count_boot); //spin_b.sensitive = chk_b.active; @@ -260,7 +220,7 @@ class ScheduleBox : Gtk.Box{ } private void add_schedule_option( - Gtk.Box box, string period, string period_desc, + Gtk.Box box, Tags period, string period_desc, out Gtk.CheckButton chk, out Gtk.SpinButton spin){ var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); @@ -275,9 +235,21 @@ class ScheduleBox : Gtk.Box{ sg_count = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); } - var txt = "%s".printf(period); - chk = add_checkbox(hbox, txt); + var txt = "%s".printf(period.localized_name()); + Gtk.CheckButton check = add_checkbox(hbox, txt); + chk = check; sg_title.add_widget(chk); + + check.active = period in App.schedule; + check.toggled.connect(()=>{ + if(check.active) { + App.schedule |= period; // add + } else { + App.schedule &= ~ period; // remove + } + chk_cron.sensitive = App.scheduled; + update_statusbar(); + }); var tt = _("Number of snapshots to keep.\nOlder snapshots will be removed once this limit is exceeded."); var label = add_label(hbox, " " + _("Keep")); @@ -296,8 +268,7 @@ class ScheduleBox : Gtk.Box{ public void update_statusbar(){ - if (App.schedule_monthly || App.schedule_weekly || App.schedule_daily - || App.schedule_hourly || App.schedule_boot){ + if (App.scheduled){ img_shield.surface = IconManager.lookup_surface(IconManager.SHIELD_HIGH, IconManager.SHIELD_ICON_SIZE, img_shield.scale_factor); diff --git a/src/Gtk/SetupWizardWindow.vala b/src/Gtk/SetupWizardWindow.vala index 6056c087..9cf50ffb 100644 --- a/src/Gtk/SetupWizardWindow.vala +++ b/src/Gtk/SetupWizardWindow.vala @@ -78,12 +78,8 @@ class SetupWizardWindow : Gtk.Window{ this.resize(def_width, def_height); if (App.first_run && !schedule_accepted){ - App.schedule_boot = false; - App.schedule_hourly = false; - App.schedule_daily = true; // set - log_debug("Setting schedule_daily for first run"); - App.schedule_weekly = false; - App.schedule_monthly = false; + App.schedule = Tags.Daily; + log_debug("Setting schedule Daily for first run"); } // add notebook @@ -150,11 +146,7 @@ class SetupWizardWindow : Gtk.Window{ private bool on_delete_event(Gdk.EventAny event){ if (App.first_run && !schedule_accepted){ - App.schedule_boot = false; - App.schedule_hourly = false; - App.schedule_daily = false; // unset - App.schedule_weekly = false; - App.schedule_monthly = false; + App.schedule = 0; } save_changes(); diff --git a/src/Gtk/SnapshotListBox.vala b/src/Gtk/SnapshotListBox.vala index 968aa987..cb1267e7 100644 --- a/src/Gtk/SnapshotListBox.vala +++ b/src/Gtk/SnapshotListBox.vala @@ -408,7 +408,7 @@ class SnapshotListBox : Gtk.Box{ model.get (iter, 0, out bak, -1); var ctxt = (cell as Gtk.CellRendererText); - ctxt.text = bak.taglist_short; + ctxt.text = bak.tags.as_short_list(); ctxt.sensitive = !bak.marked_for_deletion; if (bak.live){ diff --git a/src/meson.build b/src/meson.build index aa825824..24205512 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,7 @@ sources_core = files([ 'Core/Snapshot.vala', 'Core/SnapshotRepo.vala', 'Core/Subvolume.vala', + 'Core/Tags.vala', ]) sources_utility = files([