mirror of
https://github.com/restic/restic.git
synced 2025-08-23 14:57:37 +00:00
WIP: WebDAV server
This commit is contained in:
231
internal/serve/fs.go
Normal file
231
internal/serve/fs.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package serve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"golang.org/x/net/webdav"
|
||||
)
|
||||
|
||||
// Config holds settings for the file system served.
|
||||
type Config struct {
|
||||
Host string
|
||||
Tags []restic.TagList
|
||||
Paths []string
|
||||
}
|
||||
|
||||
const snapshotFormat = "2006-01-02_150405"
|
||||
|
||||
// RepoFileSystem implements a read-only file system on top of a repositoy.
|
||||
type RepoFileSystem struct {
|
||||
repo restic.Repository
|
||||
lastCheck time.Time
|
||||
|
||||
entries map[string]webdav.File
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// NewRepoFileSystem returns a new file system for the repo.
|
||||
func NewRepoFileSystem(ctx context.Context, repo restic.Repository, cfg Config) (*RepoFileSystem, error) {
|
||||
snapshots := restic.FindFilteredSnapshots(ctx, repo, cfg.Host, cfg.Tags, cfg.Paths)
|
||||
|
||||
lastcheck := time.Now()
|
||||
|
||||
nodes := make([]*restic.Node, 0, len(snapshots))
|
||||
entries := make(map[string]webdav.File)
|
||||
|
||||
for _, sn := range snapshots {
|
||||
name := sn.Time.Format(snapshotFormat)
|
||||
snFileInfo := virtFileInfo{
|
||||
name: name,
|
||||
size: 0,
|
||||
mode: 0755 | os.ModeDir,
|
||||
modtime: sn.Time,
|
||||
isdir: true,
|
||||
}
|
||||
|
||||
if sn.Tree == nil {
|
||||
return nil, errors.Errorf("snapshot %v has nil tree", sn.ID().Str())
|
||||
}
|
||||
|
||||
tree, err := repo.LoadTree(ctx, *sn.Tree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := path.Join("/", name)
|
||||
entries[p] = &RepoDir{
|
||||
fi: snFileInfo,
|
||||
nodes: tree.Nodes,
|
||||
}
|
||||
|
||||
nodes = append(nodes, &restic.Node{
|
||||
Name: name,
|
||||
Type: "dir",
|
||||
})
|
||||
}
|
||||
|
||||
entries["/"] = &RepoDir{
|
||||
nodes: nodes,
|
||||
fi: virtFileInfo{
|
||||
name: "root",
|
||||
size: 0,
|
||||
mode: 0755 | os.ModeDir,
|
||||
modtime: lastcheck,
|
||||
isdir: true,
|
||||
},
|
||||
}
|
||||
|
||||
fs := &RepoFileSystem{
|
||||
repo: repo,
|
||||
lastCheck: lastcheck,
|
||||
entries: entries,
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
// statically ensure that RepoFileSystem implements webdav.FileSystem
|
||||
var _ webdav.FileSystem = &RepoFileSystem{}
|
||||
|
||||
// Mkdir creates a new directory, it is not available for RepoFileSystem.
|
||||
func (fs *RepoFileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||
return webdav.ErrForbidden
|
||||
}
|
||||
|
||||
func (fs *RepoFileSystem) loadPath(ctx context.Context, name string) error {
|
||||
debug.Log("%v", name)
|
||||
|
||||
fs.m.Lock()
|
||||
_, ok := fs.entries[name]
|
||||
fs.m.Unlock()
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
dirname := path.Dir(name)
|
||||
if dirname == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := fs.loadPath(ctx, dirname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry, ok := fs.entries[dirname]
|
||||
if !ok {
|
||||
// loadPath did not succeed
|
||||
return nil
|
||||
}
|
||||
|
||||
repodir, ok := entry.(*RepoDir)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
filename := path.Base(name)
|
||||
for _, node := range repodir.nodes {
|
||||
if node.Name != filename {
|
||||
continue
|
||||
}
|
||||
|
||||
debug.Log("found item %v :%v", filename, node)
|
||||
|
||||
switch node.Type {
|
||||
case "dir":
|
||||
if node.Subtree == nil {
|
||||
return errors.Errorf("tree %v has nil tree", dirname)
|
||||
}
|
||||
|
||||
tree, err := fs.repo.LoadTree(ctx, *node.Subtree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newEntry := &RepoDir{
|
||||
fi: fileInfoFromNode(node),
|
||||
nodes: tree.Nodes,
|
||||
}
|
||||
|
||||
fs.m.Lock()
|
||||
fs.entries[name] = newEntry
|
||||
fs.m.Unlock()
|
||||
case "file":
|
||||
newEntry := &RepoFile{
|
||||
fi: fileInfoFromNode(node),
|
||||
node: node,
|
||||
}
|
||||
fs.m.Lock()
|
||||
fs.entries[name] = newEntry
|
||||
fs.m.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenFile opens a file for reading.
|
||||
func (fs *RepoFileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||
name = path.Clean(name)
|
||||
debug.Log("%v", name)
|
||||
if flag != os.O_RDONLY {
|
||||
return nil, webdav.ErrForbidden
|
||||
}
|
||||
|
||||
err := fs.loadPath(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fs.m.Lock()
|
||||
entry, ok := fs.entries[name]
|
||||
fs.m.Unlock()
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// RemoveAll recursively removes files and directories, it is not available for RepoFileSystem.
|
||||
func (fs *RepoFileSystem) RemoveAll(ctx context.Context, name string) error {
|
||||
debug.Log("%v", name)
|
||||
return webdav.ErrForbidden
|
||||
}
|
||||
|
||||
// Rename renames files or directories, it is not available for RepoFileSystem.
|
||||
func (fs *RepoFileSystem) Rename(ctx context.Context, oldName, newName string) error {
|
||||
debug.Log("%v -> %v", oldName, newName)
|
||||
return webdav.ErrForbidden
|
||||
}
|
||||
|
||||
// Stat returns information on a file or directory.
|
||||
func (fs *RepoFileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
||||
name = path.Clean(name)
|
||||
err := fs.loadPath(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fs.m.Lock()
|
||||
entry, ok := fs.entries[name]
|
||||
fs.m.Unlock()
|
||||
if !ok {
|
||||
debug.Log("%v not found", name)
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
fi, err := entry.Stat()
|
||||
debug.Log("%v %v", name, fi)
|
||||
return fi, err
|
||||
}
|
Reference in New Issue
Block a user