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  }