2
2
mirror of https://github.com/octoleo/restic.git synced 2024-05-28 22:50:48 +00:00
restic/src/restic/snapshot_filter.go

199 lines
4.8 KiB
Go
Raw Normal View History

2016-08-19 18:50:52 +00:00
package restic
import (
"fmt"
"reflect"
"sort"
"time"
)
// Snapshots is a list of snapshots.
type Snapshots []*Snapshot
// Len returns the number of snapshots in sn.
func (sn Snapshots) Len() int {
return len(sn)
}
// Less returns true iff the ith snapshot has been made after the jth.
func (sn Snapshots) Less(i, j int) bool {
return sn[i].Time.After(sn[j].Time)
}
// Swap exchanges the two snapshots.
func (sn Snapshots) Swap(i, j int) {
sn[i], sn[j] = sn[j], sn[i]
}
// ExpirePolicy configures which snapshots should be automatically removed.
type ExpirePolicy struct {
2016-09-13 18:37:11 +00:00
Last int // keep the last n snapshots
Hourly int // keep the last n hourly snapshots
Daily int // keep the last n daily snapshots
Weekly int // keep the last n weekly snapshots
Monthly int // keep the last n monthly snapshots
Yearly int // keep the last n yearly snapshots
Tags []string // keep all snapshots with these tags
2016-08-19 18:50:52 +00:00
}
// Sum returns the maximum number of snapshots to be kept according to this
// policy.
func (e ExpirePolicy) Sum() int {
2016-08-20 15:51:48 +00:00
return e.Last + e.Hourly + e.Daily + e.Weekly + e.Monthly + e.Yearly
}
// Empty returns true iff no policy has been configured (all values zero).
func (e ExpirePolicy) Empty() bool {
2016-09-13 18:37:11 +00:00
if len(e.Tags) != 0 {
return false
}
2016-09-17 10:36:05 +00:00
empty := ExpirePolicy{Tags: e.Tags}
2016-09-13 18:37:11 +00:00
return reflect.DeepEqual(e, empty)
2016-08-19 18:50:52 +00:00
}
// filter is used to split a list of snapshots into those to keep and those to
// remove according to a policy.
type filter struct {
Unprocessed Snapshots
Remove Snapshots
Keep Snapshots
}
func (f filter) String() string {
return fmt.Sprintf("<filter %d todo, %d keep, %d remove>", len(f.Unprocessed), len(f.Keep), len(f.Remove))
}
2016-08-20 13:55:02 +00:00
// ymdh returns an integer in the form YYYYMMDDHH.
func ymdh(d time.Time) int {
return d.Year()*1000000 + int(d.Month())*10000 + d.Day()*100 + d.Hour()
}
2016-08-19 18:50:52 +00:00
// ymd returns an integer in the form YYYYMMDD.
func ymd(d time.Time) int {
return d.Year()*10000 + int(d.Month())*100 + d.Day()
}
// yw returns an integer in the form YYYYWW, where WW is the week number.
func yw(d time.Time) int {
year, week := d.ISOWeek()
return year*100 + week
}
// ym returns an integer in the form YYYYMM.
func ym(d time.Time) int {
return d.Year()*100 + int(d.Month())
}
// y returns the year of d.
func y(d time.Time) int {
return d.Year()
}
// apply moves snapshots from Unprocess to either Keep or Remove. It sorts the
// snapshots into buckets according to the return value of fn, and then moves
// the newest snapshot in each bucket to Keep and all others to Remove. When
// max snapshots were found, processing stops.
2016-08-19 18:50:52 +00:00
func (f *filter) apply(fn func(time.Time) int, max int) {
if max == 0 || len(f.Unprocessed) == 0 {
return
}
sameBucket := Snapshots{}
lastBucket := fn(f.Unprocessed[0].Time)
2016-08-19 18:50:52 +00:00
for len(f.Unprocessed) > 0 {
cur := f.Unprocessed[0]
bucket := fn(cur.Time)
2016-08-19 18:50:52 +00:00
// if the snapshots are from a new bucket, forget all but the first
// (=last in time) snapshot from the previous bucket.
if bucket != lastBucket {
f.Keep = append(f.Keep, sameBucket[0])
f.Remove = append(f.Remove, sameBucket[1:]...)
2016-08-19 18:50:52 +00:00
sameBucket = Snapshots{}
lastBucket = bucket
2016-08-19 18:50:52 +00:00
max--
if max == 0 {
return
2016-08-19 18:50:52 +00:00
}
}
// collect all snapshots for the current bucket
sameBucket = append(sameBucket, cur)
2016-08-19 18:50:52 +00:00
f.Unprocessed = f.Unprocessed[1:]
}
// if we have leftovers, process them too.
if len(sameBucket) > 0 {
f.Keep = append(f.Keep, sameBucket[0])
f.Remove = append(f.Remove, sameBucket[1:]...)
2016-08-19 18:50:52 +00:00
}
}
2016-09-13 18:37:11 +00:00
// keepTags marks the snapshots which have all tags as to be kept.
func (f *filter) keepTags(tags []string) {
if len(tags) == 0 {
return
}
2016-09-13 18:56:18 +00:00
unprocessed := f.Unprocessed[:0]
2016-09-13 18:37:11 +00:00
for _, sn := range f.Unprocessed {
if sn.HasTags(tags) {
f.Keep = append(f.Keep, sn)
2016-09-13 18:56:18 +00:00
continue
2016-09-13 18:37:11 +00:00
}
2016-09-13 18:56:18 +00:00
unprocessed = append(unprocessed, sn)
2016-09-13 18:37:11 +00:00
}
2016-09-13 18:56:18 +00:00
f.Unprocessed = unprocessed
2016-09-13 18:37:11 +00:00
}
2016-08-19 18:50:52 +00:00
// keepLast marks the last n snapshots as to be kept.
func (f *filter) keepLast(n int) {
if n > len(f.Unprocessed) {
n = len(f.Unprocessed)
}
f.Keep = append(f.Keep, f.Unprocessed[:n]...)
f.Unprocessed = f.Unprocessed[n:]
}
// finish moves all remaining snapshots to remove.
func (f *filter) finish() {
f.Remove = append(f.Remove, f.Unprocessed...)
}
// ApplyPolicy runs returns the snapshots from s that are to be deleted according
// to the policy p. s is sorted in the process.
func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
sort.Sort(list)
2016-08-20 15:51:48 +00:00
if p.Empty() {
2016-08-19 18:50:52 +00:00
return list, remove
}
if len(list) == 0 {
return list, remove
}
f := filter{
Unprocessed: list,
Remove: Snapshots{},
Keep: Snapshots{},
}
2016-09-13 18:37:11 +00:00
f.keepTags(p.Tags)
2016-08-19 18:50:52 +00:00
f.keepLast(p.Last)
2016-08-20 13:55:02 +00:00
f.apply(ymdh, p.Hourly)
2016-08-19 18:50:52 +00:00
f.apply(ymd, p.Daily)
f.apply(yw, p.Weekly)
f.apply(ym, p.Monthly)
f.apply(y, p.Yearly)
f.finish()
return f.Keep, f.Remove
}