@cryptotaxi247 / kubo / commits / 9ba728532

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 +}