Merge branch 'main' into v2

* main:
  feat(fs, config): add support for custom filesystem type construction (#9887)
  build(deps): update dependencies (#10020)
This commit is contained in:
Jakob Borg
2025-04-03 10:21:01 +02:00
14 changed files with 132 additions and 119 deletions
+3 -1
View File
@@ -111,6 +111,7 @@ func TestDefaultValues(t *testing.T) {
AutoNormalize: true,
MinDiskFree: size,
Versioning: VersioningConfiguration{
FSType: FilesystemTypeBasic,
CleanupIntervalS: 3600,
Params: map[string]string{},
},
@@ -518,7 +519,8 @@ func TestIssue1750(t *testing.T) {
func TestFolderPath(t *testing.T) {
folder := FolderConfiguration{
Path: "~/tmp",
FilesystemType: FilesystemTypeBasic,
Path: "~/tmp",
}
realPath := folder.Filesystem().URI()
+13 -30
View File
@@ -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))
}
+1 -1
View File
@@ -46,7 +46,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"`
+2 -2
View File
@@ -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 {
+8
View File
@@ -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
+8
View File
@@ -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
+14 -19
View File
@@ -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,
}
}
+28 -14
View File
@@ -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
}
+4 -3
View File
@@ -2773,9 +2773,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},
},
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
}
+4 -2
View File
@@ -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,
},
}