1
0
Fork 0
mirror of https://github.com/restic/restic.git synced 2024-12-21 23:33:03 +00:00
This commit is contained in:
Alexander Neumann 2024-03-03 14:19:22 +01:00
parent bd3022c504
commit b3f38686ee
7 changed files with 324 additions and 143 deletions

View file

@ -8,6 +8,7 @@ import (
"time"
"github.com/spf13/cobra"
"golang.org/x/net/webdav"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
@ -104,12 +105,21 @@ func runWebDAV(ctx context.Context, opts WebDAVOptions, gopts GlobalOptions, arg
// },
// }
logRequest := func(req *http.Request, err error) {
errorLogger.Printf("req %v %v -> %v\n", req.Method, req.URL.Path, err)
}
srv := &http.Server{
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
Addr: opts.Listen,
Handler: http.FileServer(http.FS(root)),
ErrorLog: errorLogger,
// Handler: http.FileServer(http.FS(root)),
Handler: &webdav.Handler{
FileSystem: rofs.WebDAVFS(root),
LockSystem: webdav.NewMemLS(),
Logger: logRequest,
},
ErrorLog: errorLogger,
}
return srv.ListenAndServe()

View file

@ -23,7 +23,7 @@ func (d dirEntry) Info() (fs.FileInfo, error) { return d.fileInfo, nil }
// openDir represents a directory opened for reading.
type openDir struct {
path string
fileInfo FileInfo
fileInfo fileInfo
entries []fs.DirEntry
offset int
}

View file

@ -5,19 +5,19 @@ import (
"time"
)
// FileInfo provides information about a file or directory.
type FileInfo struct {
// fileInfo provides information about a file or directory.
type fileInfo struct {
name string
mode fs.FileMode
modtime time.Time
size int64
}
func (fi FileInfo) Name() string { return fi.name }
func (fi FileInfo) IsDir() bool { return fi.mode.IsDir() }
func (fi FileInfo) ModTime() time.Time { return fi.modtime }
func (fi FileInfo) Mode() fs.FileMode { return fi.mode }
func (fi FileInfo) Size() int64 { return fi.size }
func (fi FileInfo) Sys() any { return nil }
func (fi fileInfo) Name() string { return fi.name }
func (fi fileInfo) IsDir() bool { return fi.mode.IsDir() }
func (fi fileInfo) ModTime() time.Time { return fi.modtime }
func (fi fileInfo) Mode() fs.FileMode { return fi.mode }
func (fi fileInfo) Size() int64 { return fi.size }
func (fi fileInfo) Sys() any { return nil }
var _ fs.FileInfo = FileInfo{}
var _ fs.FileInfo = fileInfo{}

View file

@ -16,7 +16,7 @@ type ROFS struct {
repo restic.Repository
cfg Config
entries map[string]rofsEntry
fileInfo FileInfo
fileInfo fileInfo
}
type rofsEntry interface {
@ -50,7 +50,7 @@ func New(ctx context.Context, repo restic.Repository, cfg Config) (*ROFS, error)
repo: repo,
cfg: cfg,
entries: make(map[string]rofsEntry),
fileInfo: FileInfo{
fileInfo: fileInfo{
name: ".",
mode: 0755,
modtime: time.Now(),
@ -65,6 +65,43 @@ func New(ctx context.Context, repo restic.Repository, cfg Config) (*ROFS, error)
return rofs, nil
}
func buildSnapshotEntries(ctx context.Context, repo restic.Repository, cfg Config) (map[string]rofsEntry, error) {
var snapshots restic.Snapshots
err := cfg.Filter.FindAll(ctx, repo, repo, nil, func(_ string, sn *restic.Snapshot, _ error) error {
if sn != nil {
snapshots = append(snapshots, sn)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("filter snapshots: %w", err)
}
debug.Log("found %d snapshots", len(snapshots))
list := make(map[string]rofsEntry)
list["foo"] = NewMemFile("foo", []byte("foobar content of file foo"), time.Now())
list["snapshots"] = NewMemFile("snapshots", []byte("here goes the snapshot list"), time.Now())
// list["snapshots"] = NewSnapshotsDir(cfg.PathTemplates, cfg.TimeTemplate)
return list, nil
}
func (rofs *ROFS) updateSnapshots(ctx context.Context) error {
entries, err := buildSnapshotEntries(ctx, rofs.repo, rofs.cfg)
if err != nil {
return err
}
rofs.entries = entries
return nil
}
// Open opens the named file.
//
// When Open returns an error, it should be of type *PathError
@ -90,7 +127,7 @@ func (rofs *ROFS) Open(name string) (fs.File, error) {
d := &openDir{
path: ".",
fileInfo: FileInfo{
fileInfo: fileInfo{
name: ".",
mode: fs.ModeDir | 0555,
modtime: time.Now(),
@ -115,38 +152,3 @@ func (rofs *ROFS) Open(name string) (fs.File, error) {
return entry.Open()
}
func buildSnapshotEntries(ctx context.Context, repo restic.Repository, cfg Config) (map[string]rofsEntry, error) {
var snapshots restic.Snapshots
err := cfg.Filter.FindAll(ctx, repo, repo, nil, func(_ string, sn *restic.Snapshot, _ error) error {
if sn != nil {
snapshots = append(snapshots, sn)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("filter snapshots: %w", err)
}
debug.Log("found %d snapshots", len(snapshots))
list := make(map[string]rofsEntry)
list["snapshots"] = dirEntry{}
//FIXME CONTINUE
return list, nil
}
func (rofs *ROFS) updateSnapshots(ctx context.Context) error {
entries, err := buildSnapshotEntries(ctx, rofs.repo, rofs.cfg)
if err != nil {
return err
}
rofs.entries = entries
return nil
}

View file

@ -11,7 +11,7 @@ import (
type MemFile struct {
Path string
FileInfo FileInfo
FileInfo fileInfo
Data []byte
}
@ -20,7 +20,7 @@ func NewMemFile(filename string, data []byte, modTime time.Time) MemFile {
return MemFile{
Path: filename,
Data: data,
FileInfo: FileInfo{
FileInfo: fileInfo{
name: path.Base(filename),
size: int64(len(data)),
mode: 0644,
@ -47,7 +47,7 @@ func (f MemFile) DirEntry() fs.DirEntry {
type openMemFile struct {
path string
fileInfo FileInfo
fileInfo fileInfo
data []byte
offset int64

View file

@ -1,117 +1,134 @@
package rofs
import (
"io"
"io/fs"
"slices"
"time"
// // SnapshotsDir implements a tree of snapshots in repo as a file system in various sub-directories.
// type SnapshotsDir struct {
// lastUpdate time.Time
"github.com/restic/restic/internal/debug"
)
// pathTemplates []string
// timeTemplate string
// SnapshotsDir implements a tree of snapshots in repo as a file system in various sub-directories.
type SnapshotsDir struct {
modTime time.Time
// // list of top-level directories
// entries []rofsEntry
// }
pathTemplates []string
timeTemplate string
// // ensure that the interface is implemented
// var _ rofsEntry = &SnapshotsDir{}
// prepare the list of top-level directories
entries []fs.DirEntry
// // NewSnapshotsDir initializes a new top-level snapshots directory.
// func NewSnapshotsDir(pathTemplates []string, timeTemplate string) *SnapshotsDir {
// dir := &SnapshotsDir{
// pathTemplates: pathTemplates,
// timeTemplate: timeTemplate,
// lastUpdate: time.Now(),
// }
// used by ReadDir() with positive number of entries to return
entriesRemaining []fs.DirEntry
}
// // testnames := []string{"foo", "bar", "baz", "snapshots"}
// // for _, name := range testnames {
// // dir.entries = append(dir.entries,
// // fs.FileInfoToDirEntry(FileInfo{
// // name: name,
// // mode: 0644,
// // modtime: time.Now(),
// // }))
// // }
// NewSnapshotsDir initializes a new top-level snapshots directory.
func NewSnapshotsDir(pathTemplates []string, timeTemplate string) *SnapshotsDir {
dir := &SnapshotsDir{
pathTemplates: pathTemplates,
timeTemplate: timeTemplate,
modTime: time.Now(),
}
// // slices.SortFunc(dir.entries, func(a, b fs.DirEntry) int {
// // if a.Name() == b.Name() {
// // return 0
// // }
testnames := []string{"foo", "bar", "baz", "snapshots"}
for _, name := range testnames {
dir.entries = append(dir.entries,
fs.FileInfoToDirEntry(FileInfo{
name: name,
mode: 0644,
modtime: time.Now(),
}))
}
// // if a.Name() < b.Name() {
// // return 1
// // }
slices.SortFunc(dir.entries, func(a, b fs.DirEntry) int {
if a.Name() == b.Name() {
return 0
}
// // return -1
// // })
if a.Name() < b.Name() {
return 1
}
// // // prepare for readdir with positive n
// // dir.entriesRemaining = dir.entries
return -1
})
// return dir
// }
// prepare for readdir with positive n
dir.entriesRemaining = dir.entries
// // ensure that it implements all necessary interfaces.
// var _ fs.ReadDirFile = &SnapshotsDir{}
return dir
}
// // Close closes the snapshots dir.
// func (dir *SnapshotsDir) Close() error {
// debug.Log("Close()")
// ensure that it implements all necessary interfaces.
var _ fs.ReadDirFile = &SnapshotsDir{}
// // reset readdir list
// // dir.entriesRemaining = dir.entries
// Close closes the snapshots dir.
func (dir *SnapshotsDir) Close() error {
debug.Log("Close()")
// return nil
// }
// reset readdir list
dir.entriesRemaining = dir.entries
// // Read is not implemented for a dir.
// func (dir *SnapshotsDir) Read([]byte) (int, error) {
// return 0, &fs.PathError{
// Op: "read",
// Err: fs.ErrInvalid,
// }
// }
return nil
}
// // Stat returns information about the dir.
// func (dir *SnapshotsDir) Stat() (fs.FileInfo, error) {
// debug.Log("Stat(root)")
// Read is not implemented for a dir.
func (dir *SnapshotsDir) Read([]byte) (int, error) {
return 0, &fs.PathError{
Op: "read",
Err: fs.ErrInvalid,
}
}
// fi := FileInfo{
// name: "root", // use special name, this is the root node
// size: 0,
// modtime: dir.lastUpdate,
// mode: 0755,
// }
// Stat returns information about the dir.
func (dir *SnapshotsDir) Stat() (fs.FileInfo, error) {
debug.Log("Stat(root)")
// return fi, nil
// }
fi := FileInfo{
name: "root", // use special name, this is the root node
size: 0,
modtime: dir.modTime,
mode: 0755,
}
// // ReadDir returns a list of entries.
// func (dir *SnapshotsDir) ReadDir(n int) ([]fs.DirEntry, error) {
// if n < 0 {
// debug.Log("Readdir(root, %v), return %v entries", n, len(dir.entries))
// return dir.entries, nil
// }
return fi, nil
}
// // complicated pointer handling
// if n > len(dir.entriesRemaining) {
// n = len(dir.entriesRemaining)
// }
// ReadDir returns a list of entries.
func (dir *SnapshotsDir) ReadDir(n int) ([]fs.DirEntry, error) {
if n < 0 {
debug.Log("Readdir(root, %v), return %v entries", n, len(dir.entries))
return dir.entries, nil
}
// if n == 0 {
// return nil, io.EOF
// }
// complicated pointer handling
if n > len(dir.entriesRemaining) {
n = len(dir.entriesRemaining)
}
// list := dir.entriesRemaining[:n]
// dir.entriesRemaining = dir.entriesRemaining[n:]
if n == 0 {
return nil, io.EOF
}
// return list, nil
// }
list := dir.entriesRemaining[:n]
dir.entriesRemaining = dir.entriesRemaining[n:]
// // DirEntry returns meta data about the dir snapshots dir itself.
// func (dir *SnapshotsDir) DirEntry() fs.DirEntry {
// return dirEntry{
// fileInfo: FileInfo{
// name: "snapshots",
// mode: fs.ModeDir | 0755,
// modtime: dir.lastUpdate,
// },
// }
// }
return list, nil
}
// // Open opens the dir for reading.
// func (dir *SnapshotsDir) Open() (fs.File, error) {
// d := &openDir{
// path: "snapshots",
// fileInfo: FileInfo{
// name: "snapshots",
// mode: fs.ModeDir | 0555,
// modtime: dir.lastUpdate,
// },
// entries: dirMap2DirEntry(dir.entries),
// }
// return d
// }

View file

@ -0,0 +1,152 @@
package rofs
import (
"context"
"fmt"
"io"
"io/fs"
"os"
"path"
"golang.org/x/net/webdav"
)
// WebDAVFS returns a file system suitable for use with the WebDAV server.
func WebDAVFS(fs *ROFS) webdav.FileSystem {
return &webDAVFS{FS: fs}
}
// webDAVFS wraps an fs.FS and returns a (read-only) filesystem suitable for use with WebDAV.
type webDAVFS struct {
fs.FS
}
// ensure that WebDAVFS can be used for webdav.
var _ webdav.FileSystem = &webDAVFS{}
func (*webDAVFS) Mkdir(_ context.Context, name string, _ fs.FileMode) error {
return &fs.PathError{
Op: "Mkdir",
Path: name,
Err: fs.ErrPermission,
}
}
func (*webDAVFS) RemoveAll(_ context.Context, name string) error {
return &fs.PathError{
Op: "RemoveAll",
Path: name,
Err: fs.ErrPermission,
}
}
func (*webDAVFS) Rename(_ context.Context, from string, to string) error {
return &fs.PathError{
Op: "Rename",
Path: from,
Err: fs.ErrPermission,
}
}
func (w *webDAVFS) Open(name string) (fs.File, error) {
// use relative paths for FS
name = path.Join(".", name)
return w.FS.Open(name)
}
func (w *webDAVFS) OpenFile(ctx context.Context, name string, flag int, perm fs.FileMode) (webdav.File, error) {
// use relative paths for FS
name = path.Join(".", name)
if flag != os.O_RDONLY {
return nil, &fs.PathError{
Op: "OpenFile",
Path: name,
Err: fs.ErrPermission,
}
}
f, err := w.FS.Open(name)
if err != nil {
return nil, err
}
readdirFile, ok := f.(fs.ReadDirFile)
if !ok {
readdirFile = nil
}
seeker, ok := f.(io.Seeker)
if !ok {
seeker = nil
}
return &readOnlyFile{File: f, readDirFile: readdirFile, Seeker: seeker}, nil
}
func (w *webDAVFS) Stat(ctx context.Context, name string) (fs.FileInfo, error) {
// use relative paths for FS
name = path.Join(".", name)
f, err := w.FS.Open(name)
if err != nil {
return nil, err
}
fi, err := f.Stat()
if err != nil {
_ = f.Close()
return nil, err
}
err = f.Close()
if err != nil {
return nil, err
}
return fi, nil
}
type readOnlyFile struct {
fs.File
readDirFile fs.ReadDirFile
io.Seeker
}
func (f readOnlyFile) Write([]byte) (int, error) {
return 0, fs.ErrPermission
}
func (f readOnlyFile) Seek(offset int64, whence int) (int64, error) {
if f.Seeker == nil {
return 0, fs.ErrInvalid
}
return f.Seeker.Seek(offset, whence)
}
func (f readOnlyFile) Readdir(n int) ([]fs.FileInfo, error) {
if f.readDirFile == nil {
return nil, fs.ErrInvalid
}
entries, err := f.readDirFile.ReadDir(n)
if err != nil {
return nil, err
}
result := make([]fs.FileInfo, 0, len(entries))
for _, entry := range entries {
fi, err := entry.Info()
if err != nil {
return nil, fmt.Errorf("get fileinfo: %w", err)
}
result = append(result, fi)
}
return result, nil
}