forked from TrueCloudLab/restic
wip
This commit is contained in:
parent
bd3022c504
commit
b3f38686ee
7 changed files with 324 additions and 143 deletions
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"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{
|
srv := &http.Server{
|
||||||
ReadTimeout: 60 * time.Second,
|
ReadTimeout: 60 * time.Second,
|
||||||
WriteTimeout: 60 * time.Second,
|
WriteTimeout: 60 * time.Second,
|
||||||
Addr: opts.Listen,
|
Addr: opts.Listen,
|
||||||
Handler: http.FileServer(http.FS(root)),
|
// Handler: http.FileServer(http.FS(root)),
|
||||||
ErrorLog: errorLogger,
|
Handler: &webdav.Handler{
|
||||||
|
FileSystem: rofs.WebDAVFS(root),
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
Logger: logRequest,
|
||||||
|
},
|
||||||
|
ErrorLog: errorLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
return srv.ListenAndServe()
|
return srv.ListenAndServe()
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (d dirEntry) Info() (fs.FileInfo, error) { return d.fileInfo, nil }
|
||||||
// openDir represents a directory opened for reading.
|
// openDir represents a directory opened for reading.
|
||||||
type openDir struct {
|
type openDir struct {
|
||||||
path string
|
path string
|
||||||
fileInfo FileInfo
|
fileInfo fileInfo
|
||||||
entries []fs.DirEntry
|
entries []fs.DirEntry
|
||||||
offset int
|
offset int
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,19 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileInfo provides information about a file or directory.
|
// fileInfo provides information about a file or directory.
|
||||||
type FileInfo struct {
|
type fileInfo struct {
|
||||||
name string
|
name string
|
||||||
mode fs.FileMode
|
mode fs.FileMode
|
||||||
modtime time.Time
|
modtime time.Time
|
||||||
size int64
|
size int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi FileInfo) Name() string { return fi.name }
|
func (fi fileInfo) Name() string { return fi.name }
|
||||||
func (fi FileInfo) IsDir() bool { return fi.mode.IsDir() }
|
func (fi fileInfo) IsDir() bool { return fi.mode.IsDir() }
|
||||||
func (fi FileInfo) ModTime() time.Time { return fi.modtime }
|
func (fi fileInfo) ModTime() time.Time { return fi.modtime }
|
||||||
func (fi FileInfo) Mode() fs.FileMode { return fi.mode }
|
func (fi fileInfo) Mode() fs.FileMode { return fi.mode }
|
||||||
func (fi FileInfo) Size() int64 { return fi.size }
|
func (fi fileInfo) Size() int64 { return fi.size }
|
||||||
func (fi FileInfo) Sys() any { return nil }
|
func (fi fileInfo) Sys() any { return nil }
|
||||||
|
|
||||||
var _ fs.FileInfo = FileInfo{}
|
var _ fs.FileInfo = fileInfo{}
|
||||||
|
|
|
@ -16,7 +16,7 @@ type ROFS struct {
|
||||||
repo restic.Repository
|
repo restic.Repository
|
||||||
cfg Config
|
cfg Config
|
||||||
entries map[string]rofsEntry
|
entries map[string]rofsEntry
|
||||||
fileInfo FileInfo
|
fileInfo fileInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type rofsEntry interface {
|
type rofsEntry interface {
|
||||||
|
@ -50,7 +50,7 @@ func New(ctx context.Context, repo restic.Repository, cfg Config) (*ROFS, error)
|
||||||
repo: repo,
|
repo: repo,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
entries: make(map[string]rofsEntry),
|
entries: make(map[string]rofsEntry),
|
||||||
fileInfo: FileInfo{
|
fileInfo: fileInfo{
|
||||||
name: ".",
|
name: ".",
|
||||||
mode: 0755,
|
mode: 0755,
|
||||||
modtime: time.Now(),
|
modtime: time.Now(),
|
||||||
|
@ -65,6 +65,43 @@ func New(ctx context.Context, repo restic.Repository, cfg Config) (*ROFS, error)
|
||||||
return rofs, nil
|
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.
|
// Open opens the named file.
|
||||||
//
|
//
|
||||||
// When Open returns an error, it should be of type *PathError
|
// 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{
|
d := &openDir{
|
||||||
path: ".",
|
path: ".",
|
||||||
fileInfo: FileInfo{
|
fileInfo: fileInfo{
|
||||||
name: ".",
|
name: ".",
|
||||||
mode: fs.ModeDir | 0555,
|
mode: fs.ModeDir | 0555,
|
||||||
modtime: time.Now(),
|
modtime: time.Now(),
|
||||||
|
@ -115,38 +152,3 @@ func (rofs *ROFS) Open(name string) (fs.File, error) {
|
||||||
|
|
||||||
return entry.Open()
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
type MemFile struct {
|
type MemFile struct {
|
||||||
Path string
|
Path string
|
||||||
FileInfo FileInfo
|
FileInfo fileInfo
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ func NewMemFile(filename string, data []byte, modTime time.Time) MemFile {
|
||||||
return MemFile{
|
return MemFile{
|
||||||
Path: filename,
|
Path: filename,
|
||||||
Data: data,
|
Data: data,
|
||||||
FileInfo: FileInfo{
|
FileInfo: fileInfo{
|
||||||
name: path.Base(filename),
|
name: path.Base(filename),
|
||||||
size: int64(len(data)),
|
size: int64(len(data)),
|
||||||
mode: 0644,
|
mode: 0644,
|
||||||
|
@ -47,7 +47,7 @@ func (f MemFile) DirEntry() fs.DirEntry {
|
||||||
type openMemFile struct {
|
type openMemFile struct {
|
||||||
path string
|
path string
|
||||||
|
|
||||||
fileInfo FileInfo
|
fileInfo fileInfo
|
||||||
data []byte
|
data []byte
|
||||||
|
|
||||||
offset int64
|
offset int64
|
||||||
|
|
|
@ -1,117 +1,134 @@
|
||||||
package rofs
|
package rofs
|
||||||
|
|
||||||
import (
|
// // SnapshotsDir implements a tree of snapshots in repo as a file system in various sub-directories.
|
||||||
"io"
|
// type SnapshotsDir struct {
|
||||||
"io/fs"
|
// lastUpdate time.Time
|
||||||
"slices"
|
|
||||||
"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.
|
// // list of top-level directories
|
||||||
type SnapshotsDir struct {
|
// entries []rofsEntry
|
||||||
modTime time.Time
|
// }
|
||||||
|
|
||||||
pathTemplates []string
|
// // ensure that the interface is implemented
|
||||||
timeTemplate string
|
// var _ rofsEntry = &SnapshotsDir{}
|
||||||
|
|
||||||
// prepare the list of top-level directories
|
// // NewSnapshotsDir initializes a new top-level snapshots directory.
|
||||||
entries []fs.DirEntry
|
// 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
|
// // testnames := []string{"foo", "bar", "baz", "snapshots"}
|
||||||
entriesRemaining []fs.DirEntry
|
// // 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.
|
// // slices.SortFunc(dir.entries, func(a, b fs.DirEntry) int {
|
||||||
func NewSnapshotsDir(pathTemplates []string, timeTemplate string) *SnapshotsDir {
|
// // if a.Name() == b.Name() {
|
||||||
dir := &SnapshotsDir{
|
// // return 0
|
||||||
pathTemplates: pathTemplates,
|
// // }
|
||||||
timeTemplate: timeTemplate,
|
|
||||||
modTime: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
testnames := []string{"foo", "bar", "baz", "snapshots"}
|
// // if a.Name() < b.Name() {
|
||||||
for _, name := range testnames {
|
// // return 1
|
||||||
dir.entries = append(dir.entries,
|
// // }
|
||||||
fs.FileInfoToDirEntry(FileInfo{
|
|
||||||
name: name,
|
|
||||||
mode: 0644,
|
|
||||||
modtime: time.Now(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.SortFunc(dir.entries, func(a, b fs.DirEntry) int {
|
// // return -1
|
||||||
if a.Name() == b.Name() {
|
// // })
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Name() < b.Name() {
|
// // // prepare for readdir with positive n
|
||||||
return 1
|
// // dir.entriesRemaining = dir.entries
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
// return dir
|
||||||
})
|
// }
|
||||||
|
|
||||||
// prepare for readdir with positive n
|
// // ensure that it implements all necessary interfaces.
|
||||||
dir.entriesRemaining = dir.entries
|
// 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.
|
// // reset readdir list
|
||||||
var _ fs.ReadDirFile = &SnapshotsDir{}
|
// // dir.entriesRemaining = dir.entries
|
||||||
|
|
||||||
// Close closes the snapshots dir.
|
// return nil
|
||||||
func (dir *SnapshotsDir) Close() error {
|
// }
|
||||||
debug.Log("Close()")
|
|
||||||
|
|
||||||
// reset readdir list
|
// // Read is not implemented for a dir.
|
||||||
dir.entriesRemaining = dir.entries
|
// 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.
|
// fi := FileInfo{
|
||||||
func (dir *SnapshotsDir) Read([]byte) (int, error) {
|
// name: "root", // use special name, this is the root node
|
||||||
return 0, &fs.PathError{
|
// size: 0,
|
||||||
Op: "read",
|
// modtime: dir.lastUpdate,
|
||||||
Err: fs.ErrInvalid,
|
// mode: 0755,
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// Stat returns information about the dir.
|
// return fi, nil
|
||||||
func (dir *SnapshotsDir) Stat() (fs.FileInfo, error) {
|
// }
|
||||||
debug.Log("Stat(root)")
|
|
||||||
|
|
||||||
fi := FileInfo{
|
// // ReadDir returns a list of entries.
|
||||||
name: "root", // use special name, this is the root node
|
// func (dir *SnapshotsDir) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||||
size: 0,
|
// if n < 0 {
|
||||||
modtime: dir.modTime,
|
// debug.Log("Readdir(root, %v), return %v entries", n, len(dir.entries))
|
||||||
mode: 0755,
|
// 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.
|
// if n == 0 {
|
||||||
func (dir *SnapshotsDir) ReadDir(n int) ([]fs.DirEntry, error) {
|
// return nil, io.EOF
|
||||||
if n < 0 {
|
// }
|
||||||
debug.Log("Readdir(root, %v), return %v entries", n, len(dir.entries))
|
|
||||||
return dir.entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// complicated pointer handling
|
// list := dir.entriesRemaining[:n]
|
||||||
if n > len(dir.entriesRemaining) {
|
// dir.entriesRemaining = dir.entriesRemaining[n:]
|
||||||
n = len(dir.entriesRemaining)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n == 0 {
|
// return list, nil
|
||||||
return nil, io.EOF
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
list := dir.entriesRemaining[:n]
|
// // DirEntry returns meta data about the dir snapshots dir itself.
|
||||||
dir.entriesRemaining = dir.entriesRemaining[n:]
|
// 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
|
||||||
|
// }
|
||||||
|
|
152
internal/server/rofs/webdav_fs.go
Normal file
152
internal/server/rofs/webdav_fs.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue