diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 14ef53308..b4d61fab4 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -111,6 +111,7 @@ func TestDefaultValues(t *testing.T) { AutoNormalize: true, MinDiskFree: size, Versioning: VersioningConfiguration{ + FSType: FilesystemTypeBasic, CleanupIntervalS: 3600, Params: map[string]string{}, }, @@ -520,7 +521,8 @@ func TestIssue1750(t *testing.T) { func TestFolderPath(t *testing.T) { folder := FolderConfiguration{ - Path: "~/tmp", + FilesystemType: FilesystemTypeBasic, + Path: "~/tmp", } realPath := folder.Filesystem(nil).URI() diff --git a/lib/config/filesystemtype.go b/lib/config/filesystemtype.go index eacfce6af..9608647f5 100644 --- a/lib/config/filesystemtype.go +++ b/lib/config/filesystemtype.go @@ -8,47 +8,30 @@ package config import "github.com/syncthing/syncthing/lib/fs" -type FilesystemType int32 +type FilesystemType string const ( - FilesystemTypeBasic FilesystemType = 0 - FilesystemTypeFake FilesystemType = 1 + FilesystemTypeBasic FilesystemType = "basic" + FilesystemTypeFake FilesystemType = "fake" ) -func (t FilesystemType) String() string { - switch t { - case FilesystemTypeBasic: - return "basic" - case FilesystemTypeFake: - return "fake" - default: - return "unknown" - } +func (t FilesystemType) ToFS() fs.FilesystemType { + return fs.FilesystemType(string(t)) } -func (t FilesystemType) ToFS() fs.FilesystemType { - switch t { - case FilesystemTypeBasic: - return fs.FilesystemTypeBasic - case FilesystemTypeFake: - return fs.FilesystemTypeFake - default: - return fs.FilesystemTypeBasic - } +func (t FilesystemType) String() string { + return string(t) } func (t FilesystemType) MarshalText() ([]byte, error) { - return []byte(t.String()), nil + return []byte(t), nil } func (t *FilesystemType) UnmarshalText(bs []byte) error { - switch string(bs) { - case "basic": - *t = FilesystemTypeBasic - case "fake": - *t = FilesystemTypeFake - default: - *t = FilesystemTypeBasic - } + *t = FilesystemType(string(bs)) return nil } + +func (t *FilesystemType) ParseDefault(str string) error { + return t.UnmarshalText([]byte(str)) +} diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index d8ec913e8..786be1f8d 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -47,7 +47,7 @@ type FolderDeviceConfiguration struct { type FolderConfiguration struct { ID string `json:"id" xml:"id,attr" nodefault:"true"` Label string `json:"label" xml:"label,attr" restart:"false"` - FilesystemType FilesystemType `json:"filesystemType" xml:"filesystemType"` + FilesystemType FilesystemType `json:"filesystemType" xml:"filesystemType" default:"basic"` Path string `json:"path" xml:"path,attr" default:"~"` Type FolderType `json:"type" xml:"type,attr"` Devices []FolderDeviceConfiguration `json:"devices" xml:"device"` diff --git a/lib/config/versioningconfiguration.go b/lib/config/versioningconfiguration.go index 1aa4cb06e..988f8ab44 100644 --- a/lib/config/versioningconfiguration.go +++ b/lib/config/versioningconfiguration.go @@ -20,7 +20,7 @@ type VersioningConfiguration struct { Params map[string]string `json:"params" xml:"parameter" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` CleanupIntervalS int `json:"cleanupIntervalS" xml:"cleanupIntervalS" default:"3600"` FSPath string `json:"fsPath" xml:"fsPath"` - FSType FilesystemType `json:"fsType" xml:"fsType"` + FSType FilesystemType `json:"fsType" xml:"fsType" default:"basic"` } func (c *VersioningConfiguration) Reset() { @@ -33,7 +33,7 @@ type internalVersioningConfiguration struct { Params []internalParam `xml:"param"` CleanupIntervalS int `xml:"cleanupIntervalS" default:"3600"` FSPath string `xml:"fsPath"` - FSType FilesystemType `xml:"fsType"` + FSType FilesystemType `xml:"fsType" default:"basic"` } type internalParam struct { diff --git a/lib/fs/basicfs.go b/lib/fs/basicfs.go index 6a5d58c74..225eb8132 100644 --- a/lib/fs/basicfs.go +++ b/lib/fs/basicfs.go @@ -19,6 +19,8 @@ import ( "github.com/syncthing/syncthing/lib/build" ) +const FilesystemTypeBasic FilesystemType = "basic" + var ( errInvalidFilenameEmpty = errors.New("name is invalid, must not be empty") errInvalidFilenameWindowsSpacePeriod = errors.New("name is invalid, must not end in space or period on Windows") @@ -56,6 +58,12 @@ type ( groupCache = valueCache[string, *user.Group] ) +func init() { + RegisterFilesystemType(FilesystemTypeBasic, func(root string, opts ...Option) (Filesystem, error) { + return newBasicFilesystem(root, opts...), nil + }) +} + func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem { if root == "" { root = "." // Otherwise "" becomes "/" below diff --git a/lib/fs/fakefs.go b/lib/fs/fakefs.go index 5c693afa6..9429350eb 100644 --- a/lib/fs/fakefs.go +++ b/lib/fs/fakefs.go @@ -26,6 +26,14 @@ import ( "github.com/syncthing/syncthing/lib/protocol" ) +const FilesystemTypeFake FilesystemType = "fake" + +func init() { + RegisterFilesystemType(FilesystemTypeFake, func(root string, opts ...Option) (Filesystem, error) { + return newFakeFilesystem(root, opts...), nil + }) +} + // see readShortAt() const randomBlockShift = 14 // 128k diff --git a/lib/fs/filesystem.go b/lib/fs/filesystem.go index a727e2239..2d7ffbadd 100644 --- a/lib/fs/filesystem.go +++ b/lib/fs/filesystem.go @@ -9,6 +9,7 @@ package fs import ( "context" "errors" + "fmt" "io" "io/fs" "os" @@ -215,17 +216,6 @@ func IsPermission(err error) bool { // IsPathSeparator is the equivalent of os.IsPathSeparator var IsPathSeparator = os.IsPathSeparator -// 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) Filesystem -} - func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem { var caseOpt Option var mtimeOpt Option @@ -246,18 +236,23 @@ func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem } opts = opts[:i] + // Construct file system using the registered factory function var fs Filesystem - switch fsType { - case FilesystemTypeBasic: - fs = newBasicFilesystem(uri, opts...) - case FilesystemTypeFake: - fs = newFakeFilesystem(uri, opts...) - default: - l.Debugln("Unknown filesystem", fsType, uri) + var err error + filesystemFactoriesMutex.Lock() + fsFactory, factoryFound := filesystemFactories[fsType] + filesystemFactoriesMutex.Unlock() + if factoryFound { + fs, err = fsFactory(uri, opts...) + } else { + err = fmt.Errorf("File system type '%s' not recognized", fsType) + } + + if err != nil { fs = &errorFilesystem{ fsType: fsType, uri: uri, - err: errors.New("filesystem with type " + fsType.String() + " does not exist."), + err: err, } } diff --git a/lib/fs/types.go b/lib/fs/types.go index 243ea60c8..7dc86c9fb 100644 --- a/lib/fs/types.go +++ b/lib/fs/types.go @@ -6,20 +6,34 @@ package fs -type FilesystemType int32 +import "sync" -const ( - FilesystemTypeBasic FilesystemType = 0 - FilesystemTypeFake FilesystemType = 1 -) +type FilesystemType string -func (t FilesystemType) String() string { - switch t { - case FilesystemTypeBasic: - return "basic" - case FilesystemTypeFake: - return "fake" - default: - return "unknown" - } +// 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) Filesystem +} + +// Factory function type for constructing a custom file system. It takes the URI +// and options as its parameters. +type FilesystemFactory func(string, ...Option) (Filesystem, error) + +// For each registered file system type, a function to construct a file system. +var filesystemFactories map[FilesystemType]FilesystemFactory = make(map[FilesystemType]FilesystemFactory) +var filesystemFactoriesMutex sync.Mutex = sync.Mutex{} + +// Register a function to be called when a filesystem is to be constructed with +// the specified fsType. The function will receive the URI for the file system as well +// as all options. +func RegisterFilesystemType(fsType FilesystemType, fn FilesystemFactory) { + filesystemFactoriesMutex.Lock() + defer filesystemFactoriesMutex.Unlock() + filesystemFactories[fsType] = fn } diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 1c79f0e1e..ebffee28d 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -2774,9 +2774,10 @@ func TestIssue4903(t *testing.T) { folderPath := "nonexistent" cfg := defaultCfgWrapper.RawCopy() fcfg := config.FolderConfiguration{ - ID: "folder1", - Path: folderPath, - Paused: true, + ID: "folder1", + Path: folderPath, + FilesystemType: config.FilesystemTypeBasic, + Paused: true, Devices: []config.FolderDeviceConfiguration{ {DeviceID: device1}, }, diff --git a/lib/scanner/walk_test.go b/lib/scanner/walk_test.go index 5c6254d29..266399244 100644 --- a/lib/scanner/walk_test.go +++ b/lib/scanner/walk_test.go @@ -677,7 +677,7 @@ func TestStopWalk(t *testing.T) { // Use an errorFs as the backing fs for the rest of the interface // The way we get it is a bit hacky tho. - errorFs := fs.NewFilesystem(fs.FilesystemType(-1), ".") + errorFs := fs.NewFilesystem(fs.FilesystemType("error"), ".") fs := fs.NewWalkFilesystem(&infiniteFS{errorFs, 100, 100, 1e6}) const numHashers = 4 diff --git a/lib/versioner/external.go b/lib/versioner/external.go index 2a5559408..656bdfa72 100644 --- a/lib/versioner/external.go +++ b/lib/versioner/external.go @@ -74,7 +74,7 @@ func (v external) Archive(filePath string) error { } context := map[string]string{ - "%FOLDER_FILESYSTEM%": v.filesystem.Type().String(), + "%FOLDER_FILESYSTEM%": string(v.filesystem.Type()), "%FOLDER_PATH%": v.filesystem.URI(), "%FILE_PATH%": filePath, } diff --git a/lib/versioner/staggered_test.go b/lib/versioner/staggered_test.go index 60b8065b1..534705599 100644 --- a/lib/versioner/staggered_test.go +++ b/lib/versioner/staggered_test.go @@ -139,10 +139,12 @@ func TestCreateVersionPath(t *testing.T) { } folderCfg := config.FolderConfiguration{ - ID: "default", - Path: tmpDir, + ID: "default", + FilesystemType: config.FilesystemTypeBasic, + Path: tmpDir, Versioning: config.VersioningConfiguration{ Type: "staggered", + FSType: config.FilesystemTypeBasic, FSPath: versionsDir, }, }