serialfile.go 3.19 KB
Newer Older
1 2 3
package files

import (
4
	"fmt"
5
	"io"
rht's avatar
rht committed
6
	"io/ioutil"
7
	"os"
8
	"path/filepath"
Jeromy's avatar
Jeromy committed
9
	"strings"
10 11 12 13 14 15 16
	"syscall"
)

// serialFile implements File, and reads from a path on the OS filesystem.
// No more than one file will be opened at a time (directories will advance
// to the next file when NextFile() is called).
type serialFile struct {
Jeromy's avatar
Jeromy committed
17 18 19 20 21 22
	name              string
	path              string
	files             []os.FileInfo
	stat              os.FileInfo
	current           *File
	handleHiddenFiles bool
23 24
}

Jeromy's avatar
Jeromy committed
25
func NewSerialFile(name, path string, hidden bool, stat os.FileInfo) (File, error) {
26
	switch mode := stat.Mode(); {
rht's avatar
rht committed
27 28 29 30 31 32 33 34 35 36 37 38 39
	case mode.IsRegular():
		file, err := os.Open(path)
		if err != nil {
			return nil, err
		}
		return NewReaderFile(name, path, file, stat), nil
	case mode.IsDir():
		// for directories, stat all of the contents first, so we know what files to
		// open when NextFile() is called
		contents, err := ioutil.ReadDir(path)
		if err != nil {
			return nil, err
		}
Jeromy's avatar
Jeromy committed
40
		return &serialFile{name, path, contents, stat, nil, hidden}, nil
41
	case mode&os.ModeSymlink != 0:
42 43 44 45
		target, err := os.Readlink(path)
		if err != nil {
			return nil, err
		}
rht's avatar
rht committed
46
		return NewLinkFile(name, path, target, stat), nil
47
	default:
rht's avatar
rht committed
48
		return nil, fmt.Errorf("Unrecognized file type for %s: %s", name, mode.String())
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
	}
}

func (f *serialFile) IsDirectory() bool {
	// non-directories get created as a ReaderFile, so serialFiles should only
	// represent directories
	return true
}

func (f *serialFile) NextFile() (File, error) {
	// if a file was opened previously, close it
	err := f.Close()
	if err != nil {
		return nil, err
	}

	// if there aren't any files left in the root directory, we're done
	if len(f.files) == 0 {
		return nil, io.EOF
	}

	stat := f.files[0]
	f.files = f.files[1:]

Jeromy's avatar
Jeromy committed
73
	for !f.handleHiddenFiles && strings.HasPrefix(stat.Name(), ".") {
Jeromy's avatar
Jeromy committed
74 75 76 77 78 79 80 81
		if len(f.files) == 0 {
			return nil, io.EOF
		}

		stat = f.files[0]
		f.files = f.files[1:]
	}

82
	// open the next file
83 84
	fileName := filepath.ToSlash(filepath.Join(f.name, stat.Name()))
	filePath := filepath.ToSlash(filepath.Join(f.path, stat.Name()))
85

rht's avatar
rht committed
86 87 88
	// recursively call the constructor on the next file
	// if it's a regular file, we will open it as a ReaderFile
	// if it's a directory, files in it will be opened serially
Jeromy's avatar
Jeromy committed
89
	sf, err := NewSerialFile(fileName, filePath, f.handleHiddenFiles, stat)
90 91 92
	if err != nil {
		return nil, err
	}
93

rht's avatar
rht committed
94
	f.current = &sf
95

rht's avatar
rht committed
96
	return sf, nil
97 98 99
}

func (f *serialFile) FileName() string {
100 101 102 103
	return f.name
}

func (f *serialFile) FullPath() string {
104 105 106 107
	return f.path
}

func (f *serialFile) Read(p []byte) (int, error) {
Jeromy's avatar
Jeromy committed
108
	return 0, io.EOF
109 110 111 112 113
}

func (f *serialFile) Close() error {
	// close the current file if there is one
	if f.current != nil {
rht's avatar
rht committed
114
		err := (*f.current).Close()
115 116 117 118 119 120 121 122
		// ignore EINVAL error, the file might have already been closed
		if err != nil && err != syscall.EINVAL {
			return err
		}
	}

	return nil
}
123 124 125 126

func (f *serialFile) Stat() os.FileInfo {
	return f.stat
}
127 128

func (f *serialFile) Size() (int64, error) {
129 130
	if !f.stat.IsDir() {
		return f.stat.Size(), nil
131 132
	}

133
	var du int64
134
	err := filepath.Walk(f.FileName(), func(p string, fi os.FileInfo, err error) error {
135 136
		if fi != nil && fi.Mode()&(os.ModeSymlink|os.ModeNamedPipe) == 0 {
			du += fi.Size()
137
		}
138 139 140
		return nil
	})
	return du, err
141
}