Skip to content

Commit fbb5d45

Browse files
authored
Create vfs.go
1 parent 622239b commit fbb5d45

1 file changed

Lines changed: 336 additions & 0 deletions

File tree

source-code/src/vfs.go

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
package src
2+
3+
import (
4+
"archive/tar"
5+
"archive/zip"
6+
"compress/gzip"
7+
"io"
8+
"io/fs"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
"time"
13+
14+
"github.com/pkg/sftp"
15+
"golang.org/x/crypto/ssh"
16+
)
17+
18+
type vfsHandler interface {
19+
ReadDir(dir string) ([]fs.DirEntry, error)
20+
Open(file string) (fs.File, error)
21+
Stat(file string) (fs.FileInfo, error)
22+
Chdir(dir string) error
23+
Getwd() (string, error)
24+
}
25+
26+
type localVFS struct{}
27+
28+
func (l localVFS) ReadDir(dir string) ([]fs.DirEntry, error) { return os.ReadDir(dir) }
29+
func (l localVFS) Open(file string) (fs.File, error) { return os.Open(file) }
30+
func (l localVFS) Stat(file string) (fs.FileInfo, error) { return os.Stat(file) }
31+
func (l localVFS) Chdir(dir string) error { return os.Chdir(dir) }
32+
func (l localVFS) Getwd() (string, error) { return os.Getwd() }
33+
34+
type sftpVFS struct {
35+
client *sftp.Client
36+
}
37+
38+
func newSFTPVFS(host, user, pass string) (*sftpVFS, error) {
39+
config := &ssh.ClientConfig{
40+
User: user,
41+
Auth: []ssh.AuthMethod{ssh.Password(pass)},
42+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
43+
}
44+
conn, err := ssh.Dial("tcp", host, config)
45+
if err != nil {
46+
return nil, err
47+
}
48+
client, err := sftp.NewClient(conn)
49+
if err != nil {
50+
return nil, err
51+
}
52+
return &sftpVFS{client: client}, nil
53+
}
54+
55+
func (s *sftpVFS) ReadDir(dir string) ([]fs.DirEntry, error) {
56+
files, err := s.client.ReadDir(dir)
57+
if err != nil {
58+
return nil, err
59+
}
60+
var entries []fs.DirEntry
61+
for _, f := range files {
62+
entries = append(entries, &sftpDirEntry{info: f})
63+
}
64+
return entries, nil
65+
}
66+
67+
func (s *sftpVFS) Open(file string) (fs.File, error) { return s.client.Open(file) }
68+
func (s *sftpVFS) Stat(file string) (fs.FileInfo, error) { return s.client.Stat(file) }
69+
func (s *sftpVFS) Chdir(dir string) error {
70+
_, err := s.client.Stat(dir)
71+
return err
72+
}
73+
func (s *sftpVFS) Getwd() (string, error) { return s.client.Getwd() }
74+
75+
type sftpDirEntry struct {
76+
info fs.FileInfo
77+
}
78+
79+
func (e *sftpDirEntry) Name() string { return e.info.Name() }
80+
func (e *sftpDirEntry) IsDir() bool { return e.info.IsDir() }
81+
func (e *sftpDirEntry) Type() fs.FileMode { return e.info.Mode() }
82+
func (e *sftpDirEntry) Info() (fs.FileInfo, error) { return e.info, nil }
83+
84+
type tarVFS struct {
85+
filename string
86+
isGz bool
87+
entries map[string]*tar.Header
88+
}
89+
90+
func newTarVFS(filename string) (*tarVFS, error) {
91+
f, err := os.Open(filename)
92+
if err != nil {
93+
return nil, err
94+
}
95+
defer f.Close()
96+
var r io.Reader = f
97+
isGz := strings.HasSuffix(filename, ".gz")
98+
var gzr *gzip.Reader
99+
if isGz {
100+
gzr, err = gzip.NewReader(f)
101+
if err != nil {
102+
return nil, err
103+
}
104+
r = gzr
105+
}
106+
tr := tar.NewReader(r)
107+
entries := make(map[string]*tar.Header)
108+
for {
109+
hdr, err := tr.Next()
110+
if err == io.EOF {
111+
break
112+
}
113+
if err != nil {
114+
return nil, err
115+
}
116+
entries[hdr.Name] = hdr
117+
}
118+
if isGz {
119+
gzr.Close()
120+
}
121+
return &tarVFS{filename: filename, isGz: isGz, entries: entries}, nil
122+
}
123+
124+
func (t *tarVFS) ReadDir(dir string) ([]fs.DirEntry, error) {
125+
var entries []fs.DirEntry
126+
prefix := strings.TrimPrefix(dir, t.filename+"/") + "/"
127+
for name, hdr := range t.entries {
128+
if strings.HasPrefix(name, prefix) {
129+
suffix := name[len(prefix):]
130+
if strings.Contains(suffix, "/") {
131+
continue
132+
}
133+
entries = append(entries, &tarDirEntry{name: filepath.Base(name), hdr: hdr})
134+
}
135+
}
136+
return entries, nil
137+
}
138+
139+
func (t *tarVFS) Open(path string) (fs.File, error) {
140+
f, err := os.Open(t.filename)
141+
if err != nil {
142+
return nil, err
143+
}
144+
var r io.Reader = f
145+
var closer io.Closer = f
146+
if t.isGz {
147+
gzr, err := gzip.NewReader(f)
148+
if err != nil {
149+
f.Close()
150+
return nil, err
151+
}
152+
r = gzr
153+
closer = gzr
154+
}
155+
tr := tar.NewReader(r)
156+
for {
157+
hdr, err := tr.Next()
158+
if err == io.EOF {
159+
closer.Close()
160+
return nil, fs.ErrNotExist
161+
}
162+
if err != nil {
163+
closer.Close()
164+
return nil, err
165+
}
166+
if hdr.Name == path {
167+
return &tarFile{reader: io.LimitReader(tr, hdr.Size), closer: closer, name: path, size: hdr.Size}, nil
168+
}
169+
}
170+
}
171+
172+
func (t *tarVFS) Stat(path string) (fs.FileInfo, error) {
173+
hdr, ok := t.entries[path]
174+
if !ok {
175+
return nil, fs.ErrNotExist
176+
}
177+
return hdr.FileInfo(), nil
178+
}
179+
180+
func (t *tarVFS) Chdir(dir string) error {
181+
return nil
182+
}
183+
184+
func (t *tarVFS) Getwd() (string, error) { return t.filename, nil }
185+
186+
type tarDirEntry struct {
187+
name string
188+
hdr *tar.Header
189+
}
190+
191+
func (e *tarDirEntry) Name() string { return e.name }
192+
func (e *tarDirEntry) IsDir() bool { return e.hdr.Typeflag == tar.TypeDir }
193+
func (e *tarDirEntry) Type() fs.FileMode { return e.hdr.FileInfo().Mode() }
194+
func (e *tarDirEntry) Info() (fs.FileInfo, error) { return e.hdr.FileInfo(), nil }
195+
196+
type tarFile struct {
197+
reader io.Reader
198+
closer io.Closer
199+
name string
200+
size int64
201+
pos int64
202+
}
203+
204+
func (tf *tarFile) Read(b []byte) (int, error) {
205+
n, err := tf.reader.Read(b)
206+
tf.pos += int64(n)
207+
return n, err
208+
}
209+
210+
func (tf *tarFile) Close() error { return tf.closer.Close() }
211+
212+
func (tf *tarFile) Stat() (fs.FileInfo, error) {
213+
return &tarFileInfo{name: tf.name, size: tf.size}, nil
214+
}
215+
216+
type tarFileInfo struct {
217+
name string
218+
size int64
219+
}
220+
221+
func (i *tarFileInfo) Name() string { return i.name }
222+
func (i *tarFileInfo) Size() int64 { return i.size }
223+
func (i *tarFileInfo) Mode() fs.FileMode { return 0644 }
224+
func (i *tarFileInfo) ModTime() time.Time { return time.Now() }
225+
func (i *tarFileInfo) IsDir() bool { return false }
226+
func (i *tarFileInfo) Sys() any { return nil }
227+
228+
type zipVFS struct {
229+
filename string
230+
reader *zip.Reader
231+
file *os.File
232+
}
233+
234+
func newZipVFS(filename string) (*zipVFS, error) {
235+
f, err := os.Open(filename)
236+
if err != nil {
237+
return nil, err
238+
}
239+
fi, err := f.Stat()
240+
if err != nil {
241+
f.Close()
242+
return nil, err
243+
}
244+
r, err := zip.NewReader(f, fi.Size())
245+
if err != nil {
246+
f.Close()
247+
return nil, err
248+
}
249+
return &zipVFS{filename: filename, reader: r, file: f}, nil
250+
}
251+
252+
func (z *zipVFS) ReadDir(dir string) ([]fs.DirEntry, error) {
253+
var entries []fs.DirEntry
254+
prefix := strings.TrimPrefix(dir, z.filename+"/") + "/"
255+
seenDirs := make(map[string]bool)
256+
for _, f := range z.reader.File {
257+
name := f.Name
258+
if strings.HasPrefix(name, prefix) {
259+
suffix := name[len(prefix):]
260+
if suffix == "" {
261+
continue
262+
}
263+
if idx := strings.Index(suffix, "/"); idx != -1 {
264+
dirName := suffix[:idx]
265+
if seenDirs[dirName] {
266+
continue
267+
}
268+
seenDirs[dirName] = true
269+
entries = append(entries, &zipDirEntry{f: &zip.File{FileHeader: zip.FileHeader{Name: dirName}}, isDir: true})
270+
} else {
271+
entries = append(entries, &zipDirEntry{f: f, isDir: false})
272+
}
273+
}
274+
}
275+
return entries, nil
276+
}
277+
278+
func (z *zipVFS) Open(path string) (fs.File, error) {
279+
for _, f := range z.reader.File {
280+
if f.Name == path {
281+
rc, err := f.Open()
282+
if err != nil {
283+
return nil, err
284+
}
285+
return &zipFile{reader: rc, name: path, size: int64(f.UncompressedSize64)}, nil
286+
}
287+
}
288+
return nil, fs.ErrNotExist
289+
}
290+
291+
func (z *zipVFS) Stat(path string) (fs.FileInfo, error) {
292+
for _, f := range z.reader.File {
293+
if f.Name == path {
294+
return f.FileInfo(), nil
295+
}
296+
}
297+
return nil, fs.ErrNotExist
298+
}
299+
300+
func (z *zipVFS) Chdir(dir string) error {
301+
return nil
302+
}
303+
304+
func (z *zipVFS) Getwd() (string, error) { return z.filename, nil }
305+
306+
type zipDirEntry struct {
307+
f *zip.File
308+
isDir bool
309+
}
310+
311+
func (e *zipDirEntry) Name() string { return e.f.Name }
312+
func (e *zipDirEntry) IsDir() bool { return e.isDir }
313+
func (e *zipDirEntry) Type() fs.FileMode { return e.f.Mode() }
314+
func (e *zipDirEntry) Info() (fs.FileInfo, error) { return e.f.FileInfo(), nil }
315+
316+
type zipFile struct {
317+
reader io.ReadCloser
318+
name string
319+
size int64
320+
}
321+
322+
func (zf *zipFile) Read(b []byte) (int, error) { return zf.reader.Read(b) }
323+
func (zf *zipFile) Close() error { return zf.reader.Close() }
324+
func (zf *zipFile) Stat() (fs.FileInfo, error) { return &zipFileInfo{name: zf.name, size: zf.size}, nil }
325+
326+
type zipFileInfo struct {
327+
name string
328+
size int64
329+
}
330+
331+
func (i *zipFileInfo) Name() string { return i.name }
332+
func (i *zipFileInfo) Size() int64 { return i.size }
333+
func (i *zipFileInfo) Mode() fs.FileMode { return 0644 }
334+
func (i *zipFileInfo) ModTime() time.Time { return time.Now() }
335+
func (i *zipFileInfo) IsDir() bool { return false }
336+
func (i *zipFileInfo) Sys() any { return nil }

0 commit comments

Comments
 (0)