From ff487d1e8529ff0fb0cba0aa122a12853d08b9f5 Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Sat, 19 Aug 2017 19:49:18 -0700 Subject: [PATCH] added ability to mount special object 'all' that exposes the whole set of snapshots grouped by user@host, path and time --- cli/command_mount.go | 14 ++++++-- fs/repofs/all_sources.go | 59 ++++++++++++++++++++++++++++++ fs/repofs/source_directories.go | 48 +++++++++++++++++++++++++ fs/repofs/source_snapshots.go | 64 +++++++++++++++++++++++++++++++++ fuse/fusefs.go | 15 ++++++-- 5 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 fs/repofs/all_sources.go create mode 100644 fs/repofs/source_directories.go create mode 100644 fs/repofs/source_snapshots.go diff --git a/cli/command_mount.go b/cli/command_mount.go index 14f6d64a9..3cbc9fc33 100644 --- a/cli/command_mount.go +++ b/cli/command_mount.go @@ -43,12 +43,22 @@ func runMountCommand(context *kingpin.ParseContext) error { fuse.VolumeName("Kopia"), ) - oid, err := parseObjectID(*mountObjectID, rep) if err != nil { return err } - entry := repofs.Directory(rep, oid) + var entry fs.Directory + + if *mountObjectID == "all" { + entry = repofs.AllSources(rep) + } else { + oid, err := parseObjectID(*mountObjectID, rep) + if err != nil { + return err + } + entry = repofs.Directory(rep, oid) + } + if *mountTraceFS { entry = loggingfs.Wrap(entry).(fs.Directory) } diff --git a/fs/repofs/all_sources.go b/fs/repofs/all_sources.go new file mode 100644 index 000000000..3281e63cc --- /dev/null +++ b/fs/repofs/all_sources.go @@ -0,0 +1,59 @@ +package repofs + +import ( + "fmt" + "time" + + "github.com/kopia/kopia/fs" + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/snapshot" +) + +type repositoryAllSources struct { + repo *repo.Repository + snapshotManager *snapshot.Manager +} + +func (s *repositoryAllSources) Parent() fs.Directory { + return nil +} + +func (s *repositoryAllSources) Metadata() *fs.EntryMetadata { + return &fs.EntryMetadata{ + Name: "/", + Permissions: 0555, + Type: fs.EntryTypeDirectory, + ModTime: time.Now(), + } +} + +func (s *repositoryAllSources) Readdir() (fs.Entries, error) { + srcs, err := s.snapshotManager.ListSources() + if err != nil { + return nil, err + } + + users := map[string]bool{} + for _, src := range srcs { + users[fmt.Sprintf("%v@%v", src.UserName, src.Host)] = true + } + + var result fs.Entries + for u := range users { + result = append(result, &sourceDirectories{ + parent: s, + repo: s.repo, + snapshotManager: s.snapshotManager, + userHost: u, + }) + } + + return result, nil +} + +// AllSources returns fs.Directory that contains the list of all snapshot sources found in the repository. +func AllSources(r *repo.Repository) fs.Directory { + sm := snapshot.NewManager(r) + + return &repositoryAllSources{repo: r, snapshotManager: sm} +} diff --git a/fs/repofs/source_directories.go b/fs/repofs/source_directories.go new file mode 100644 index 000000000..d2f828ed1 --- /dev/null +++ b/fs/repofs/source_directories.go @@ -0,0 +1,48 @@ +package repofs + +import ( + "time" + + "github.com/kopia/kopia/fs" + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/snapshot" +) + +type sourceDirectories struct { + parent fs.Directory + repo *repo.Repository + snapshotManager *snapshot.Manager + userHost string +} + +func (s *sourceDirectories) Parent() fs.Directory { + return s.parent +} + +func (s *sourceDirectories) Metadata() *fs.EntryMetadata { + return &fs.EntryMetadata{ + Name: s.userHost, + Permissions: 0555, + Type: fs.EntryTypeDirectory, + ModTime: time.Now(), + } +} + +func (s *sourceDirectories) Readdir() (fs.Entries, error) { + sources, err := s.snapshotManager.ListSources() + if err != nil { + return nil, err + } + + var result fs.Entries + + for _, src := range sources { + if src.UserName+"@"+src.Host != s.userHost { + continue + } + + result = append(result, &sourceSnapshots{s, s.repo, s.snapshotManager, src}) + } + + return result, nil +} diff --git a/fs/repofs/source_snapshots.go b/fs/repofs/source_snapshots.go new file mode 100644 index 000000000..d68621d8a --- /dev/null +++ b/fs/repofs/source_snapshots.go @@ -0,0 +1,64 @@ +package repofs + +import ( + "fmt" + "strings" + + "github.com/kopia/kopia/fs" + "github.com/kopia/kopia/internal/dir" + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/snapshot" +) + +type sourceSnapshots struct { + parent fs.Directory + repo *repo.Repository + snapshotManager *snapshot.Manager + src *snapshot.SourceInfo +} + +func (s *sourceSnapshots) Parent() fs.Directory { + return s.parent +} + +func (s *sourceSnapshots) Metadata() *fs.EntryMetadata { + return &fs.EntryMetadata{ + Name: fmt.Sprintf("%v", safeName(s.src.Path)), + Permissions: 0555, + Type: fs.EntryTypeDirectory, + } +} + +func safeName(path string) string { + path = strings.TrimLeft(path, "/") + return strings.Replace(path, "/", "_", -1) +} + +func (s *sourceSnapshots) Readdir() (fs.Entries, error) { + manifests, err := s.snapshotManager.ListSnapshots(s.src, -1) + if err != nil { + return nil, err + } + + var result fs.Entries + + for _, m := range manifests { + name := m.StartTime.Format("20060102-150405") + if m.IncompleteReason != "" { + name += fmt.Sprintf(" (%v)", m.IncompleteReason) + } + e := newRepoEntry(s.repo, &dir.Entry{ + EntryMetadata: fs.EntryMetadata{ + Name: name, + Permissions: 0555, + Type: fs.EntryTypeDirectory, + ModTime: m.StartTime, + }, + ObjectID: m.RootObjectID, + }, s) + + result = append(result, e) + } + + return result, nil +} diff --git a/fuse/fusefs.go b/fuse/fusefs.go index 5cd17f205..09babd124 100644 --- a/fuse/fusefs.go +++ b/fuse/fusefs.go @@ -9,6 +9,7 @@ "fmt" "io/ioutil" "os" + "sort" "github.com/kopia/kopia/fs" @@ -72,11 +73,21 @@ func (dir *fuseDirectoryNode) Lookup(ctx context.Context, fileName string) (fuse } return newFuseNode(e, dir.cache) - } func (dir *fuseDirectoryNode) readPossiblyCachedReaddir() (fs.Entries, error) { - return dir.cache.getEntries(dir.cacheID, func() (fs.Entries, error) { return dir.directory().Readdir() }) + return dir.cache.getEntries(dir.cacheID, func() (fs.Entries, error) { + entries, err := dir.directory().Readdir() + if err != nil { + return nil, err + } + + sort.Slice(entries, func(i, j int) bool { + return entries[i].Metadata().Name < entries[j].Metadata().Name + }) + + return entries, nil + }) } func (dir *fuseDirectoryNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {