]>
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 | } | |
116 | } | |
117 | ||
a8c8366c DM |
118 | pub fn compute_prune_info( |
119 | mut list: Vec<BackupInfo>, | |
9b783521 | 120 | options: &PruneOptions, |
a8c8366c DM |
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); | |
dc188491 | 128 | |
9b783521 | 129 | if let Some(keep_last) = options.keep_last { |
dc188491 DM |
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 | ||
9b783521 | 135 | if let Some(keep_daily) = options.keep_daily { |
dc188491 DM |
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 | ||
9b783521 | 141 | if let Some(keep_weekly) = options.keep_weekly { |
dc188491 DM |
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 | ||
9b783521 | 147 | if let Some(keep_monthly) = options.keep_monthly { |
dc188491 DM |
148 | mark_selections(&mut mark, &list, keep_monthly as usize, |local_time, _info| { |
149 | format!("{}/{}", local_time.year(), local_time.month()) | |
150 | }); | |
151 | } | |
152 | ||
9b783521 | 153 | if let Some(keep_yearly) = options.keep_yearly { |
dc188491 DM |
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, | |
40843436 DM |
164 | Some(PruneMark::KeepPartial) => true, |
165 | _ => false, | |
dc188491 DM |
166 | }; |
167 | (info, keep) | |
168 | }) | |
169 | .collect(); | |
170 | ||
171 | Ok(prune_info) | |
172 | } |