package restic import ( "context" "fmt" "os/user" "path/filepath" "strings" "time" ) // Snapshot is the state of a resource at one point in time. type Snapshot struct { Time time.Time `json:"time"` Parent *ID `json:"parent,omitempty"` Tree *ID `json:"tree"` Paths []string `json:"paths"` Hostname string `json:"hostname,omitempty"` Username string `json:"username,omitempty"` UID uint32 `json:"uid,omitempty"` GID uint32 `json:"gid,omitempty"` Excludes []string `json:"excludes,omitempty"` Tags []string `json:"tags,omitempty"` Original *ID `json:"original,omitempty"` id *ID // plaintext ID, used during restore } // NewSnapshot returns an initialized snapshot struct for the current user and // time. func NewSnapshot(paths []string, tags []string, hostname string) (*Snapshot, error) { for i, path := range paths { if p, err := filepath.Abs(path); err != nil { paths[i] = p } } sn := &Snapshot{ Paths: paths, Time: time.Now(), Tags: tags, Hostname: hostname, } err := sn.fillUserInfo() if err != nil { return nil, err } return sn, nil } // LoadSnapshot loads the snapshot with the id and returns it. func LoadSnapshot(ctx context.Context, repo Repository, id ID) (*Snapshot, error) { sn := &Snapshot{id: &id} err := repo.LoadJSONUnpacked(ctx, SnapshotFile, id, sn) if err != nil { return nil, err } return sn, nil } // LoadAllSnapshots returns a list of all snapshots in the repo. func LoadAllSnapshots(ctx context.Context, repo Repository) (snapshots []*Snapshot, err error) { for id := range repo.List(ctx, SnapshotFile) { sn, err := LoadSnapshot(ctx, repo, id) if err != nil { return nil, err } snapshots = append(snapshots, sn) } return } func (sn Snapshot) String() string { return fmt.Sprintf("", sn.id.Str(), sn.Paths, sn.Time, sn.Username, sn.Hostname) } // ID returns the snapshot's ID. func (sn Snapshot) ID() *ID { return sn.id } func (sn *Snapshot) fillUserInfo() error { usr, err := user.Current() if err != nil { return nil } sn.Username = usr.Username // set userid and groupid sn.UID, sn.GID, err = uidGidInt(*usr) return err } // AddTags adds the given tags to the snapshots tags, preventing duplicates. // It returns true if any changes were made. func (sn *Snapshot) AddTags(addTags []string) (changed bool) { nextTag: for _, add := range addTags { for _, tag := range sn.Tags { if tag == add { continue nextTag } } sn.Tags = append(sn.Tags, add) changed = true } return } // RemoveTags removes the given tags from the snapshots tags and // returns true if any changes were made. func (sn *Snapshot) RemoveTags(removeTags []string) (changed bool) { for _, remove := range removeTags { for i, tag := range sn.Tags { if tag == remove { // https://github.com/golang/go/wiki/SliceTricks sn.Tags[i] = sn.Tags[len(sn.Tags)-1] sn.Tags[len(sn.Tags)-1] = "" sn.Tags = sn.Tags[:len(sn.Tags)-1] changed = true break } } } return } func (sn *Snapshot) hasTag(tag string) bool { for _, snTag := range sn.Tags { if tag == snTag { return true } } return false } // HasTags returns true if the snapshot has at least one of the tags. Tags // are compared as strings, unless they contain a comma. Then each of the comma // separated parts of the tag need to be present. func (sn *Snapshot) HasTags(tags []string) bool { if len(tags) == 0 { return true } nextTag: for _, tag := range tags { for _, s := range strings.Split(tag, ",") { if !sn.hasTag(s) { // fail, try next tag continue nextTag } } return true } return false } func (sn *Snapshot) hasPath(path string) bool { for _, snPath := range sn.Paths { if path == snPath { return true } } return false } // HasPaths returns true if the snapshot has at least one of the paths. Paths // are compared as strings unless they contain a comma. Then each of the comma // separated parts of the path need to be present. func (sn *Snapshot) HasPaths(paths []string) bool { if len(paths) == 0 { return true } nextPath: for _, path := range paths { for _, p := range strings.Split(path, ",") { if !sn.hasPath(p) { // fail, try next path continue nextPath } } return true } return false } // SamePaths returns true if the snapshot matches the entire paths set func (sn *Snapshot) SamePaths(paths []string) bool { if len(sn.Paths) != len(paths) { return false } return sn.HasPaths(paths) } // 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] }