]> git.proxmox.com Git - proxmox-backup.git/blob - src/backup/prune.rs
src/backup/prune.rs: define new struct PruneOptions
[proxmox-backup.git] / src / backup / prune.rs
1 use failure::*;
2 use std::collections::{HashMap, HashSet};
3 use std::path::PathBuf;
4
5 use chrono::{DateTime, Datelike, Local};
6
7 use super::{BackupDir, BackupInfo};
8
9 enum PruneMark { Keep, KeepPartial, Remove }
10
11 fn 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
48 fn remove_incomplete_snapshots(
49 mark: &mut HashMap<PathBuf, PruneMark>,
50 list: &Vec<BackupInfo>,
51 ) {
52
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
63 mark.insert(backup_id, PruneMark::KeepPartial);
64 } else {
65 mark.insert(backup_id, PruneMark::Remove);
66 }
67 keep_unfinished = false;
68 }
69 }
70 }
71
72 pub 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
80 impl 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 }
116 }
117
118 pub fn compute_prune_info(
119 mut list: Vec<BackupInfo>,
120 options: &PruneOptions,
121 ) -> Result<Vec<(BackupInfo, bool)>, Error> {
122
123 let mut mark = HashMap::new();
124
125 BackupInfo::sort_list(&mut list, false);
126
127 remove_incomplete_snapshots(&mut mark, &list);
128
129 if let Some(keep_last) = options.keep_last {
130 mark_selections(&mut mark, &list, keep_last as usize, |_local_time, info| {
131 BackupDir::backup_time_to_string(info.backup_dir.backup_time())
132 });
133 }
134
135 if let Some(keep_daily) = options.keep_daily {
136 mark_selections(&mut mark, &list, keep_daily as usize, |local_time, _info| {
137 format!("{}/{}/{}", local_time.year(), local_time.month(), local_time.day())
138 });
139 }
140
141 if let Some(keep_weekly) = options.keep_weekly {
142 mark_selections(&mut mark, &list, keep_weekly as usize, |local_time, _info| {
143 format!("{}/{}", local_time.year(), local_time.iso_week().week())
144 });
145 }
146
147 if let Some(keep_monthly) = options.keep_monthly {
148 mark_selections(&mut mark, &list, keep_monthly as usize, |local_time, _info| {
149 format!("{}/{}", local_time.year(), local_time.month())
150 });
151 }
152
153 if let Some(keep_yearly) = options.keep_yearly {
154 mark_selections(&mut mark, &list, keep_yearly as usize, |local_time, _info| {
155 format!("{}/{}", local_time.year(), local_time.year())
156 });
157 }
158
159 let prune_info: Vec<(BackupInfo, bool)> = list.into_iter()
160 .map(|info| {
161 let backup_id = info.backup_dir.relative_path();
162 let keep = match mark.get(&backup_id) {
163 Some(PruneMark::Keep) => true,
164 Some(PruneMark::KeepPartial) => true,
165 _ => false,
166 };
167 (info, keep)
168 })
169 .collect();
170
171 Ok(prune_info)
172 }