]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/prune.rs
src/backup/prune.rs: add new helper keeps_something()
[proxmox-backup.git] / src / backup / prune.rs
CommitLineData
dc188491
DM
1use failure::*;
2use std::collections::{HashMap, HashSet};
3use std::path::PathBuf;
4
5use chrono::{DateTime, Datelike, Local};
6
7use super::{BackupDir, BackupInfo};
8
40843436 9enum PruneMark { Keep, KeepPartial, Remove }
dc188491
DM
10
11fn mark_selections<F: Fn(DateTime<Local>, &BackupInfo) -> String> (
12 mark: &mut HashMap<PathBuf, PruneMark>,
13 list: &Vec<BackupInfo>,
14 keep: usize,
15 select_id: F,
16) {
17
18 let mut include_hash = HashSet::new();
19
20 let mut already_included = HashSet::new();
21 for info in list {
22 let backup_id = info.backup_dir.relative_path();
23 if let Some(PruneMark::Keep) = mark.get(&backup_id) {
24 let local_time = info.backup_dir.backup_time().with_timezone(&Local);
25 let sel_id: String = select_id(local_time, &info);
26 already_included.insert(sel_id);
27 }
28 }
29
30 for info in list {
31 let backup_id = info.backup_dir.relative_path();
32 if let Some(_) = mark.get(&backup_id) { continue; }
33 let local_time = info.backup_dir.backup_time().with_timezone(&Local);
34 let sel_id: String = select_id(local_time, &info);
35
36 if already_included.contains(&sel_id) { continue; }
37
38 if !include_hash.contains(&sel_id) {
39 if include_hash.len() >= keep { break; }
40 include_hash.insert(sel_id);
41 mark.insert(backup_id, PruneMark::Keep);
42 } else {
43 mark.insert(backup_id, PruneMark::Remove);
44 }
45 }
46}
47
a8c8366c
DM
48fn remove_incomplete_snapshots(
49 mark: &mut HashMap<PathBuf, PruneMark>,
50 list: &Vec<BackupInfo>,
51) {
dc188491 52
dc188491
DM
53 let mut keep_unfinished = true;
54 for info in list.iter() {
55 // backup is considered unfinished if there is no manifest
56 if info.files.iter().any(|name| name == super::MANIFEST_BLOB_NAME) {
57 // There is a new finished backup, so there is no need
58 // to keep older unfinished backups.
59 keep_unfinished = false;
60 } else {
61 let backup_id = info.backup_dir.relative_path();
62 if keep_unfinished { // keep first unfinished
40843436 63 mark.insert(backup_id, PruneMark::KeepPartial);
dc188491
DM
64 } else {
65 mark.insert(backup_id, PruneMark::Remove);
66 }
67 keep_unfinished = false;
68 }
69 }
a8c8366c
DM
70}
71
9b783521
DM
72pub struct PruneOptions {
73 pub keep_last: Option<u64>,
74 pub keep_daily: Option<u64>,
75 pub keep_weekly: Option<u64>,
76 pub keep_monthly: Option<u64>,
77 pub keep_yearly: Option<u64>,
78}
79
80impl PruneOptions {
81
82 pub fn new() -> Self {
83 Self {
84 keep_last: None,
85 keep_daily: None,
86 keep_weekly: None,
87 keep_monthly: None,
88 keep_yearly: None,
89 }
90 }
91
92 pub fn keep_last(mut self, value: Option<u64>) -> Self {
93 self.keep_last = value;
94 self
95 }
96
97 pub fn keep_daily(mut self, value: Option<u64>) -> Self {
98 self.keep_daily = value;
99 self
100 }
101
102 pub fn keep_weekly(mut self, value: Option<u64>) -> Self {
103 self.keep_weekly = value;
104 self
105 }
106
107 pub fn keep_monthly(mut self, value: Option<u64>) -> Self {
108 self.keep_monthly = value;
109 self
110 }
111
112 pub fn keep_yearly(mut self, value: Option<u64>) -> Self {
113 self.keep_yearly = value;
114 self
115 }
9e3f0088
DM
116
117 pub fn keeps_something(&self) -> bool {
118 let mut keep_something = false;
119 if let Some(count) = self.keep_last { if count > 0 { keep_something = true; } }
120 if let Some(count) = self.keep_daily { if count > 0 { keep_something = true; } }
121 if let Some(count) = self.keep_weekly { if count > 0 { keep_something = true; } }
122 if let Some(count) = self.keep_monthly { if count > 0 { keep_something = true; } }
123 if let Some(count) = self.keep_yearly { if count > 0 { keep_something = true; } }
124 keep_something
125 }
9b783521
DM
126}
127
a8c8366c
DM
128pub fn compute_prune_info(
129 mut list: Vec<BackupInfo>,
9b783521 130 options: &PruneOptions,
a8c8366c
DM
131) -> Result<Vec<(BackupInfo, bool)>, Error> {
132
133 let mut mark = HashMap::new();
134
135 BackupInfo::sort_list(&mut list, false);
136
137 remove_incomplete_snapshots(&mut mark, &list);
dc188491 138
9b783521 139 if let Some(keep_last) = options.keep_last {
dc188491
DM
140 mark_selections(&mut mark, &list, keep_last as usize, |_local_time, info| {
141 BackupDir::backup_time_to_string(info.backup_dir.backup_time())
142 });
143 }
144
9b783521 145 if let Some(keep_daily) = options.keep_daily {
dc188491
DM
146 mark_selections(&mut mark, &list, keep_daily as usize, |local_time, _info| {
147 format!("{}/{}/{}", local_time.year(), local_time.month(), local_time.day())
148 });
149 }
150
9b783521 151 if let Some(keep_weekly) = options.keep_weekly {
dc188491
DM
152 mark_selections(&mut mark, &list, keep_weekly as usize, |local_time, _info| {
153 format!("{}/{}", local_time.year(), local_time.iso_week().week())
154 });
155 }
156
9b783521 157 if let Some(keep_monthly) = options.keep_monthly {
dc188491
DM
158 mark_selections(&mut mark, &list, keep_monthly as usize, |local_time, _info| {
159 format!("{}/{}", local_time.year(), local_time.month())
160 });
161 }
162
9b783521 163 if let Some(keep_yearly) = options.keep_yearly {
dc188491
DM
164 mark_selections(&mut mark, &list, keep_yearly as usize, |local_time, _info| {
165 format!("{}/{}", local_time.year(), local_time.year())
166 });
167 }
168
169 let prune_info: Vec<(BackupInfo, bool)> = list.into_iter()
170 .map(|info| {
171 let backup_id = info.backup_dir.relative_path();
172 let keep = match mark.get(&backup_id) {
173 Some(PruneMark::Keep) => true,
40843436
DM
174 Some(PruneMark::KeepPartial) => true,
175 _ => false,
dc188491
DM
176 };
177 (info, keep)
178 })
179 .collect();
180
181 Ok(prune_info)
182}