mirror of
https://github.com/restic/restic.git
synced 2025-12-04 02:41:52 +00:00
Generalize fuse snapshot dirs implemetation
+ allow "/" in tags and snapshot template
This commit is contained in:
committed by
Michael Eischer
parent
696c18e031
commit
57f4003f2f
202
internal/fuse/snapshots_dirstruct.go
Normal file
202
internal/fuse/snapshots_dirstruct.go
Normal file
@@ -0,0 +1,202 @@
|
||||
//go:build darwin || freebsd || linux
|
||||
// +build darwin freebsd linux
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// SnapshotsDirStructure contains the directory structure for snapshots.
|
||||
// It uses a paths and time template to generate a map of pathnames
|
||||
// pointing to the actual snapshots. For templates that end with a time,
|
||||
// also "latest" links are generated.
|
||||
type SnapshotsDirStructure struct {
|
||||
root *Root
|
||||
pathTemplates []string
|
||||
timeTemplate string
|
||||
|
||||
names map[string]*restic.Snapshot
|
||||
latest map[string]string
|
||||
snCount int
|
||||
lastCheck time.Time
|
||||
}
|
||||
|
||||
// NewSnapshotsDirStructure returns a new directory structure for snapshots.
|
||||
func NewSnapshotsDirStructure(root *Root, pathTemplates []string, timeTemplate string) *SnapshotsDirStructure {
|
||||
return &SnapshotsDirStructure{
|
||||
root: root,
|
||||
pathTemplates: pathTemplates,
|
||||
timeTemplate: timeTemplate,
|
||||
snCount: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// uniqueName returns a unique name to be used for prefix+name.
|
||||
// It appends -number to make the name unique.
|
||||
func (d *SnapshotsDirStructure) uniqueName(prefix, name string) (newname string) {
|
||||
newname = name
|
||||
for i := 1; ; i++ {
|
||||
if _, ok := d.names[prefix+newname]; !ok {
|
||||
break
|
||||
}
|
||||
newname = fmt.Sprintf("%s-%d", name, i)
|
||||
}
|
||||
return newname
|
||||
}
|
||||
|
||||
// pathsFromSn generates the paths from pathTemplate and timeTemplate
|
||||
// where the variables are replaced by the snapshot data.
|
||||
// The time is given as suffix if the pathTemplate ends with "%T".
|
||||
func pathsFromSn(pathTemplate string, timeTemplate string, sn *restic.Snapshot) (paths []string, timeSuffix string) {
|
||||
timeformat := sn.Time.Format(timeTemplate)
|
||||
|
||||
inVerb := false
|
||||
writeTime := false
|
||||
out := make([]strings.Builder, 1)
|
||||
for _, c := range pathTemplate {
|
||||
if writeTime {
|
||||
for i := range out {
|
||||
out[i].WriteString(timeformat)
|
||||
}
|
||||
writeTime = false
|
||||
}
|
||||
|
||||
if !inVerb {
|
||||
if c == '%' {
|
||||
inVerb = true
|
||||
} else {
|
||||
for i := range out {
|
||||
out[i].WriteRune(c)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var repl string
|
||||
inVerb = false
|
||||
switch c {
|
||||
case 'T':
|
||||
// lazy write; time might be returned as suffix
|
||||
writeTime = true
|
||||
continue
|
||||
|
||||
case 't':
|
||||
if len(sn.Tags) != 1 {
|
||||
// needs special treatment: Rebuild the string builders
|
||||
newout := make([]strings.Builder, len(out)*len(sn.Tags))
|
||||
for i, tag := range sn.Tags {
|
||||
for j := range out {
|
||||
newout[i*len(out)+j].WriteString(out[j].String() + tag)
|
||||
}
|
||||
}
|
||||
out = newout
|
||||
continue
|
||||
}
|
||||
repl = sn.Tags[0]
|
||||
|
||||
case 'i':
|
||||
repl = sn.ID().Str()
|
||||
|
||||
case 'I':
|
||||
repl = sn.ID().String()
|
||||
|
||||
case 'u':
|
||||
repl = sn.Username
|
||||
|
||||
case 'h':
|
||||
repl = sn.Hostname
|
||||
}
|
||||
|
||||
// write replacement string to all string builders
|
||||
for i := range out {
|
||||
out[i].WriteString(repl)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range out {
|
||||
paths = append(paths, out[i].String())
|
||||
}
|
||||
|
||||
if writeTime {
|
||||
timeSuffix = timeformat
|
||||
}
|
||||
|
||||
return paths, timeSuffix
|
||||
}
|
||||
|
||||
// makeDirs inserts all paths generated from pathTemplates and
|
||||
// TimeTemplate for all given snapshots into d.names.
|
||||
// Also adds d.latest links if "%T" is at end of a path template
|
||||
func (d *SnapshotsDirStructure) makeDirs(snapshots restic.Snapshots) {
|
||||
d.names = make(map[string]*restic.Snapshot)
|
||||
d.latest = make(map[string]string)
|
||||
|
||||
// insert pure directories; needed to get empty structure even if there
|
||||
// are no snapshots in these dirs
|
||||
for _, p := range d.pathTemplates {
|
||||
for _, pattern := range []string{"%i", "%I", "%u", "%h", "%t", "%T"} {
|
||||
p = strings.ReplaceAll(p, pattern, "")
|
||||
}
|
||||
d.names[path.Clean(p)+"/"] = nil
|
||||
}
|
||||
|
||||
latestTime := make(map[string]time.Time)
|
||||
for _, sn := range snapshots {
|
||||
for _, templ := range d.pathTemplates {
|
||||
paths, timeSuffix := pathsFromSn(templ, d.timeTemplate, sn)
|
||||
for _, p := range paths {
|
||||
suffix := d.uniqueName(p, timeSuffix)
|
||||
d.names[path.Clean(p+suffix)] = sn
|
||||
if timeSuffix != "" {
|
||||
lt, ok := latestTime[p]
|
||||
if !ok || !sn.Time.Before(lt) {
|
||||
debug.Log("link (update) %v -> %v\n", p, suffix)
|
||||
d.latest[p] = suffix
|
||||
latestTime[p] = sn.Time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const minSnapshotsReloadTime = 60 * time.Second
|
||||
|
||||
// update snapshots if repository has changed
|
||||
func (d *SnapshotsDirStructure) updateSnapshots(ctx context.Context) error {
|
||||
if time.Since(d.lastCheck) < minSnapshotsReloadTime {
|
||||
return nil
|
||||
}
|
||||
|
||||
snapshots, err := restic.FindFilteredSnapshots(ctx, d.root.repo.Backend(), d.root.repo, d.root.cfg.Hosts, d.root.cfg.Tags, d.root.cfg.Paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// sort snapshots ascending by time (default order is descending)
|
||||
sort.Sort(sort.Reverse(snapshots))
|
||||
|
||||
d.lastCheck = time.Now()
|
||||
|
||||
if d.snCount == len(snapshots) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = d.root.repo.LoadIndex(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.snCount = len(snapshots)
|
||||
|
||||
d.makeDirs(snapshots)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user