浏览代码

lib/fs: Expose fs option on interface (fixes #7385, ref #7381) (#7389)

Simon Frei 4 年之前
父节点
当前提交
3938b61c3f
共有 9 个文件被更改,包括 74 次插入36 次删除
  1. 2 2
      lib/config/folderconfiguration.go
  2. 18 11
      lib/fs/basicfs.go
  3. 8 10
      lib/fs/casefs.go
  4. 4 1
      lib/fs/errorfs.go
  5. 4 0
      lib/fs/fakefs.go
  6. 10 3
      lib/fs/filesystem.go
  7. 22 7
      lib/fs/walkfs.go
  8. 2 2
      lib/fs/walkfs_test.go
  9. 4 0
      lib/scanner/virtualfs_test.go

+ 2 - 2
lib/config/folderconfiguration.go

@@ -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
 }

+ 18 - 11
lib/fs/basicfs.go

@@ -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.

+ 8 - 10
lib/fs/casefs.go

@@ -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 {

+ 4 - 1
lib/fs/errorfs.go

@@ -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
 }

+ 4 - 0
lib/fs/fakefs.go

@@ -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

+ 10 - 3
lib/fs/filesystem.go

@@ -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 {

+ 22 - 7
lib/fs/walkfs.go

@@ -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)
 }

+ 2 - 2
lib/fs/walkfs_test.go

@@ -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)

+ 4 - 0
lib/scanner/virtualfs_test.go

@@ -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