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