commands/files: Created SerialFile, which opens directory contents serially
Matt Bell committed
Jan 13, 2015 at 11:13 UTC
9ba728532f78c0cbd5d431c274f504fc847d2576
2 files changed
+123
-56
commands/cli/parse.go
+8
-56
@@ -5,13 +5,11 @@ import (
5
"errors"
6
"fmt"
7
"os"
8
- fp "path"
8
"runtime"
10
- "sort"
9
"strings"
10
11
cmds "github.com/jbenet/go-ipfs/commands"
14
- cmdsFiles "github.com/jbenet/go-ipfs/commands/files"
12
+ files "github.com/jbenet/go-ipfs/commands/files"
13
u "github.com/jbenet/go-ipfs/util"
14
)
15
@@ -66,7 +64,7 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c
64
}
65
req.SetArguments(stringArgs)
66
69
- file := &cmdsFiles.SliceFile{"", fileArgs}
67
+ file := &files.SliceFile{"", fileArgs}
68
req.SetFiles(file)
69
70
err = cmd.CheckArguments(req)
@@ -140,7 +138,7 @@ func parseOptions(input []string) (map[string]interface{}, []string, error) {
138
return opts, args, nil
139
}
140
143
-func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmdsFiles.File, error) {
141
+func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []files.File, error) {
142
// ignore stdin on Windows
143
if runtime.GOOS == "windows" {
144
stdin = nil
@@ -177,7 +175,7 @@ func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursi
175
}
176
177
stringArgs := make([]string, 0, numInputs)
180
- fileArgs := make([]cmdsFiles.File, 0, numInputs)
178
+ fileArgs := make([]files.File, 0, numInputs)
179
180
argDefIndex := 0 // the index of the current argument definition
181
for i := 0; i < numInputs; i++ {
@@ -264,7 +262,7 @@ func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, err
262
return append(args, buf.String()), nil, nil
263
}
264
267
-func appendFile(args []cmdsFiles.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmdsFiles.File, []string, error) {
265
+func appendFile(args []files.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]files.File, []string, error) {
266
path := inputs[0]
267
268
file, err := os.Open(path)
@@ -290,7 +288,7 @@ func appendFile(args []cmdsFiles.File, inputs []string, argDef *cmds.Argument, r
288
}
289
}
290
293
- arg, err := openPath(file, path)
291
+ arg, err := files.NewSerialFile(path, file)
292
if err != nil {
293
return nil, nil, err
294
}
@@ -298,51 +296,11 @@ func appendFile(args []cmdsFiles.File, inputs []string, argDef *cmds.Argument, r
296
return append(args, arg), inputs[1:], nil
297
}
298
301
-func appendStdinAsFile(args []cmdsFiles.File, stdin *os.File) ([]cmdsFiles.File, *os.File) {
302
- arg := &cmdsFiles.ReaderFile{"", stdin}
299
+func appendStdinAsFile(args []files.File, stdin *os.File) ([]files.File, *os.File) {
300
+ arg := &files.ReaderFile{"", stdin}
301
return append(args, arg), nil
302
}
303
306
-// recursively get file or directory contents as a cmdsFiles.File
307
-func openPath(file *os.File, path string) (cmdsFiles.File, error) {
308
- stat, err := file.Stat()
309
- if err != nil {
310
- return nil, err
311
- }
312
-
313
- // for non-directories, return a ReaderFile
314
- if !stat.IsDir() {
315
- return &cmdsFiles.ReaderFile{path, file}, nil
316
- }
317
-
318
- // for directories, recursively iterate though children then return as a SliceFile
319
- contents, err := file.Readdir(0)
320
- if err != nil {
321
- return nil, err
322
- }
323
-
324
- // make sure contents are sorted so -- repeatably -- we get the same inputs.
325
- sort.Sort(sortFIByName(contents))
326
-
327
- files := make([]cmdsFiles.File, 0, len(contents))
328
- for _, child := range contents {
329
- childPath := fp.Join(path, child.Name())
330
- childFile, err := os.Open(childPath)
331
- if err != nil {
332
- return nil, err
333
- }
334
-
335
- f, err := openPath(childFile, childPath)
336
- if err != nil {
337
- return nil, err
338
- }
339
-
340
- files = append(files, f)
341
- }
342
-
343
- return &cmdsFiles.SliceFile{path, files}, nil
344
-}
345
-
304
// isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`),
305
// and false otherwise (e.g. nothing is being piped in, so stdin is
306
// coming from the terminal)
@@ -355,9 +313,3 @@ func isTerminal(stdin *os.File) (bool, error) {
313
// if stdin is a CharDevice, return true
314
return ((stat.Mode() & os.ModeCharDevice) != 0), nil
315
}
358
-
359
-type sortFIByName []os.FileInfo
360
-
361
-func (es sortFIByName) Len() int { return len(es) }
362
-func (es sortFIByName) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
363
-func (es sortFIByName) Less(i, j int) bool { return es[i].Name() < es[j].Name() }
commands/files/serialfile.go
new
+115
@@ -0,0 +1,115 @@
1
+package files
2
+
3
+import (
4
+ "io"
5
+ "os"
6
+ fp "path"
7
+ "sort"
8
+ "syscall"
9
+)
10
+
11
+type sortFIByName []os.FileInfo
12
+
13
+func (es sortFIByName) Len() int { return len(es) }
14
+func (es sortFIByName) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
15
+func (es sortFIByName) Less(i, j int) bool { return es[i].Name() < es[j].Name() }
16
+
17
+// serialFile implements File, and reads from a path on the OS filesystem.
18
+// No more than one file will be opened at a time (directories will advance
19
+// to the next file when NextFile() is called).
20
+type serialFile struct {
21
+ path string
22
+ files []os.FileInfo
23
+ current *os.File
24
+}
25
+
26
+func NewSerialFile(path string, file *os.File) (File, error) {
27
+ stat, err := file.Stat()
28
+ if err != nil {
29
+ return nil, err
30
+ }
31
+
32
+ return newSerialFile(path, file, stat)
33
+}
34
+
35
+func newSerialFile(path string, file *os.File, stat os.FileInfo) (File, error) {
36
+ // for non-directories, return a ReaderFile
37
+ if !stat.IsDir() {
38
+ return &ReaderFile{path, file}, nil
39
+ }
40
+
41
+ // for directories, stat all of the contents first, so we know what files to
42
+ // open when NextFile() is called
43
+ contents, err := file.Readdir(0)
44
+ if err != nil {
45
+ return nil, err
46
+ }
47
+
48
+ // we no longer need our root directory file (we already statted the contents),
49
+ // so close it
50
+ err = file.Close()
51
+ if err != nil {
52
+ return nil, err
53
+ }
54
+
55
+ // make sure contents are sorted so -- repeatably -- we get the same inputs.
56
+ sort.Sort(sortFIByName(contents))
57
+
58
+ return &serialFile{path, contents, nil}, nil
59
+}
60
+
61
+func (f *serialFile) IsDirectory() bool {
62
+ // non-directories get created as a ReaderFile, so serialFiles should only
63
+ // represent directories
64
+ return true
65
+}
66
+
67
+func (f *serialFile) NextFile() (File, error) {
68
+ // if a file was opened previously, close it
69
+ err := f.Close()
70
+ if err != nil {
71
+ return nil, err
72
+ }
73
+
74
+ // if there aren't any files left in the root directory, we're done
75
+ if len(f.files) == 0 {
76
+ return nil, io.EOF
77
+ }
78
+
79
+ stat := f.files[0]
80
+ f.files = f.files[1:]
81
+
82
+ // open the next file
83
+ filePath := fp.Join(f.path, stat.Name())
84
+ file, err := os.Open(filePath)
85
+ if err != nil {
86
+ return nil, err
87
+ }
88
+ f.current = file
89
+
90
+ // recursively call the constructor on the next file
91
+ // if it's a regular file, we will open it as a ReaderFile
92
+ // if it's a directory, files in it will be opened serially
93
+ return newSerialFile(filePath, file, stat)
94
+}
95
+
96
+func (f *serialFile) FileName() string {
97
+ return f.path
98
+}
99
+
100
+func (f *serialFile) Read(p []byte) (int, error) {
101
+ return 0, ErrNotReader
102
+}
103
+
104
+func (f *serialFile) Close() error {
105
+ // close the current file if there is one
106
+ if f.current != nil {
107
+ err := f.current.Close()
108
+ // ignore EINVAL error, the file might have already been closed
109
+ if err != nil && err != syscall.EINVAL {
110
+ return err
111
+ }
112
+ }
113
+
114
+ return nil
115
+}