diff --git a/pkg/fswrap/doc.go b/pkg/fswrap/doc.go new file mode 100644 index 000000000..91bee7057 --- /dev/null +++ b/pkg/fswrap/doc.go @@ -0,0 +1,5 @@ +/* +package iofswrap is a wrapper that convert go-storage services to fs.FS. +*/ + +package fswrap diff --git a/pkg/fswrap/fileinfo.go b/pkg/fswrap/fileinfo.go new file mode 100644 index 000000000..0f3db2a49 --- /dev/null +++ b/pkg/fswrap/fileinfo.go @@ -0,0 +1,52 @@ +package fswrap + +import ( + "os" + "time" + + "github.com/aos-dev/go-storage/v3/types" +) + +type fileInfoWrapper struct { + object *types.Object +} + +func (o fileInfoWrapper) Name() string { + return o.object.Path +} + +func (o fileInfoWrapper) Size() int64 { + return o.object.MustGetContentLength() +} + +func (o fileInfoWrapper) Mode() os.FileMode { + return formatFileMode(o.object.Mode) +} + +func (o fileInfoWrapper) ModTime() time.Time { + return o.object.MustGetLastModified() +} + +func (o fileInfoWrapper) IsDir() bool { + return o.object.Mode.IsDir() +} + +// Sys will return internal Object. +func (o fileInfoWrapper) Sys() interface{} { + return o.object +} + +func formatFileMode(om types.ObjectMode) os.FileMode { + var m os.FileMode + + if om.IsDir() { + m |= os.ModeDir + } + if om.IsAppend() { + m |= os.ModeAppend + } + if om.IsLink() { + m |= os.ModeSymlink + } + return m +} diff --git a/pkg/fswrap/httpfs.go b/pkg/fswrap/httpfs.go new file mode 100644 index 000000000..cf6ba5c2c --- /dev/null +++ b/pkg/fswrap/httpfs.go @@ -0,0 +1,125 @@ +package fswrap + +import ( + "bytes" + "errors" + "io" + "net/http" + "os" + + "github.com/aos-dev/go-storage/v3/pairs" + "github.com/aos-dev/go-storage/v3/types" +) + +var ( + _ http.FileSystem = httpFsWrapper{} + + _ http.File = httpFileWrapper{} +) + +// HttpFs convert a Storager to http.FileSystem +func HttpFs(s types.Storager) http.FileSystem { + return httpFsWrapper{s} +} + +type httpFsWrapper struct { + store types.Storager +} + +func (h httpFsWrapper) Open(name string) (http.File, error) { + o, err := h.store.Stat(name) + if err != nil { + return nil, err + } + return &httpFileWrapper{store: h.store, object: o}, nil +} + +type httpFileWrapper struct { + store types.Storager + object *types.Object + + offset int64 + buf bytes.Buffer +} + +func (h httpFileWrapper) Close() error { + h.store = nil + h.object = nil + h.offset = 0 + return nil +} + +func (h httpFileWrapper) Read(bs []byte) (int, error) { + size := int64(len(bs)) + + n, err := h.store.Read(h.object.Path, &h.buf, pairs.WithSize(size), pairs.WithOffset(h.offset)) + if err != nil { + return int(n), err + } + h.offset += n + + nn := copy(bs, h.buf.Bytes()) + h.buf.Reset() // Reset internal buffer after copy + return nn, nil +} + +func (h httpFileWrapper) Seek(offset int64, whence int) (int64, error) { + size := h.object.MustGetContentLength() + + switch whence { + case io.SeekStart: + h.offset = offset + case io.SeekCurrent: + h.offset += offset + case io.SeekEnd: + // TODO: Do we need to check value here? + h.offset = size - offset + } + return h.offset, nil +} + +func (h httpFileWrapper) Readdir(count int) ([]os.FileInfo, error) { + if !h.object.Mode.IsDir() { + return nil, os.ErrInvalid + } + + it, err := h.store.List(h.object.Path, pairs.WithListMode(types.ListModeDir)) + if err != nil { + return nil, err + } + + // Change the meaning of n for the implementation below. + // + // The n above was for the public interface of "if n <= 0, + // Readdir returns all the FileInfo from the directory in a + // single slice". + // + // But below, we use only negative to mean looping until the + // end and positive to mean bounded, with positive + // terminating at 0. + if count == 0 { + count = -1 + } + + fi := make([]os.FileInfo, 0) + for count != 0 { + o, err := it.Next() + if err != nil && errors.Is(err, types.IterateDone) { + break + } + if err != nil { + return nil, err + } + + fi = append(fi, fileInfoWrapper{o}) + + if count > 0 { + count-- + } + } + return fi, nil +} + +func (h httpFileWrapper) Stat() (os.FileInfo, error) { + return &fileInfoWrapper{object: h.object}, nil +} diff --git a/pkg/fswrap/iofs.go b/pkg/fswrap/iofs.go new file mode 100644 index 000000000..2dabbff8e --- /dev/null +++ b/pkg/fswrap/iofs.go @@ -0,0 +1,168 @@ +//+build go1.16 + +package fswrap + +import ( + "bytes" + "errors" + "io/fs" + "path" + + "github.com/aos-dev/go-storage/v3/pairs" + "github.com/aos-dev/go-storage/v3/types" +) + +var ( + _ fs.FS = fsWrapper{} + _ fs.GlobFS = fsWrapper{} + _ fs.ReadDirFS = fsWrapper{} + _ fs.ReadFileFS = fsWrapper{} + _ fs.StatFS = fsWrapper{} + _ fs.SubFS = fsWrapper{} + + _ fs.File = &fileWrapper{} + + _ fs.FileInfo = &fileInfoWrapper{} + + _ fs.DirEntry = &dirEntryWrapper{} +) + +// Fs convert a Storager to fs.FS +func Fs(s types.Storager) fs.FS { + return fsWrapper{s} +} + +type fsWrapper struct { + store types.Storager +} + +func (w fsWrapper) Open(name string) (fs.File, error) { + o, err := w.store.Stat(name) + if err != nil { + return nil, err + } + return &fileWrapper{store: w.store, object: o}, nil +} + +func (w fsWrapper) Glob(name string) ([]string, error) { + it, err := w.store.List("", pairs.WithListMode(types.ListModePrefix)) + if err != nil { + return nil, err + } + + s := make([]string, 0) + for { + o, err := it.Next() + if err != nil && errors.Is(err, types.IterateDone) { + break + } + if err != nil { + return nil, err + } + + ok, err := path.Match(name, o.Path) + if err != nil { + return nil, err + } + if ok { + s = append(s, o.Path) + } + } + return s, nil +} + +func (w fsWrapper) ReadDir(name string) ([]fs.DirEntry, error) { + it, err := w.store.List(name, pairs.WithListMode(types.ListModeDir)) + if err != nil { + return nil, err + } + + ds := make([]fs.DirEntry, 0) + for { + o, err := it.Next() + if err != nil && errors.Is(err, types.IterateDone) { + break + } + if err != nil { + return nil, err + } + + ds = append(ds, dirEntryWrapper{o}) + } + return ds, nil +} + +func (w fsWrapper) ReadFile(name string) ([]byte, error) { + var buf bytes.Buffer + + _, err := w.store.Read(name, &buf) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (w fsWrapper) Stat(name string) (fs.FileInfo, error) { + o, err := w.store.Stat(name) + if err != nil { + return nil, err + } + return &fileInfoWrapper{object: o}, nil +} + +func (w fsWrapper) Sub(dir string) (fs.FS, error) { + panic("implement me") +} + +type fileWrapper struct { + store types.Storager + object *types.Object + + offset int64 + buf bytes.Buffer +} + +func (o fileWrapper) Stat() (fs.FileInfo, error) { + return &fileInfoWrapper{o.object}, nil +} + +func (o fileWrapper) Read(bs []byte) (int, error) { + size := int64(len(bs)) + + n, err := o.store.Read(o.object.Path, &o.buf, pairs.WithSize(size), pairs.WithOffset(o.offset)) + if err != nil { + return int(n), err + } + o.offset += n + + nn := copy(bs, o.buf.Bytes()) + o.buf.Reset() // Reset internal buffer after copy + return nn, nil +} + +func (o fileWrapper) Close() error { + o.store = nil + o.object = nil + o.offset = 0 + return nil +} + +type dirEntryWrapper struct { + object *types.Object +} + +func (d dirEntryWrapper) Name() string { + return d.object.Path +} + +func (d dirEntryWrapper) IsDir() bool { + return d.object.Mode.IsDir() +} + +func (d dirEntryWrapper) Type() fs.FileMode { + return formatFileMode(d.object.Mode) +} + +func (d dirEntryWrapper) Info() (fs.FileInfo, error) { + return &fileInfoWrapper{d.object}, nil +}