Skip to content

Commit

Permalink
pkg: Implement wrapper for fs.FS and http.FileSystem (#538)
Browse files Browse the repository at this point in the history
* pkg: Implement wrapper for fs.FS

Signed-off-by: Xuanwo <github@xuanwo.io>

* Rename tp fswrap

Signed-off-by: Xuanwo <github@xuanwo.io>

* Fix build

Signed-off-by: Xuanwo <github@xuanwo.io>

* Add convert function

Signed-off-by: Xuanwo <github@xuanwo.io>
  • Loading branch information
Xuanwo authored Apr 23, 2021
1 parent bc0fae4 commit 4d27eff
Show file tree
Hide file tree
Showing 4 changed files with 350 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pkg/fswrap/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*
package iofswrap is a wrapper that convert go-storage services to fs.FS.
*/

package fswrap
52 changes: 52 additions & 0 deletions pkg/fswrap/fileinfo.go
Original file line number Diff line number Diff line change
@@ -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
}
125 changes: 125 additions & 0 deletions pkg/fswrap/httpfs.go
Original file line number Diff line number Diff line change
@@ -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
}
168 changes: 168 additions & 0 deletions pkg/fswrap/iofs.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 4d27eff

Please sign in to comment.