-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pkg: Implement wrapper for fs.FS and http.FileSystem (#538)
* 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
Showing
4 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |