Skip to content

Commit 4d27eff

Browse files
authored
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>
1 parent bc0fae4 commit 4d27eff

File tree

4 files changed

+350
-0
lines changed

4 files changed

+350
-0
lines changed

pkg/fswrap/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*
2+
package iofswrap is a wrapper that convert go-storage services to fs.FS.
3+
*/
4+
5+
package fswrap

pkg/fswrap/fileinfo.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package fswrap
2+
3+
import (
4+
"os"
5+
"time"
6+
7+
"github.com/aos-dev/go-storage/v3/types"
8+
)
9+
10+
type fileInfoWrapper struct {
11+
object *types.Object
12+
}
13+
14+
func (o fileInfoWrapper) Name() string {
15+
return o.object.Path
16+
}
17+
18+
func (o fileInfoWrapper) Size() int64 {
19+
return o.object.MustGetContentLength()
20+
}
21+
22+
func (o fileInfoWrapper) Mode() os.FileMode {
23+
return formatFileMode(o.object.Mode)
24+
}
25+
26+
func (o fileInfoWrapper) ModTime() time.Time {
27+
return o.object.MustGetLastModified()
28+
}
29+
30+
func (o fileInfoWrapper) IsDir() bool {
31+
return o.object.Mode.IsDir()
32+
}
33+
34+
// Sys will return internal Object.
35+
func (o fileInfoWrapper) Sys() interface{} {
36+
return o.object
37+
}
38+
39+
func formatFileMode(om types.ObjectMode) os.FileMode {
40+
var m os.FileMode
41+
42+
if om.IsDir() {
43+
m |= os.ModeDir
44+
}
45+
if om.IsAppend() {
46+
m |= os.ModeAppend
47+
}
48+
if om.IsLink() {
49+
m |= os.ModeSymlink
50+
}
51+
return m
52+
}

pkg/fswrap/httpfs.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package fswrap
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"net/http"
8+
"os"
9+
10+
"github.com/aos-dev/go-storage/v3/pairs"
11+
"github.com/aos-dev/go-storage/v3/types"
12+
)
13+
14+
var (
15+
_ http.FileSystem = httpFsWrapper{}
16+
17+
_ http.File = httpFileWrapper{}
18+
)
19+
20+
// HttpFs convert a Storager to http.FileSystem
21+
func HttpFs(s types.Storager) http.FileSystem {
22+
return httpFsWrapper{s}
23+
}
24+
25+
type httpFsWrapper struct {
26+
store types.Storager
27+
}
28+
29+
func (h httpFsWrapper) Open(name string) (http.File, error) {
30+
o, err := h.store.Stat(name)
31+
if err != nil {
32+
return nil, err
33+
}
34+
return &httpFileWrapper{store: h.store, object: o}, nil
35+
}
36+
37+
type httpFileWrapper struct {
38+
store types.Storager
39+
object *types.Object
40+
41+
offset int64
42+
buf bytes.Buffer
43+
}
44+
45+
func (h httpFileWrapper) Close() error {
46+
h.store = nil
47+
h.object = nil
48+
h.offset = 0
49+
return nil
50+
}
51+
52+
func (h httpFileWrapper) Read(bs []byte) (int, error) {
53+
size := int64(len(bs))
54+
55+
n, err := h.store.Read(h.object.Path, &h.buf, pairs.WithSize(size), pairs.WithOffset(h.offset))
56+
if err != nil {
57+
return int(n), err
58+
}
59+
h.offset += n
60+
61+
nn := copy(bs, h.buf.Bytes())
62+
h.buf.Reset() // Reset internal buffer after copy
63+
return nn, nil
64+
}
65+
66+
func (h httpFileWrapper) Seek(offset int64, whence int) (int64, error) {
67+
size := h.object.MustGetContentLength()
68+
69+
switch whence {
70+
case io.SeekStart:
71+
h.offset = offset
72+
case io.SeekCurrent:
73+
h.offset += offset
74+
case io.SeekEnd:
75+
// TODO: Do we need to check value here?
76+
h.offset = size - offset
77+
}
78+
return h.offset, nil
79+
}
80+
81+
func (h httpFileWrapper) Readdir(count int) ([]os.FileInfo, error) {
82+
if !h.object.Mode.IsDir() {
83+
return nil, os.ErrInvalid
84+
}
85+
86+
it, err := h.store.List(h.object.Path, pairs.WithListMode(types.ListModeDir))
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
// Change the meaning of n for the implementation below.
92+
//
93+
// The n above was for the public interface of "if n <= 0,
94+
// Readdir returns all the FileInfo from the directory in a
95+
// single slice".
96+
//
97+
// But below, we use only negative to mean looping until the
98+
// end and positive to mean bounded, with positive
99+
// terminating at 0.
100+
if count == 0 {
101+
count = -1
102+
}
103+
104+
fi := make([]os.FileInfo, 0)
105+
for count != 0 {
106+
o, err := it.Next()
107+
if err != nil && errors.Is(err, types.IterateDone) {
108+
break
109+
}
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
fi = append(fi, fileInfoWrapper{o})
115+
116+
if count > 0 {
117+
count--
118+
}
119+
}
120+
return fi, nil
121+
}
122+
123+
func (h httpFileWrapper) Stat() (os.FileInfo, error) {
124+
return &fileInfoWrapper{object: h.object}, nil
125+
}

