restic/src/restic/snapshot.go

216 lines
4.8 KiB
Go
Raw Normal View History

2014-12-05 20:45:49 +00:00
package restic
2014-08-04 18:47:04 +00:00
import (
2014-08-11 20:47:24 +00:00
"fmt"
"os/user"
2014-09-23 20:39:12 +00:00
"path/filepath"
2014-08-04 18:47:04 +00:00
"time"
2014-09-23 20:39:12 +00:00
2016-09-01 20:17:37 +00:00
"restic/errors"
2014-08-04 18:47:04 +00:00
)
// Snapshot is the state of a resource at one point in time.
2014-08-04 18:47:04 +00:00
type Snapshot struct {
2016-08-31 18:29:54 +00:00
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"`
2016-09-13 18:12:55 +00:00
Tags []string `json:"tags,omitempty"`
2016-08-31 18:29:54 +00:00
id *ID // plaintext ID, used during restore
2014-08-04 18:47:04 +00:00
}
// NewSnapshot returns an initialized snapshot struct for the current user and
// time.
2017-02-10 18:37:33 +00:00
func NewSnapshot(paths []string, tags []string, hostname string) (*Snapshot, error) {
2015-03-02 13:48:47 +00:00
for i, path := range paths {
if p, err := filepath.Abs(path); err != nil {
paths[i] = p
}
2014-09-23 20:39:12 +00:00
}
2014-08-04 18:47:04 +00:00
sn := &Snapshot{
2017-02-10 18:37:33 +00:00
Paths: paths,
Time: time.Now(),
Tags: tags,
Hostname: hostname,
2014-08-04 18:47:04 +00:00
}
2017-02-10 18:37:33 +00:00
err := sn.fillUserInfo()
if err != nil {
return nil, err
2014-08-04 18:47:04 +00:00
}
return sn, nil
2014-08-04 18:47:04 +00:00
}
// LoadSnapshot loads the snapshot with the id and returns it.
2016-08-31 18:29:54 +00:00
func LoadSnapshot(repo Repository, id ID) (*Snapshot, error) {
sn := &Snapshot{id: &id}
2016-08-31 18:29:54 +00:00
err := repo.LoadJSONUnpacked(SnapshotFile, id, sn)
2014-08-04 20:46:14 +00:00
if err != nil {
return nil, err
}
return sn, nil
2014-08-04 18:47:04 +00:00
}
2014-08-11 20:47:24 +00:00
// LoadAllSnapshots returns a list of all snapshots in the repo.
2016-08-31 18:29:54 +00:00
func LoadAllSnapshots(repo Repository) (snapshots []*Snapshot, err error) {
2016-04-10 14:51:46 +00:00
done := make(chan struct{})
defer close(done)
2016-08-31 18:29:54 +00:00
for id := range repo.List(SnapshotFile, done) {
2016-04-10 14:51:46 +00:00
sn, err := LoadSnapshot(repo, id)
if err != nil {
return nil, err
}
snapshots = append(snapshots, sn)
}
return
2016-04-10 14:51:46 +00:00
}
func (sn Snapshot) String() string {
2016-08-19 18:50:52 +00:00
return fmt.Sprintf("<Snapshot %s of %v at %s by %s@%s>",
sn.id.Str(), sn.Paths, sn.Time, sn.Username, sn.Hostname)
2014-08-11 20:47:24 +00:00
}
2017-02-08 23:43:10 +00:00
// ID returns the snapshot's ID.
2016-08-31 18:29:54 +00:00
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
}
// HasTags returns true if the snapshot has all the tags.
func (sn *Snapshot) HasTags(tags []string) bool {
nextTag:
for _, tag := range tags {
for _, snTag := range sn.Tags {
if tag == snTag {
continue nextTag
}
}
return false
}
return true
}
2016-05-10 19:51:56 +00:00
// SamePaths compares the Snapshot's paths and provided paths are exactly the same
func SamePaths(expected, actual []string) bool {
2016-09-17 10:36:05 +00:00
if len(expected) == 0 || len(actual) == 0 {
return true
}
for i := range expected {
2016-05-10 19:51:56 +00:00
found := false
for j := range actual {
if expected[i] == actual[j] {
found = true
break
}
}
if !found {
return false
}
}
return true
}
2016-08-19 18:36:24 +00:00
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
var ErrNoSnapshotFound = errors.New("no snapshot found")
// FindLatestSnapshot finds latest snapshot with optional target/directory and hostname filters.
func FindLatestSnapshot(repo Repository, targets []string, hostname string) (ID, error) {
var (
latest time.Time
2016-08-31 18:29:54 +00:00
latestID ID
found bool
)
2016-08-31 18:29:54 +00:00
for snapshotID := range repo.List(SnapshotFile, make(chan struct{})) {
snapshot, err := LoadSnapshot(repo, snapshotID)
if err != nil {
2016-08-31 18:29:54 +00:00
return ID{}, errors.Errorf("Error listing snapshot: %v", err)
}
if snapshot.Time.After(latest) && SamePaths(snapshot.Paths, targets) && (hostname == "" || hostname == snapshot.Hostname) {
latest = snapshot.Time
latestID = snapshotID
found = true
}
}
if !found {
2016-08-31 18:29:54 +00:00
return ID{}, ErrNoSnapshotFound
}
return latestID, nil
}
2016-09-01 14:04:29 +00:00
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
// the string as closely as possible.
func FindSnapshot(repo Repository, s string) (ID, error) {
// find snapshot id with prefix
name, err := Find(repo.Backend(), SnapshotFile, s)
if err != nil {
return ID{}, err
}
return ParseID(name)
}