]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-datastore/src/prune.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / pbs-datastore / src / prune.rs
CommitLineData
dc188491
DM
1use std::collections::{HashMap, HashSet};
2use std::path::PathBuf;
3
2f02e431 4use anyhow::{Error};
dc46aa9a 5
89725197 6use pbs_api_types::PruneOptions;
2f02e431 7
6a7be83e 8use super::BackupInfo;
dc188491 9
02db7267
DC
10#[derive(Clone, Copy, PartialEq, Eq)]
11pub enum PruneMark { Protected, Keep, KeepPartial, Remove }
12
13impl 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
23impl 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 34fn 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
75fn 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
99pub 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
110pub 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
147pub 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}