github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/commands/files/serialfile.go (about) 1 package files 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 fp "path/filepath" 9 "syscall" 10 ) 11 12 // serialFile implements File, and reads from a path on the OS filesystem. 13 // No more than one file will be opened at a time (directories will advance 14 // to the next file when NextFile() is called). 15 type serialFile struct { 16 name string 17 path string 18 files []os.FileInfo 19 stat os.FileInfo 20 current *File 21 } 22 23 func NewSerialFile(name, path string, stat os.FileInfo) (File, error) { 24 switch mode := stat.Mode(); { 25 case mode.IsRegular(): 26 file, err := os.Open(path) 27 if err != nil { 28 return nil, err 29 } 30 return NewReaderFile(name, path, file, stat), nil 31 case mode.IsDir(): 32 // for directories, stat all of the contents first, so we know what files to 33 // open when NextFile() is called 34 contents, err := ioutil.ReadDir(path) 35 if err != nil { 36 return nil, err 37 } 38 return &serialFile{name, path, contents, stat, nil}, nil 39 case mode&os.ModeSymlink != 0: 40 target, err := os.Readlink(path) 41 if err != nil { 42 return nil, err 43 } 44 return NewLinkFile(name, path, target, stat), nil 45 default: 46 return nil, fmt.Errorf("Unrecognized file type for %s: %s", name, mode.String()) 47 } 48 } 49 50 func (f *serialFile) IsDirectory() bool { 51 // non-directories get created as a ReaderFile, so serialFiles should only 52 // represent directories 53 return true 54 } 55 56 func (f *serialFile) NextFile() (File, error) { 57 // if a file was opened previously, close it 58 err := f.Close() 59 if err != nil { 60 return nil, err 61 } 62 63 // if there aren't any files left in the root directory, we're done 64 if len(f.files) == 0 { 65 return nil, io.EOF 66 } 67 68 stat := f.files[0] 69 f.files = f.files[1:] 70 71 // open the next file 72 fileName := fp.Join(f.name, stat.Name()) 73 filePath := fp.Join(f.path, stat.Name()) 74 75 // recursively call the constructor on the next file 76 // if it's a regular file, we will open it as a ReaderFile 77 // if it's a directory, files in it will be opened serially 78 sf, err := NewSerialFile(fileName, filePath, stat) 79 if err != nil { 80 return nil, err 81 } 82 83 f.current = &sf 84 85 return sf, nil 86 } 87 88 func (f *serialFile) FileName() string { 89 return f.name 90 } 91 92 func (f *serialFile) FullPath() string { 93 return f.path 94 } 95 96 func (f *serialFile) Read(p []byte) (int, error) { 97 return 0, ErrNotReader 98 } 99 100 func (f *serialFile) Close() error { 101 // close the current file if there is one 102 if f.current != nil { 103 err := (*f.current).Close() 104 // ignore EINVAL error, the file might have already been closed 105 if err != nil && err != syscall.EINVAL { 106 return err 107 } 108 } 109 110 return nil 111 } 112 113 func (f *serialFile) Stat() os.FileInfo { 114 return f.stat 115 } 116 117 func (f *serialFile) Size() (int64, error) { 118 if !f.stat.IsDir() { 119 return f.stat.Size(), nil 120 } 121 122 var du int64 123 err := fp.Walk(f.FileName(), func(p string, fi os.FileInfo, err error) error { 124 if fi != nil && fi.Mode()&(os.ModeSymlink|os.ModeNamedPipe) == 0 { 125 du += fi.Size() 126 } 127 return nil 128 }) 129 return du, err 130 }