2 use std
::collections
::{HashMap, HashSet}
;
3 use std
::path
::PathBuf
;
7 enum PruneMark { Keep, KeepPartial, Remove }
9 fn mark_selections
<F
: Fn(&BackupInfo
) -> Result
<String
, Error
>> (
10 mark
: &mut HashMap
<PathBuf
, PruneMark
>,
11 list
: &Vec
<BackupInfo
>,
14 ) -> Result
<(), Error
> {
16 let mut include_hash
= HashSet
::new();
18 let mut already_included
= HashSet
::new();
20 let backup_id
= info
.backup_dir
.relative_path();
21 if let Some(PruneMark
::Keep
) = mark
.get(&backup_id
) {
22 let sel_id
: String
= select_id(&info
)?
;
23 already_included
.insert(sel_id
);
28 let backup_id
= info
.backup_dir
.relative_path();
29 if let Some(_
) = mark
.get(&backup_id
) { continue; }
30 let sel_id
: String
= select_id(&info
)?
;
32 if already_included
.contains(&sel_id
) { continue; }
34 if !include_hash
.contains(&sel_id
) {
35 if include_hash
.len() >= keep { break; }
36 include_hash
.insert(sel_id
);
37 mark
.insert(backup_id
, PruneMark
::Keep
);
39 mark
.insert(backup_id
, PruneMark
::Remove
);
46 fn remove_incomplete_snapshots(
47 mark
: &mut HashMap
<PathBuf
, PruneMark
>,
48 list
: &Vec
<BackupInfo
>,
51 let mut keep_unfinished
= true;
52 for info
in list
.iter() {
53 // backup is considered unfinished if there is no manifest
54 if info
.is_finished() {
55 // There is a new finished backup, so there is no need
56 // to keep older unfinished backups.
57 keep_unfinished
= false;
59 let backup_id
= info
.backup_dir
.relative_path();
60 if keep_unfinished
{ // keep first unfinished
61 mark
.insert(backup_id
, PruneMark
::KeepPartial
);
63 mark
.insert(backup_id
, PruneMark
::Remove
);
65 keep_unfinished
= false;
70 pub struct PruneOptions
{
71 pub keep_last
: Option
<u64>,
72 pub keep_hourly
: Option
<u64>,
73 pub keep_daily
: Option
<u64>,
74 pub keep_weekly
: Option
<u64>,
75 pub keep_monthly
: Option
<u64>,
76 pub keep_yearly
: Option
<u64>,
81 pub fn new() -> Self {
92 pub fn keep_hourly(mut self, value
: Option
<u64>) -> Self {
93 self.keep_hourly
= value
;
97 pub fn keep_last(mut self, value
: Option
<u64>) -> Self {
98 self.keep_last
= value
;
102 pub fn keep_daily(mut self, value
: Option
<u64>) -> Self {
103 self.keep_daily
= value
;
107 pub fn keep_weekly(mut self, value
: Option
<u64>) -> Self {
108 self.keep_weekly
= value
;
112 pub fn keep_monthly(mut self, value
: Option
<u64>) -> Self {
113 self.keep_monthly
= value
;
117 pub fn keep_yearly(mut self, value
: Option
<u64>) -> Self {
118 self.keep_yearly
= value
;
122 pub fn keeps_something(&self) -> bool
{
123 let mut keep_something
= false;
124 if let Some(count
) = self.keep_last { if count > 0 { keep_something = true; }
}
125 if let Some(count
) = self.keep_hourly { if count > 0 { keep_something = true; }
}
126 if let Some(count
) = self.keep_daily { if count > 0 { keep_something = true; }
}
127 if let Some(count
) = self.keep_weekly { if count > 0 { keep_something = true; }
}
128 if let Some(count
) = self.keep_monthly { if count > 0 { keep_something = true; }
}
129 if let Some(count
) = self.keep_yearly { if count > 0 { keep_something = true; }
}
133 pub fn cli_options_string(&self) -> String
{
134 let mut opts
= Vec
::new();
136 if let Some(count
) = self.keep_last
{
138 opts
.push(format
!("--keep-last {}", count
));
141 if let Some(count
) = self.keep_hourly
{
143 opts
.push(format
!("--keep-hourly {}", count
));
146 if let Some(count
) = self.keep_daily
{
148 opts
.push(format
!("--keep-daily {}", count
));
151 if let Some(count
) = self.keep_weekly
{
153 opts
.push(format
!("--keep-weekly {}", count
));
156 if let Some(count
) = self.keep_monthly
{
158 opts
.push(format
!("--keep-monthly {}", count
));
161 if let Some(count
) = self.keep_yearly
{
163 opts
.push(format
!("--keep-yearly {}", count
));
171 pub fn compute_prune_info(
172 mut list
: Vec
<BackupInfo
>,
173 options
: &PruneOptions
,
174 ) -> Result
<Vec
<(BackupInfo
, bool
)>, Error
> {
176 let mut mark
= HashMap
::new();
178 BackupInfo
::sort_list(&mut list
, false);
180 remove_incomplete_snapshots(&mut mark
, &list
);
182 if let Some(keep_last
) = options
.keep_last
{
183 mark_selections(&mut mark
, &list
, keep_last
as usize, |info
| {
184 Ok(info
.backup_dir
.backup_time_string().to_owned())
188 use proxmox
::tools
::time
::strftime_local
;
190 if let Some(keep_hourly
) = options
.keep_hourly
{
191 mark_selections(&mut mark
, &list
, keep_hourly
as usize, |info
| {
192 strftime_local("%Y/%m/%d/%H", info
.backup_dir
.backup_time())
196 if let Some(keep_daily
) = options
.keep_daily
{
197 mark_selections(&mut mark
, &list
, keep_daily
as usize, |info
| {
198 strftime_local("%Y/%m/%d", info
.backup_dir
.backup_time())
202 if let Some(keep_weekly
) = options
.keep_weekly
{
203 mark_selections(&mut mark
, &list
, keep_weekly
as usize, |info
| {
204 // Note: Use iso-week year/week here. This year number
205 // might not match the calendar year number.
206 strftime_local("%G/%V", info
.backup_dir
.backup_time())
210 if let Some(keep_monthly
) = options
.keep_monthly
{
211 mark_selections(&mut mark
, &list
, keep_monthly
as usize, |info
| {
212 strftime_local("%Y/%m", info
.backup_dir
.backup_time())
216 if let Some(keep_yearly
) = options
.keep_yearly
{
217 mark_selections(&mut mark
, &list
, keep_yearly
as usize, |info
| {
218 strftime_local("%Y", info
.backup_dir
.backup_time())
222 let prune_info
: Vec
<(BackupInfo
, bool
)> = list
.into_iter()
224 let backup_id
= info
.backup_dir
.relative_path();
225 let keep
= match mark
.get(&backup_id
) {
226 Some(PruneMark
::Keep
) => true,
227 Some(PruneMark
::KeepPartial
) => true,