]>
Commit | Line | Data |
---|---|---|
dc188491 DM |
1 | use std::collections::{HashMap, HashSet}; |
2 | use std::path::PathBuf; | |
3 | ||
2f02e431 | 4 | use anyhow::{Error}; |
dc46aa9a | 5 | |
89725197 | 6 | use pbs_api_types::PruneOptions; |
2f02e431 | 7 | |
6a7be83e | 8 | use super::BackupInfo; |
dc188491 | 9 | |
02db7267 DC |
10 | #[derive(Clone, Copy, PartialEq, Eq)] |
11 | pub enum PruneMark { Protected, Keep, KeepPartial, Remove } | |
12 | ||
13 | impl PruneMark { | |
d4e9d547 DC |
14 | pub fn keep(self) -> bool { |
15 | self != PruneMark::Remove | |
02db7267 DC |
16 | } |
17 | ||
d4e9d547 DC |
18 | pub fn protected(self) -> bool { |
19 | self == PruneMark::Protected | |
02db7267 DC |
20 | } |
21 | } | |
22 | ||
23 | impl std::fmt::Display for PruneMark { | |
24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
342ed4ae | 25 | f.write_str(match self { |
02db7267 DC |
26 | PruneMark::Protected => "protected", |
27 | PruneMark::Keep => "keep", | |
28 | PruneMark::KeepPartial => "keep-partial", | |
29 | PruneMark::Remove => "remove", | |
342ed4ae | 30 | }) |
02db7267 DC |
31 | } |
32 | } | |
dc188491 | 33 | |
6a7be83e | 34 | fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> ( |
dc188491 | 35 | mark: &mut HashMap<PathBuf, PruneMark>, |
09faa9ee | 36 | list: &[BackupInfo], |
dc188491 DM |
37 | keep: usize, |
38 | select_id: F, | |
6a7be83e | 39 | ) -> Result<(), Error> { |
dc188491 DM |
40 | |
41 | let mut include_hash = HashSet::new(); | |
42 | ||
43 | let mut already_included = HashSet::new(); | |
44 | for info in list { | |
45 | let backup_id = info.backup_dir.relative_path(); | |
46 | if let Some(PruneMark::Keep) = mark.get(&backup_id) { | |
9a37bd6c | 47 | let sel_id: String = select_id(info)?; |
dc188491 DM |
48 | already_included.insert(sel_id); |
49 | } | |
50 | } | |
51 | ||
52 | for info in list { | |
53 | let backup_id = info.backup_dir.relative_path(); | |
3984a5fd | 54 | if mark.get(&backup_id).is_some() { continue; } |
db4b4692 DC |
55 | if info.protected { |
56 | mark.insert(backup_id, PruneMark::Protected); | |
57 | continue; | |
58 | } | |
9a37bd6c | 59 | let sel_id: String = select_id(info)?; |
dc188491 DM |
60 | |
61 | if already_included.contains(&sel_id) { continue; } | |
62 | ||
63 | if !include_hash.contains(&sel_id) { | |
64 | if include_hash.len() >= keep { break; } | |
65 | include_hash.insert(sel_id); | |
66 | mark.insert(backup_id, PruneMark::Keep); | |
67 | } else { | |
68 | mark.insert(backup_id, PruneMark::Remove); | |
69 | } | |
70 | } | |
6a7be83e DM |
71 | |
72 | Ok(()) | |
dc188491 DM |
73 | } |
74 | ||
a8c8366c DM |
75 | fn remove_incomplete_snapshots( |
76 | mark: &mut HashMap<PathBuf, PruneMark>, | |
09faa9ee | 77 | list: &[BackupInfo], |
a8c8366c | 78 | ) { |
dc188491 | 79 | |
dc188491 DM |
80 | let mut keep_unfinished = true; |
81 | for info in list.iter() { | |
82 | // backup is considered unfinished if there is no manifest | |
c9756b40 | 83 | if info.is_finished() { |
dc188491 DM |
84 | // There is a new finished backup, so there is no need |
85 | // to keep older unfinished backups. | |
86 | keep_unfinished = false; | |
87 | } else { | |
88 | let backup_id = info.backup_dir.relative_path(); | |
89 | if keep_unfinished { // keep first unfinished | |
40843436 | 90 | mark.insert(backup_id, PruneMark::KeepPartial); |
dc188491 DM |
91 | } else { |
92 | mark.insert(backup_id, PruneMark::Remove); | |
93 | } | |
94 | keep_unfinished = false; | |
95 | } | |
96 | } | |
a8c8366c DM |
97 | } |
98 | ||
89725197 DM |
99 | pub fn keeps_something(options: &PruneOptions) -> bool { |
100 | let mut keep_something = false; | |
101 | if let Some(count) = options.keep_last { if count > 0 { keep_something = true; } } | |
102 | if let Some(count) = options.keep_hourly { if count > 0 { keep_something = true; } } | |
103 | if let Some(count) = options.keep_daily { if count > 0 { keep_something = true; } } | |
104 | if let Some(count) = options.keep_weekly { if count > 0 { keep_something = true; } } | |
105 | if let Some(count) = options.keep_monthly { if count > 0 { keep_something = true; } } | |
106 | if let Some(count) = options.keep_yearly { if count > 0 { keep_something = true; } } | |
107 | keep_something | |
9b783521 DM |
108 | } |
109 | ||
89725197 DM |
110 | pub fn cli_options_string(options: &PruneOptions) -> String { |
111 | let mut opts = Vec::new(); | |
9b783521 | 112 | |
89725197 DM |
113 | if let Some(count) = options.keep_last { |
114 | if count > 0 { | |
115 | opts.push(format!("--keep-last {}", count)); | |
9b783521 DM |
116 | } |
117 | } | |
89725197 DM |
118 | if let Some(count) = options.keep_hourly { |
119 | if count > 0 { | |
120 | opts.push(format!("--keep-hourly {}", count)); | |
236a396a | 121 | } |
89725197 DM |
122 | } |
123 | if let Some(count) = options.keep_daily { | |
124 | if count > 0 { | |
125 | opts.push(format!("--keep-daily {}", count)); | |
236a396a | 126 | } |
89725197 DM |
127 | } |
128 | if let Some(count) = options.keep_weekly { | |
129 | if count > 0 { | |
130 | opts.push(format!("--keep-weekly {}", count)); | |
236a396a | 131 | } |
89725197 DM |
132 | } |
133 | if let Some(count) = options.keep_monthly { | |
134 | if count > 0 { | |
135 | opts.push(format!("--keep-monthly {}", count)); | |
236a396a | 136 | } |
89725197 DM |
137 | } |
138 | if let Some(count) = options.keep_yearly { | |
139 | if count > 0 { | |
140 | opts.push(format!("--keep-yearly {}", count)); | |
236a396a | 141 | } |
236a396a | 142 | } |
89725197 DM |
143 | |
144 | opts.join(" ") | |
9b783521 DM |
145 | } |
146 | ||
a8c8366c DM |
147 | pub fn compute_prune_info( |
148 | mut list: Vec<BackupInfo>, | |
9b783521 | 149 | options: &PruneOptions, |
02db7267 | 150 | ) -> Result<Vec<(BackupInfo, PruneMark)>, Error> { |
a8c8366c DM |
151 | |
152 | let mut mark = HashMap::new(); | |
153 | ||
154 | BackupInfo::sort_list(&mut list, false); | |
155 | ||
156 | remove_incomplete_snapshots(&mut mark, &list); | |
dc188491 | 157 | |
9b783521 | 158 | if let Some(keep_last) = options.keep_last { |
6a7be83e DM |
159 | mark_selections(&mut mark, &list, keep_last as usize, |info| { |
160 | Ok(info.backup_dir.backup_time_string().to_owned()) | |
161 | })?; | |
dc188491 DM |
162 | } |
163 | ||
6ef1b649 | 164 | use proxmox_time::strftime_local; |
6a7be83e | 165 | |
102d8d41 | 166 | if let Some(keep_hourly) = options.keep_hourly { |
6a7be83e | 167 | mark_selections(&mut mark, &list, keep_hourly as usize, |info| { |
6ef1b649 | 168 | strftime_local("%Y/%m/%d/%H", info.backup_dir.backup_time()).map_err(Error::from) |
6a7be83e | 169 | })?; |
102d8d41 DM |
170 | } |
171 | ||
9b783521 | 172 | if let Some(keep_daily) = options.keep_daily { |
6a7be83e | 173 | mark_selections(&mut mark, &list, keep_daily as usize, |info| { |
6ef1b649 | 174 | strftime_local("%Y/%m/%d", info.backup_dir.backup_time()).map_err(Error::from) |
6a7be83e | 175 | })?; |
dc188491 DM |
176 | } |
177 | ||
9b783521 | 178 | if let Some(keep_weekly) = options.keep_weekly { |
6a7be83e DM |
179 | mark_selections(&mut mark, &list, keep_weekly as usize, |info| { |
180 | // Note: Use iso-week year/week here. This year number | |
181 | // might not match the calendar year number. | |
6ef1b649 | 182 | strftime_local("%G/%V", info.backup_dir.backup_time()).map_err(Error::from) |
6a7be83e | 183 | })?; |
dc188491 DM |
184 | } |
185 | ||
9b783521 | 186 | if let Some(keep_monthly) = options.keep_monthly { |
6a7be83e | 187 | mark_selections(&mut mark, &list, keep_monthly as usize, |info| { |
6ef1b649 | 188 | strftime_local("%Y/%m", info.backup_dir.backup_time()).map_err(Error::from) |
6a7be83e | 189 | })?; |
dc188491 DM |
190 | } |
191 | ||
9b783521 | 192 | if let Some(keep_yearly) = options.keep_yearly { |
6a7be83e | 193 | mark_selections(&mut mark, &list, keep_yearly as usize, |info| { |
6ef1b649 | 194 | strftime_local("%Y", info.backup_dir.backup_time()).map_err(Error::from) |
6a7be83e | 195 | })?; |
dc188491 DM |
196 | } |
197 | ||
02db7267 | 198 | let prune_info: Vec<(BackupInfo, PruneMark)> = list.into_iter() |
dc188491 DM |
199 | .map(|info| { |
200 | let backup_id = info.backup_dir.relative_path(); | |
02db7267 DC |
201 | let mark = if info.protected { |
202 | PruneMark::Protected | |
203 | } else { | |
d4e9d547 | 204 | mark.get(&backup_id).copied().unwrap_or(PruneMark::Remove) |
dc188491 | 205 | }; |
02db7267 DC |
206 | |
207 | (info, mark) | |
dc188491 DM |
208 | }) |
209 | .collect(); | |
210 | ||
211 | Ok(prune_info) | |
212 | } |