2021-06-25 09:30:10 +02:00

161 lines
3.5 KiB
Go

package files
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
// serialFile implements Node, and reads from a path on the OS filesystem.
// No more than one file will be opened at a time.
type serialFile struct {
path string
files []os.FileInfo
stat os.FileInfo
filter *Filter
}
type serialIterator struct {
files []os.FileInfo
path string
filter *Filter
curName string
curFile Node
err error
}
// NewSerialFile takes a filepath, a bool specifying if hidden files should be included,
// and a fileInfo and returns a Node representing file, directory or special file.
func NewSerialFile(path string, includeHidden bool, stat os.FileInfo) (Node, error) {
filter, err := NewFilter("", nil, includeHidden)
if err != nil {
return nil, err
}
return NewSerialFileWithFilter(path, filter, stat)
}
// NewSerialFileWith takes a filepath, a filter for determining which files should be
// operated upon if the filepath is a directory, and a fileInfo and returns a
// Node representing file, directory or special file.
func NewSerialFileWithFilter(path string, filter *Filter, stat os.FileInfo) (Node, error) {
switch mode := stat.Mode(); {
case mode.IsRegular():
file, err := os.Open(path)
if err != nil {
return nil, err
}
return NewReaderPathFile(path, file, stat)
case mode.IsDir():
// for directories, stat all of the contents first, so we know what files to
// open when Entries() is called
contents, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
return &serialFile{path, contents, stat, filter}, nil
case mode&os.ModeSymlink != 0:
target, err := os.Readlink(path)
if err != nil {
return nil, err
}
return NewLinkFile(target, stat), nil
default:
return nil, fmt.Errorf("unrecognized file type for %s: %s", path, mode.String())
}
}
func (it *serialIterator) Name() string {
return it.curName
}
func (it *serialIterator) Node() Node {
return it.curFile
}
func (it *serialIterator) Next() bool {
// if there aren't any files left in the root directory, we're done
if len(it.files) == 0 {
return false
}
stat := it.files[0]
it.files = it.files[1:]
for it.filter.ShouldExclude(stat) {
if len(it.files) == 0 {
return false
}
stat = it.files[0]
it.files = it.files[1:]
}
// open the next file
filePath := filepath.ToSlash(filepath.Join(it.path, stat.Name()))
// 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
sf, err := NewSerialFileWithFilter(filePath, it.filter, stat)
if err != nil {
it.err = err
return false
}
it.curName = stat.Name()
it.curFile = sf
return true
}
func (it *serialIterator) Err() error {
return it.err
}
func (f *serialFile) Entries() DirIterator {
return &serialIterator{
path: f.path,
files: f.files,
filter: f.filter,
}
}
func (f *serialFile) Close() error {
return nil
}
func (f *serialFile) Stat() os.FileInfo {
return f.stat
}
func (f *serialFile) Size() (int64, error) {
if !f.stat.IsDir() {
//something went terribly, terribly wrong
return 0, errors.New("serialFile is not a directory")
}
var du int64
err := filepath.Walk(f.path, func(p string, fi os.FileInfo, err error) error {
if err != nil || fi == nil {
return err
}
if f.filter.ShouldExclude(fi) {
if fi.Mode().IsDir() {
return filepath.SkipDir
}
} else if fi.Mode().IsRegular() {
du += fi.Size()
}
return nil
})
return du, err
}
var _ Directory = &serialFile{}
var _ DirIterator = &serialIterator{}