pkg/fswrap/iofs.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//+build go1.16
2+
3+
package fswrap
4+
5+
import (
6+
"bytes"
7+
"errors"
8+
"io/fs"
9+
"path"
10+
11+
"github.com/aos-dev/go-storage/v3/pairs"
12+
"github.com/aos-dev/go-storage/v3/types"
13+
)
14+
15+
var (
16+
_ fs.FS = fsWrapper{}
17+
_ fs.GlobFS = fsWrapper{}
18+
_ fs.ReadDirFS = fsWrapper{}
19+
_ fs.ReadFileFS = fsWrapper{}
20+
_ fs.StatFS = fsWrapper{}
21+
_ fs.SubFS = fsWrapper{}
22+
23+
_ fs.File = &fileWrapper{}
24+
25+
_ fs.FileInfo = &fileInfoWrapper{}
26+
27+
_ fs.DirEntry = &dirEntryWrapper{}
28+
)
29+
30+
// Fs convert a Storager to fs.FS
31+
func Fs(s types.Storager) fs.FS {
32+
return fsWrapper{s}
33+
}
34+
35+
type fsWrapper struct {
36+
store types.Storager
37+
}
38+
39+
func (w fsWrapper) Open(name string) (fs.File, error) {
40+
o, err := w.store.Stat(name)
41+
if err != nil {
42+
return nil, err
43+
}
44+
return &fileWrapper{store: w.store, object: o}, nil
45+
}
46+
47+
func (w fsWrapper) Glob(name string) ([]string, error) {
48+
it, err := w.store.List("", pairs.WithListMode(types.ListModePrefix))
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
s := make([]string, 0)
54+
for {
55+
o, err := it.Next()
56+
if err != nil && errors.Is(err, types.IterateDone) {
57+
break
58+
}
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
ok, err := path.Match(name, o.Path)
64+
if err != nil {
65+
return nil, err
66+
}
67+
if ok {
68+
s = append(s, o.Path)
69+
}
70+
}
71+
return s, nil
72+
}
73+
74+
func (w fsWrapper) ReadDir(name string) ([]fs.DirEntry, error) {
75+
it, err := w.store.List(name, pairs.WithListMode(types.ListModeDir))
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
ds := make([]fs.DirEntry, 0)
81+
for {
82+
o, err := it.Next()
83+
if err != nil && errors.Is(err, types.IterateDone) {
84+
break
85+
}
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
ds = append(ds, dirEntryWrapper{o})
91+
}
92+
return ds, nil
93+
}
94+
95+
func (w fsWrapper) ReadFile(name string) ([]byte, error) {
96+
var buf bytes.Buffer
97+
98+
_, err := w.store.Read(name, &buf)
99+
if err != nil {
100+
return nil, err
101+
}
102+
return buf.Bytes(), nil
103+
}
104+
105+
func (w fsWrapper) Stat(name string) (fs.FileInfo, error) {
106+
o, err := w.store.Stat(name)
107+
if err != nil {
108+
return nil, err
109+
}
110+
return &fileInfoWrapper{object: o}, nil
111+
}
112+
113+
func (w fsWrapper) Sub(dir string) (fs.FS, error) {
114+
panic("implement me")
115+
}
116+
117+
type fileWrapper struct {
118+
store types.Storager
119+
object *types.Object
120+
121+
offset int64
122+
buf bytes.Buffer
123+
}
124+
125+
func (o fileWrapper) Stat() (fs.FileInfo, error) {
126+
return &fileInfoWrapper{o.object}, nil
127+
}
128+
129+
func (o fileWrapper) Read(bs []byte) (int, error) {
130+
size := int64(len(bs))
131+
132+
n, err := o.store.Read(o.object.Path, &o.buf, pairs.WithSize(size), pairs.WithOffset(o.offset))
133+
if err != nil {
134+
return int(n), err
135+
}
136+
o.offset += n
137+
138+
nn := copy(bs, o.buf.Bytes())
139+
o.buf.Reset() // Reset internal buffer after copy
140+
return nn, nil
141+
}
142+
143+
func (o fileWrapper) Close() error {
144+
o.store = nil
145+
o.object = nil
146+
o.offset = 0
147+
return nil
148+
}
149+
150+
type dirEntryWrapper struct {
151+
object *types.Object
152+
}
153+
154+
func (d dirEntryWrapper) Name() string {
155+
return d.object.Path
156+
}
157+
158+
func (d dirEntryWrapper) IsDir() bool {
159+
return d.object.Mode.IsDir()
160+
}
161+
162+
func (d dirEntryWrapper) Type() fs.FileMode {
163+
return formatFileMode(d.object.Mode)
164+
}
165+
166+
func (d dirEntryWrapper) Info() (fs.FileInfo, error) {
167+
return &fileInfoWrapper{d.object}, nil
168+
}

0 commit comments

Comments
 (0)