parent
cdef503db6
commit
3938b61c3f
|
@ -47,11 +47,11 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
|
|||
// cfg.Folders["default"].Filesystem() should be valid.
|
||||
var opts []fs.Option
|
||||
if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs {
|
||||
opts = append(opts, fs.WithJunctionsAsDirs())
|
||||
opts = append(opts, new(fs.OptionJunctionsAsDirs))
|
||||
}
|
||||
filesystem := fs.NewFilesystem(f.FilesystemType, f.Path, opts...)
|
||||
if !f.CaseSensitiveFS {
|
||||
filesystem = fs.NewCaseFilesystem(filesystem, opts...)
|
||||
filesystem = fs.NewCaseFilesystem(filesystem)
|
||||
}
|
||||
return filesystem
|
||||
}
|
||||
|
|
|
@ -26,24 +26,26 @@ var (
|
|||
errNotRelative = errors.New("not a relative path")
|
||||
)
|
||||
|
||||
func WithJunctionsAsDirs() Option {
|
||||
return Option{
|
||||
apply: func(fs Filesystem) {
|
||||
if basic, ok := fs.(*BasicFilesystem); !ok {
|
||||
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
|
||||
} else {
|
||||
basic.junctionsAsDirs = true
|
||||
}
|
||||
},
|
||||
id: "junctionsAsDirs",
|
||||
type OptionJunctionsAsDirs struct{}
|
||||
|
||||
func (o *OptionJunctionsAsDirs) apply(fs Filesystem) {
|
||||
if basic, ok := fs.(*BasicFilesystem); !ok {
|
||||
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
|
||||
} else {
|
||||
basic.junctionsAsDirs = true
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OptionJunctionsAsDirs) String() string {
|
||||
return "junctionsAsDirs"
|
||||
}
|
||||
|
||||
// The BasicFilesystem implements all aspects by delegating to package os.
|
||||
// All paths are relative to the root and cannot (should not) escape the root directory.
|
||||
type BasicFilesystem struct {
|
||||
root string
|
||||
junctionsAsDirs bool
|
||||
options []Option
|
||||
}
|
||||
|
||||
func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
|
||||
|
@ -82,7 +84,8 @@ func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
|
|||
}
|
||||
|
||||
fs := &BasicFilesystem{
|
||||
root: root,
|
||||
root: root,
|
||||
options: opts,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.apply(fs)
|
||||
|
@ -311,6 +314,10 @@ func (f *BasicFilesystem) URI() string {
|
|||
return strings.TrimPrefix(f.root, `\\?\`)
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Options() []Option {
|
||||
return f.options
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
|
||||
// Like os.SameFile, we always return false unless fi1 and fi2 were created
|
||||
// by this package's Stat/Lstat method.
|
||||
|
|
|
@ -55,22 +55,22 @@ type caseFilesystemRegistry struct {
|
|||
startCleaner sync.Once
|
||||
}
|
||||
|
||||
func newFSKey(fs Filesystem, opts ...Option) fskey {
|
||||
func newFSKey(fs Filesystem) fskey {
|
||||
k := fskey{
|
||||
fstype: fs.Type(),
|
||||
uri: fs.URI(),
|
||||
}
|
||||
if len(opts) > 0 {
|
||||
k.opts = opts[0].id
|
||||
if opts := fs.Options(); len(opts) > 0 {
|
||||
k.opts = opts[0].String()
|
||||
for _, o := range opts[1:] {
|
||||
k.opts += "&" + o.id
|
||||
k.opts += "&" + o.String()
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (r *caseFilesystemRegistry) get(fs Filesystem, opts ...Option) Filesystem {
|
||||
k := newFSKey(fs, opts...)
|
||||
func (r *caseFilesystemRegistry) get(fs Filesystem) Filesystem {
|
||||
k := newFSKey(fs)
|
||||
|
||||
// Use double locking when getting a caseFs. In the common case it will
|
||||
// already exist and we take the read lock fast path. If it doesn't, we
|
||||
|
@ -136,10 +136,8 @@ type caseFilesystem struct {
|
|||
// from the real path. It is safe to use with any filesystem, i.e. also a
|
||||
// case-sensitive one. However it will add some overhead and thus shouldn't be
|
||||
// used if the filesystem is known to already behave case-sensitively.
|
||||
func NewCaseFilesystem(fs Filesystem, opts ...Option) Filesystem {
|
||||
return wrapFilesystem(fs, func(fs Filesystem) Filesystem {
|
||||
return globalCaseFilesystemRegistry.get(fs, opts...)
|
||||
})
|
||||
func NewCaseFilesystem(fs Filesystem) Filesystem {
|
||||
return wrapFilesystem(fs, globalCaseFilesystemRegistry.get)
|
||||
}
|
||||
|
||||
func (f *caseFilesystem) Chmod(name string, mode FileMode) error {
|
||||
|
|
|
@ -45,7 +45,10 @@ func (fs *errorFilesystem) Roots() ([]string, error) { retur
|
|||
func (fs *errorFilesystem) Usage(name string) (Usage, error) { return Usage{}, fs.err }
|
||||
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
|
||||
func (fs *errorFilesystem) URI() string { return fs.uri }
|
||||
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
|
||||
func (fs *errorFilesystem) Options() []Option {
|
||||
return nil
|
||||
}
|
||||
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
|
||||
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||
return nil, nil, fs.err
|
||||
}
|
||||
|
|
|
@ -640,6 +640,10 @@ func (fs *fakefs) URI() string {
|
|||
return fs.uri
|
||||
}
|
||||
|
||||
func (fs *fakefs) Options() []Option {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool {
|
||||
// BUG: real systems base file sameness on path, inodes, etc
|
||||
// we try our best, but FileInfo just doesn't have enough data
|
||||
|
|
|
@ -47,6 +47,7 @@ type Filesystem interface {
|
|||
Usage(name string) (Usage, error)
|
||||
Type() FilesystemType
|
||||
URI() string
|
||||
Options() []Option
|
||||
SameFile(fi1, fi2 FileInfo) bool
|
||||
}
|
||||
|
||||
|
@ -178,9 +179,15 @@ var IsPermission = os.IsPermission
|
|||
// IsPathSeparator is the equivalent of os.IsPathSeparator
|
||||
var IsPathSeparator = os.IsPathSeparator
|
||||
|
||||
type Option struct {
|
||||
apply func(Filesystem)
|
||||
id string
|
||||
// Option modifies a filesystem at creation. An option might be specific
|
||||
// to a filesystem-type.
|
||||
//
|
||||
// String is used to detect options with the same effect, i.e. must be different
|
||||
// for options with different effects. Meaning if an option has parameters, a
|
||||
// representation of those must be part of the returned string.
|
||||
type Option interface {
|
||||
String() string
|
||||
apply(Filesystem)
|
||||
}
|
||||
|
||||
func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem {
|
||||
|
|
|
@ -63,10 +63,20 @@ type WalkFunc func(path string, info FileInfo, err error) error
|
|||
|
||||
type walkFilesystem struct {
|
||||
Filesystem
|
||||
checkInfiniteRecursion bool
|
||||
}
|
||||
|
||||
func NewWalkFilesystem(next Filesystem) Filesystem {
|
||||
return &walkFilesystem{next}
|
||||
fs := &walkFilesystem{
|
||||
Filesystem: next,
|
||||
}
|
||||
for _, opt := range next.Options() {
|
||||
if _, ok := opt.(*OptionJunctionsAsDirs); ok {
|
||||
fs.checkInfiniteRecursion = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
|
@ -89,11 +99,13 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ances
|
|||
return nil
|
||||
}
|
||||
|
||||
if !ancestors.Contains(info) {
|
||||
ancestors.Push(info)
|
||||
defer ancestors.Pop()
|
||||
} else {
|
||||
return walkFn(path, info, ErrInfiniteRecursion)
|
||||
if f.checkInfiniteRecursion {
|
||||
if !ancestors.Contains(info) {
|
||||
ancestors.Push(info)
|
||||
defer ancestors.Pop()
|
||||
} else {
|
||||
return walkFn(path, info, ErrInfiniteRecursion)
|
||||
}
|
||||
}
|
||||
|
||||
names, err := f.DirNames(path)
|
||||
|
@ -131,6 +143,9 @@ func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
|
|||
if err != nil {
|
||||
return walkFn(root, nil, err)
|
||||
}
|
||||
ancestors := &ancestorDirList{fs: f.Filesystem}
|
||||
var ancestors *ancestorDirList
|
||||
if f.checkInfiniteRecursion {
|
||||
ancestors = &ancestorDirList{fs: f.Filesystem}
|
||||
}
|
||||
return f.walk(root, info, walkFn, ancestors)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ func testWalkTraverseDirJunct(t *testing.T, fsType FilesystemType, uri string) {
|
|||
t.Skip("Directory junctions are available and tested on windows only")
|
||||
}
|
||||
|
||||
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs())
|
||||
fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
|
||||
|
||||
if err := fs.MkdirAll("target/foo", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -90,7 +90,7 @@ func testWalkInfiniteRecursion(t *testing.T, fsType FilesystemType, uri string)
|
|||
t.Skip("Infinite recursion detection is tested on windows only")
|
||||
}
|
||||
|
||||
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs())
|
||||
fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
|
||||
|
||||
if err := fs.MkdirAll("target/foo", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -97,6 +97,10 @@ func (s singleFileFS) Open(name string) (fs.File, error) {
|
|||
return &fakeFile{s.name, s.filesize, 0}, nil
|
||||
}
|
||||
|
||||
func (s singleFileFS) Options() []fs.Option {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeInfo struct {
|
||||
name string
|
||||
size int64
|
||||
|
|
Loading…
Reference in New Issue