1 use std
::collections
::{HashMap, HashSet}
;
2 use std
::path
::PathBuf
;
6 use pbs_api_types
::PruneOptions
;
10 enum PruneMark { Keep, KeepPartial, Remove }
12 fn mark_selections
<F
: Fn(&BackupInfo
) -> Result
<String
, Error
>> (
13 mark
: &mut HashMap
<PathBuf
, PruneMark
>,
17 ) -> Result
<(), Error
> {
19 let mut include_hash
= HashSet
::new();
21 let mut already_included
= HashSet
::new();
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
);
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
)?
;
35 if already_included
.contains(&sel_id
) { continue; }
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
);
42 mark
.insert(backup_id
, PruneMark
::Remove
);
49 fn remove_incomplete_snapshots(
50 mark
: &mut HashMap
<PathBuf
, PruneMark
>,
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;
62 let backup_id
= info
.backup_dir
.relative_path();
63 if keep_unfinished
{ // keep first unfinished
64 mark
.insert(backup_id
, PruneMark
::KeepPartial
);
66 mark
.insert(backup_id
, PruneMark
::Remove
);
68 keep_unfinished
= false;
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; }
}
84 pub fn cli_options_string(options
: &PruneOptions
) -> String
{
85 let mut opts
= Vec
::new();
87 if let Some(count
) = options
.keep_last
{
89 opts
.push(format
!("--keep-last {}", count
));
92 if let Some(count
) = options
.keep_hourly
{
94 opts
.push(format
!("--keep-hourly {}", count
));
97 if let Some(count
) = options
.keep_daily
{
99 opts
.push(format
!("--keep-daily {}", count
));
102 if let Some(count
) = options
.keep_weekly
{
104 opts
.push(format
!("--keep-weekly {}", count
));
107 if let Some(count
) = options
.keep_monthly
{
109 opts
.push(format
!("--keep-monthly {}", count
));
112 if let Some(count
) = options
.keep_yearly
{
114 opts
.push(format
!("--keep-yearly {}", count
));
121 pub fn compute_prune_info(
122 mut list
: Vec
<BackupInfo
>,
123 options
: &PruneOptions
,
124 ) -> Result
<Vec
<(BackupInfo
, bool
)>, Error
> {
126 let mut mark
= HashMap
::new();
128 BackupInfo
::sort_list(&mut list
, false);
130 remove_incomplete_snapshots(&mut mark
, &list
);
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())
138 use proxmox_time
::strftime_local
;
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
)
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
)
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
)
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
)
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
)
172 let prune_info
: Vec
<(BackupInfo
, bool
)> = list
.into_iter()
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,