]>
Commit | Line | Data |
---|---|---|
dc188491 DM |
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 | ||
40843436 | 9 | enum PruneMark { Keep, KeepPartial, Remove } |
dc188491 DM |
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 | ||
a8c8366c DM |
48 | fn remove_incomplete_snapshots( |
49 | mark: &mut HashMap<PathBuf, PruneMark>, | |
50 | list: &Vec<BackupInfo>, | |
51 | ) { | |
dc188491 | 52 | |
dc188491 DM |
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 | |
40843436 | 63 | mark.insert(backup_id, PruneMark::KeepPartial); |
dc188491 DM |
64 | } else { |
65 | mark.insert(backup_id, PruneMark::Remove); | |
66 | } | |
67 | keep_unfinished = false; | |
68 | } | |
69 | } | |
a8c8366c DM |
70 | } |
71 | ||
9b783521 DM |
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 | } | |
9e3f0088 DM |
116 | |
117 | pub fn keeps_something(&self) -> bool { | |
118 | let mut keep_something = false; | |
119 | if let Some(count) = self.keep_last { if count > 0 { keep_something = true; } } | |
120 | if let Some(count) = self.keep_daily { if count > 0 { keep_something = true; } } | |
121 | if let Some(count) = self.keep_weekly { if count > 0 { keep_something = true; } } | |
122 | if let Some(count) = self.keep_monthly { if count > 0 { keep_something = true; } } | |
123 | if let Some(count) = self.keep_yearly { if count > 0 { keep_something = true; } } | |
124 | keep_something | |
125 | } | |
9b783521 DM |
126 | } |
127 | ||
a8c8366c DM |
128 | pub fn compute_prune_info( |
129 | mut list: Vec<BackupInfo>, | |
9b783521 | 130 | options: &PruneOptions, |
a8c8366c DM |
131 | ) -> Result<Vec<(BackupInfo, bool)>, Error> { |
132 | ||
133 | let mut mark = HashMap::new(); | |
134 | ||
135 | BackupInfo::sort_list(&mut list, false); | |
136 | ||
137 | remove_incomplete_snapshots(&mut mark, &list); | |
dc188491 | 138 | |
9b783521 | 139 | if let Some(keep_last) = options.keep_last { |
dc188491 DM |
140 | mark_selections(&mut mark, &list, keep_last as usize, |_local_time, info| { |
141 | BackupDir::backup_time_to_string(info.backup_dir.backup_time()) | |
142 | }); | |
143 | } | |
144 | ||
9b783521 | 145 | if let Some(keep_daily) = options.keep_daily { |
dc188491 DM |
146 | mark_selections(&mut mark, &list, keep_daily as usize, |local_time, _info| { |
147 | format!("{}/{}/{}", local_time.year(), local_time.month(), local_time.day()) | |
148 | }); | |
149 | } | |
150 | ||
9b783521 | 151 | if let Some(keep_weekly) = options.keep_weekly { |
dc188491 DM |
152 | mark_selections(&mut mark, &list, keep_weekly as usize, |local_time, _info| { |
153 | format!("{}/{}", local_time.year(), local_time.iso_week().week()) | |
154 | }); | |
155 | } | |
156 | ||
9b783521 | 157 | if let Some(keep_monthly) = options.keep_monthly { |
dc188491 DM |
158 | mark_selections(&mut mark, &list, keep_monthly as usize, |local_time, _info| { |
159 | format!("{}/{}", local_time.year(), local_time.month()) | |
160 | }); | |
161 | } | |
162 | ||
9b783521 | 163 | if let Some(keep_yearly) = options.keep_yearly { |
dc188491 DM |
164 | mark_selections(&mut mark, &list, keep_yearly as usize, |local_time, _info| { |
165 | format!("{}/{}", local_time.year(), local_time.year()) | |
166 | }); | |
167 | } | |
168 | ||
169 | let prune_info: Vec<(BackupInfo, bool)> = list.into_iter() | |
170 | .map(|info| { | |
171 | let backup_id = info.backup_dir.relative_path(); | |
172 | let keep = match mark.get(&backup_id) { | |
173 | Some(PruneMark::Keep) => true, | |
40843436 DM |
174 | Some(PruneMark::KeepPartial) => true, |
175 | _ => false, | |
dc188491 DM |
176 | }; |
177 | (info, keep) | |
178 | }) | |
179 | .collect(); | |
180 | ||
181 | Ok(prune_info) | |
182 | } |