]> git.proxmox.com Git - proxmox-backup.git/blob - src/backup/prune.rs
bc0a75ad6513effe4d90499f2435a0eb9a38a927
[proxmox-backup.git] / src / backup / prune.rs
1 use anyhow::{Error};
2 use std::collections::{HashMap, HashSet};
3 use std::path::PathBuf;
4
5 use super::BackupInfo;
6
7 enum PruneMark { Keep, KeepPartial, Remove }
8
9 fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
10 mark: &mut HashMap<PathBuf, PruneMark>,
11 list: &Vec<BackupInfo>,
12 keep: usize,
13 select_id: F,
14 ) -> Result<(), Error> {
15
16 let mut include_hash = HashSet::new();
17
18 let mut already_included = HashSet::new();
19 for info in list {
20 let backup_id = info.backup_dir.relative_path();
21 if let Some(PruneMark::Keep) = mark.get(&backup_id) {
22 let sel_id: String = select_id(&info)?;
23 already_included.insert(sel_id);
24 }
25 }
26
27 for info in list {
28 let backup_id = info.backup_dir.relative_path();
29 if let Some(_) = mark.get(&backup_id) { continue; }
30 let sel_id: String = select_id(&info)?;
31
32 if already_included.contains(&sel_id) { continue; }
33
34 if !include_hash.contains(&sel_id) {
35 if include_hash.len() >= keep { break; }
36 include_hash.insert(sel_id);
37 mark.insert(backup_id, PruneMark::Keep);
38 } else {
39 mark.insert(backup_id, PruneMark::Remove);
40 }
41 }
42
43 Ok(())
44 }
45
46 fn remove_incomplete_snapshots(
47 mark: &mut HashMap<PathBuf, PruneMark>,
48 list: &Vec<BackupInfo>,
49 ) {
50
51 let mut keep_unfinished = true;
52 for info in list.iter() {
53 // backup is considered unfinished if there is no manifest
54 if info.is_finished() {
55 // There is a new finished backup, so there is no need
56 // to keep older unfinished backups.
57 keep_unfinished = false;
58 } else {
59 let backup_id = info.backup_dir.relative_path();
60 if keep_unfinished { // keep first unfinished
61 mark.insert(backup_id, PruneMark::KeepPartial);
62 } else {
63 mark.insert(backup_id, PruneMark::Remove);
64 }
65 keep_unfinished = false;
66 }
67 }
68 }
69
70 pub struct PruneOptions {
71 pub keep_last: Option<u64>,
72 pub keep_hourly: Option<u64>,
73 pub keep_daily: Option<u64>,
74 pub keep_weekly: Option<u64>,
75 pub keep_monthly: Option<u64>,
76 pub keep_yearly: Option<u64>,
77 }
78
79 impl PruneOptions {
80
81 pub fn new() -> Self {
82 Self {
83 keep_last: None,
84 keep_hourly: None,
85 keep_daily: None,
86 keep_weekly: None,
87 keep_monthly: None,
88 keep_yearly: None,
89 }
90 }
91
92 pub fn keep_hourly(mut self, value: Option<u64>) -> Self {
93 self.keep_hourly = value;
94 self
95 }
96
97 pub fn keep_last(mut self, value: Option<u64>) -> Self {
98 self.keep_last = value;
99 self
100 }
101
102 pub fn keep_daily(mut self, value: Option<u64>) -> Self {
103 self.keep_daily = value;
104 self
105 }
106
107 pub fn keep_weekly(mut self, value: Option<u64>) -> Self {
108 self.keep_weekly = value;
109 self
110 }
111
112 pub fn keep_monthly(mut self, value: Option<u64>) -> Self {
113 self.keep_monthly = value;
114 self
115 }
116
117 pub fn keep_yearly(mut self, value: Option<u64>) -> Self {
118 self.keep_yearly = value;
119 self
120 }
121
122 pub fn keeps_something(&self) -> bool {
123 let mut keep_something = false;
124 if let Some(count) = self.keep_last { if count > 0 { keep_something = true; } }
125 if let Some(count) = self.keep_hourly { if count > 0 { keep_something = true; } }
126 if let Some(count) = self.keep_daily { if count > 0 { keep_something = true; } }
127 if let Some(count) = self.keep_weekly { if count > 0 { keep_something = true; } }
128 if let Some(count) = self.keep_monthly { if count > 0 { keep_something = true; } }
129 if let Some(count) = self.keep_yearly { if count > 0 { keep_something = true; } }
130 keep_something
131 }
132
133 pub fn cli_options_string(&self) -> String {
134 let mut opts = Vec::new();
135
136 if let Some(count) = self.keep_last {
137 if count > 0 {
138 opts.push(format!("--keep-last {}", count));
139 }
140 }
141 if let Some(count) = self.keep_hourly {
142 if count > 0 {
143 opts.push(format!("--keep-hourly {}", count));
144 }
145 }
146 if let Some(count) = self.keep_daily {
147 if count > 0 {
148 opts.push(format!("--keep-daily {}", count));
149 }
150 }
151 if let Some(count) = self.keep_weekly {
152 if count > 0 {
153 opts.push(format!("--keep-weekly {}", count));
154 }
155 }
156 if let Some(count) = self.keep_monthly {
157 if count > 0 {
158 opts.push(format!("--keep-monthly {}", count));
159 }
160 }
161 if let Some(count) = self.keep_yearly {
162 if count > 0 {
163 opts.push(format!("--keep-yearly {}", count));
164 }
165 }
166
167 opts.join(" ")
168 }
169 }
170
171 pub fn compute_prune_info(
172 mut list: Vec<BackupInfo>,
173 options: &PruneOptions,
174 ) -> Result<Vec<(BackupInfo, bool)>, Error> {
175
176 let mut mark = HashMap::new();
177
178 BackupInfo::sort_list(&mut list, false);
179
180 remove_incomplete_snapshots(&mut mark, &list);
181
182 if let Some(keep_last) = options.keep_last {
183 mark_selections(&mut mark, &list, keep_last as usize, |info| {
184 Ok(info.backup_dir.backup_time_string().to_owned())
185 })?;
186 }
187
188 use proxmox::tools::time::strftime_local;
189
190 if let Some(keep_hourly) = options.keep_hourly {
191 mark_selections(&mut mark, &list, keep_hourly as usize, |info| {
192 strftime_local("%Y/%m/%d/%H", info.backup_dir.backup_time())
193 })?;
194 }
195
196 if let Some(keep_daily) = options.keep_daily {
197 mark_selections(&mut mark, &list, keep_daily as usize, |info| {
198 strftime_local("%Y/%m/%d", info.backup_dir.backup_time())
199 })?;
200 }
201
202 if let Some(keep_weekly) = options.keep_weekly {
203 mark_selections(&mut mark, &list, keep_weekly as usize, |info| {
204 // Note: Use iso-week year/week here. This year number
205 // might not match the calendar year number.
206 strftime_local("%G/%V", info.backup_dir.backup_time())
207 })?;
208 }
209
210 if let Some(keep_monthly) = options.keep_monthly {
211 mark_selections(&mut mark, &list, keep_monthly as usize, |info| {
212 strftime_local("%Y/%m", info.backup_dir.backup_time())
213 })?;
214 }
215
216 if let Some(keep_yearly) = options.keep_yearly {
217 mark_selections(&mut mark, &list, keep_yearly as usize, |info| {
218 strftime_local("%Y", info.backup_dir.backup_time())
219 })?;
220 }
221
222 let prune_info: Vec<(BackupInfo, bool)> = list.into_iter()
223 .map(|info| {
224 let backup_id = info.backup_dir.relative_path();
225 let keep = match mark.get(&backup_id) {
226 Some(PruneMark::Keep) => true,
227 Some(PruneMark::KeepPartial) => true,
228 _ => false,
229 };
230 (info, keep)
231 })
232 .collect();
233
234 Ok(prune_info)
235 }