-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopen.go
More file actions
186 lines (164 loc) · 4.64 KB
/
Copy pathopen.go
File metadata and controls
186 lines (164 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package fs
import (
"archive/tar"
"context"
"errors"
"io"
"strings"
"lesiw.io/fs/path"
)
// An FS is a file system with the Open method.
type FS interface {
// Open opens the named file for reading.
//
// The returned reader must be closed when done. The reader may also
// implement io.Seeker, io.ReaderAt, or other interfaces depending
// on the implementation.
Open(ctx context.Context, name string) (io.ReadCloser, error)
}
// A DirFS is a file system that can read directories as tar streams.
//
// DirFS is an optional interface that enables efficient bulk reads via tar
// archives, particularly useful for read-only filesystems or transferring many
// small files from remote filesystems. When not implemented, directory
// operations fall back to walking the filesystem and creating tar archives
// manually.
type DirFS interface {
FS
// OpenDir opens a tar stream for reading from the specified directory.
// The directory is archived as a tar stream that can be read until EOF.
//
// The returned reader must be closed when done.
OpenDir(ctx context.Context, dir string) (io.ReadCloser, error)
}
// Open opens the named file or directory for reading.
// Analogous to: [io/fs.Open], [os.Open], cat, tar, 9P Topen, S3 GetObject.
//
// All paths use forward slashes (/) regardless of the operating system,
// following [io/fs] conventions. Use the [path] package (not [path/filepath])
// for path manipulation. Implementations handle OS-specific conversion
// internally.
//
// The returned [ReadPathCloser] must be closed when done. The Path() method
// returns the native filesystem path, or the input path if localization is
// not supported.
//
// # Files
//
// Returns a [ReadPathCloser] for reading the file contents.
//
// Requires: [FS]
//
// # Directories
//
// A trailing slash returns a tar archive stream of the directory contents.
// A path identified as a directory via [StatFS] also returns a tar archive.
//
// Requires: [DirFS] || ([FS] && ([ReadDirFS] || [WalkFS]))
func Open(ctx context.Context, fsys FS, name string) (ReadPathCloser, error) {
name, err := localizePath(ctx, fsys, name)
if err != nil {
return nil, err
}
if path.IsDir(name) {
r, err := openDirAsTar(ctx, fsys, name)
if err != nil {
return nil, err
}
return readPathCloser(r, name), nil
}
if sfs, ok := fsys.(StatFS); ok {
info, err := sfs.Stat(ctx, name)
if err == nil && info.IsDir() {
r, err := openDirAsTar(ctx, fsys, name)
if err != nil {
return nil, err
}
return readPathCloser(r, name), nil
}
}
r, err := fsys.Open(ctx, name)
if err != nil {
return nil, err
}
return readPathCloser(r, name), nil
}
func openDirAsTar(ctx context.Context, fsys FS, dir string) (io.ReadCloser, error) {
dir = path.Dir(dir)
if tfs, ok := fsys.(DirFS); ok {
r, err := tfs.OpenDir(ctx, dir)
if err != nil && !errors.Is(err, ErrUnsupported) {
return nil, err
}
if err == nil {
return r, nil
}
}
return walkDirAsTar(ctx, fsys, dir)
}
func walkDirAsTar(ctx context.Context, fsys FS, dir string) (io.ReadCloser, error) {
pr, pw := io.Pipe()
go func() {
err := createTarFromFS(ctx, fsys, dir, pw)
pw.CloseWithError(err)
}()
return pr, nil
}
// createTarFromFS walks the filesystem and creates a tar archive.
func createTarFromFS(ctx context.Context, fsys FS, dir string, w io.Writer) error {
dir = path.Clean(dir)
tw := tar.NewWriter(w)
defer tw.Close()
// Walk all entries and add to tar
var walkPath func(string, int) error
walkPath = func(currentPath string, currentDepth int) error {
for entry, err := range ReadDir(ctx, fsys, currentPath) {
if err != nil {
return err
}
// Build full path
entryPath := path.Join(currentPath, entry.Name())
// Get relative path from dir
relPath := strings.TrimPrefix(entryPath, dir)
relPath = strings.TrimPrefix(relPath, "/")
// Get file info
info, infoErr := entry.Info()
if infoErr != nil {
return infoErr
}
// Create tar header
hdr, hdrErr := tar.FileInfoHeader(info, "")
if hdrErr != nil {
return hdrErr
}
hdr.Name = relPath
// Write header
if writeErr := tw.WriteHeader(hdr); writeErr != nil {
return writeErr
}
// Write file contents if not a directory
if !entry.IsDir() {
f, openErr := Open(ctx, fsys, entryPath)
if openErr != nil {
return openErr
}
_, copyErr := io.Copy(tw, f)
closeErr := f.Close()
if copyErr != nil {
return copyErr
}
if closeErr != nil {
return closeErr
}
} else {
// Recurse into subdirectory
recurseErr := walkPath(entryPath, currentDepth+1)
if recurseErr != nil {
return recurseErr
}
}
}
return nil
}
return walkPath(dir, 0)
}