all: Refactor preparing configuration (#7127)

This commit is contained in:
Simon Frei 2020-11-20 14:21:54 +01:00 committed by GitHub
parent 641b7aee38
commit 24af89c8e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 207 additions and 147 deletions

View File

@ -185,7 +185,7 @@ func main() {
log.Println("ID:", id) log.Println("ID:", id)
} }
wrapper := config.Wrap("config", config.New(id), events.NoopLogger) wrapper := config.Wrap("config", config.New(id), id, events.NoopLogger)
wrapper.SetOptions(config.OptionsConfiguration{ wrapper.SetOptions(config.OptionsConfiguration{
NATLeaseM: natLease, NATLeaseM: natLease,
NATRenewalM: natRenewal, NATRenewalM: natRenewal,

View File

@ -113,7 +113,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
RawUseTLS: false, RawUseTLS: false,
}, },
} }
w := config.Wrap("/dev/null", cfg, events.NoopLogger) w := config.Wrap("/dev/null", cfg, protocol.LocalDeviceID, events.NoopLogger)
srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, false).(*service) srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, false).(*service)
defer os.Remove(token) defer os.Remove(token)
@ -1251,7 +1251,7 @@ func TestConfigChanges(t *testing.T) {
panic(err) panic(err)
} }
defer os.Remove(tmpFile.Name()) defer os.Remove(tmpFile.Name())
w := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger) w := config.Wrap(tmpFile.Name(), cfg, protocol.LocalDeviceID, events.NoopLogger)
tmpFile.Close() tmpFile.Close()
baseURL, cancel, err := startHTTP(w) baseURL, cancel, err := startHTTP(w)
if err != nil { if err != nil {

View File

@ -142,6 +142,10 @@ func (c *mockedConfig) StunServers() []string {
return nil return nil
} }
func (c *mockedConfig) MyID() protocol.DeviceID {
return protocol.DeviceID{}
}
type noopWaiter struct{} type noopWaiter struct{}
func (noopWaiter) Wait() {} func (noopWaiter) Wait() {}

View File

@ -44,7 +44,7 @@ func (validationError) String() string {
func TestReplaceCommit(t *testing.T) { func TestReplaceCommit(t *testing.T) {
t.Skip("broken, fails randomly, #3834") t.Skip("broken, fails randomly, #3834")
w := wrap("/dev/null", Configuration{Version: 0}) w := wrap("/dev/null", Configuration{Version: 0}, device1)
if w.RawCopy().Version != 0 { if w.RawCopy().Version != 0 {
t.Fatal("Config incorrect") t.Fatal("Config incorrect")
} }

View File

@ -25,7 +25,6 @@ import (
"github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/util" "github.com/syncthing/syncthing/lib/util"
) )
@ -105,8 +104,6 @@ func New(myID protocol.DeviceID) Configuration {
cfg.Options.UnackedNotificationIDs = []string{"authenticationUserAndPassword"} cfg.Options.UnackedNotificationIDs = []string{"authenticationUserAndPassword"}
util.SetDefaults(&cfg) util.SetDefaults(&cfg)
util.SetDefaults(&cfg.Options)
util.SetDefaults(&cfg.GUI)
// Can't happen. // Can't happen.
if err := cfg.prepare(myID); err != nil { if err := cfg.prepare(myID); err != nil {
@ -152,8 +149,6 @@ func ReadXML(r io.Reader, myID protocol.DeviceID) (Configuration, int, error) {
var cfg xmlConfiguration var cfg xmlConfiguration
util.SetDefaults(&cfg) util.SetDefaults(&cfg)
util.SetDefaults(&cfg.Options)
util.SetDefaults(&cfg.GUI)
if err := xml.NewDecoder(r).Decode(&cfg); err != nil { if err := xml.NewDecoder(r).Decode(&cfg); err != nil {
return Configuration{}, 0, err return Configuration{}, 0, err
@ -171,8 +166,6 @@ func ReadJSON(r io.Reader, myID protocol.DeviceID) (Configuration, error) {
var cfg Configuration var cfg Configuration
util.SetDefaults(&cfg) util.SetDefaults(&cfg)
util.SetDefaults(&cfg.Options)
util.SetDefaults(&cfg.GUI)
bs, err := ioutil.ReadAll(r) bs, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
@ -230,38 +223,61 @@ func (cfg *Configuration) WriteXML(w io.Writer) error {
} }
func (cfg *Configuration) prepare(myID protocol.DeviceID) error { func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
var myName string cfg.ensureMyDevice(myID)
// Ensure this device is present in the config existingDevices, err := cfg.prepareFoldersAndDevices(myID)
for _, device := range cfg.Devices { if err != nil {
if device.DeviceID == myID {
goto found
}
}
myName, _ = os.Hostname()
cfg.Devices = append(cfg.Devices, DeviceConfiguration{
DeviceID: myID,
Name: myName,
})
found:
if err := cfg.clean(); err != nil {
return err return err
} }
// Ensure that we are part of the devices cfg.GUI.prepare()
for i := range cfg.Folders {
cfg.Folders[i].Devices = ensureDevicePresent(cfg.Folders[i].Devices, myID) guiPWIsSet := cfg.GUI.User != "" && cfg.GUI.Password != ""
} cfg.Options.prepare(guiPWIsSet)
ignoredDevices := cfg.prepareIgnoredDevices(existingDevices)
cfg.preparePendingDevices(existingDevices, ignoredDevices)
cfg.removeDeprecatedProtocols()
util.FillNilExceptDeprecated(cfg)
// TestIssue1750 relies on migrations happening after preparing options.
cfg.applyMigrations()
return nil return nil
} }
func (cfg *Configuration) clean() error { func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) {
util.FillNilSlices(&cfg.Options) // Ensure this device is present in the config
for _, device := range cfg.Devices {
if device.DeviceID == myID {
return
}
}
myName, _ := os.Hostname()
cfg.Devices = append(cfg.Devices, DeviceConfiguration{
DeviceID: myID,
Name: myName,
})
}
func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[protocol.DeviceID]bool, error) {
existingDevices := cfg.prepareDeviceList()
sharedFolders, err := cfg.prepareFolders(myID, existingDevices)
if err != nil {
return nil, err
}
cfg.prepareDevices(sharedFolders)
return existingDevices, nil
}
func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]bool {
// Ensure that the device list is // Ensure that the device list is
// - free from duplicates // - free from duplicates
// - no devices with empty ID // - no devices with empty ID
@ -272,89 +288,62 @@ func (cfg *Configuration) clean() error {
return cfg.Devices[a].DeviceID.Compare(cfg.Devices[b].DeviceID) == -1 return cfg.Devices[a].DeviceID.Compare(cfg.Devices[b].DeviceID) == -1
}) })
// Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool, len(cfg.Devices))
for _, device := range cfg.Devices {
existingDevices[device.DeviceID] = true
}
return existingDevices
}
func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) (map[protocol.DeviceID][]string, error) {
// Prepare folders and check for duplicates. Duplicates are bad and // Prepare folders and check for duplicates. Duplicates are bad and
// dangerous, can't currently be resolved in the GUI, and shouldn't // dangerous, can't currently be resolved in the GUI, and shouldn't
// happen when configured by the GUI. We return with an error in that // happen when configured by the GUI. We return with an error in that
// situation. // situation.
existingFolders := make(map[string]*FolderConfiguration) sharedFolders := make(map[protocol.DeviceID][]string, len(cfg.Devices))
existingFolders := make(map[string]*FolderConfiguration, len(cfg.Folders))
for i := range cfg.Folders { for i := range cfg.Folders {
folder := &cfg.Folders[i] folder := &cfg.Folders[i]
folder.prepare()
if folder.ID == "" { if folder.ID == "" {
return errFolderIDEmpty return nil, errFolderIDEmpty
} }
if folder.Path == "" { if folder.Path == "" {
return fmt.Errorf("folder %q: %w", folder.ID, errFolderPathEmpty) return nil, fmt.Errorf("folder %q: %w", folder.ID, errFolderPathEmpty)
} }
if _, ok := existingFolders[folder.ID]; ok { if _, ok := existingFolders[folder.ID]; ok {
return fmt.Errorf("folder %q: %w", folder.ID, errFolderIDDuplicate) return nil, fmt.Errorf("folder %q: %w", folder.ID, errFolderIDDuplicate)
} }
folder.prepare(myID, existingDevices)
existingFolders[folder.ID] = folder existingFolders[folder.ID] = folder
for _, dev := range folder.Devices {
sharedFolders[dev.DeviceID] = append(sharedFolders[dev.DeviceID], folder.ID)
}
} }
cfg.Options.RawListenAddresses = util.UniqueTrimmedStrings(cfg.Options.RawListenAddresses)
cfg.Options.RawGlobalAnnServers = util.UniqueTrimmedStrings(cfg.Options.RawGlobalAnnServers)
if cfg.Version > 0 && cfg.Version < OldestHandledVersion {
l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
}
// Upgrade configuration versions as appropriate
migrationsMut.Lock()
migrations.apply(cfg)
migrationsMut.Unlock()
// Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool)
for _, device := range cfg.Devices {
existingDevices[device.DeviceID] = true
}
// Ensure that the folder list is sorted by ID // Ensure that the folder list is sorted by ID
sort.Slice(cfg.Folders, func(a, b int) bool { sort.Slice(cfg.Folders, func(a, b int) bool {
return cfg.Folders[a].ID < cfg.Folders[b].ID return cfg.Folders[a].ID < cfg.Folders[b].ID
}) })
return sharedFolders, nil
}
// Ensure that in all folder configs func (cfg *Configuration) prepareDevices(sharedFolders map[protocol.DeviceID][]string) {
// - any loose devices are not present in the wrong places
// - there are no duplicate devices
// - the versioning configuration parameter map is not nil
sharedFolders := make(map[protocol.DeviceID][]string, len(cfg.Devices))
for i := range cfg.Folders {
cfg.Folders[i].Devices = ensureExistingDevices(cfg.Folders[i].Devices, existingDevices)
cfg.Folders[i].Devices = ensureNoDuplicateFolderDevices(cfg.Folders[i].Devices)
if cfg.Folders[i].Versioning.Params == nil {
cfg.Folders[i].Versioning.Params = map[string]string{}
}
sort.Slice(cfg.Folders[i].Devices, func(a, b int) bool {
return cfg.Folders[i].Devices[a].DeviceID.Compare(cfg.Folders[i].Devices[b].DeviceID) == -1
})
for _, dev := range cfg.Folders[i].Devices {
sharedFolders[dev.DeviceID] = append(sharedFolders[dev.DeviceID], cfg.Folders[i].ID)
}
}
for i := range cfg.Devices { for i := range cfg.Devices {
cfg.Devices[i].prepare(sharedFolders[cfg.Devices[i].DeviceID]) cfg.Devices[i].prepare(sharedFolders[cfg.Devices[i].DeviceID])
} }
}
// Very short reconnection intervals are annoying func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]bool) map[protocol.DeviceID]bool {
if cfg.Options.ReconnectIntervalS < 5 {
cfg.Options.ReconnectIntervalS = 5
}
if cfg.GUI.APIKey == "" {
cfg.GUI.APIKey = rand.String(32)
}
// The list of ignored devices should not contain any devices that have // The list of ignored devices should not contain any devices that have
// been manually added to the config. // been manually added to the config.
var newIgnoredDevices []ObservedDevice newIgnoredDevices := cfg.IgnoredDevices[:0]
ignoredDevices := make(map[protocol.DeviceID]bool) ignoredDevices := make(map[protocol.DeviceID]bool, len(cfg.IgnoredDevices))
for _, dev := range cfg.IgnoredDevices { for _, dev := range cfg.IgnoredDevices {
if !existingDevices[dev.ID] { if !existingDevices[dev.ID] {
ignoredDevices[dev.ID] = true ignoredDevices[dev.ID] = true
@ -362,7 +351,10 @@ func (cfg *Configuration) clean() error {
} }
} }
cfg.IgnoredDevices = newIgnoredDevices cfg.IgnoredDevices = newIgnoredDevices
return ignoredDevices
}
func (cfg *Configuration) preparePendingDevices(existingDevices, ignoredDevices map[protocol.DeviceID]bool) {
// The list of pending devices should not contain devices that were added manually, nor should it contain // The list of pending devices should not contain devices that were added manually, nor should it contain
// ignored devices. // ignored devices.
@ -371,7 +363,7 @@ func (cfg *Configuration) clean() error {
return cfg.PendingDevices[i].Time.Before(cfg.PendingDevices[j].Time) return cfg.PendingDevices[i].Time.Before(cfg.PendingDevices[j].Time)
}) })
var newPendingDevices []ObservedDevice newPendingDevices := cfg.PendingDevices[:0]
nextPendingDevice: nextPendingDevice:
for _, pendingDevice := range cfg.PendingDevices { for _, pendingDevice := range cfg.PendingDevices {
if !existingDevices[pendingDevice.ID] && !ignoredDevices[pendingDevice.ID] { if !existingDevices[pendingDevice.ID] && !ignoredDevices[pendingDevice.ID] {
@ -385,7 +377,9 @@ nextPendingDevice:
} }
} }
cfg.PendingDevices = newPendingDevices cfg.PendingDevices = newPendingDevices
}
func (cfg *Configuration) removeDeprecatedProtocols() {
// Deprecated protocols are removed from the list of listeners and // Deprecated protocols are removed from the list of listeners and
// device addresses. So far just kcp*. // device addresses. So far just kcp*.
for _, prefix := range []string{"kcp"} { for _, prefix := range []string{"kcp"} {
@ -395,35 +389,17 @@ nextPendingDevice:
dev.Addresses = filterURLSchemePrefix(dev.Addresses, prefix) dev.Addresses = filterURLSchemePrefix(dev.Addresses, prefix)
} }
} }
}
// Initialize any empty slices func (cfg *Configuration) applyMigrations() {
if cfg.Folders == nil { if cfg.Version > 0 && cfg.Version < OldestHandledVersion {
cfg.Folders = []FolderConfiguration{} l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
}
if cfg.IgnoredDevices == nil {
cfg.IgnoredDevices = []ObservedDevice{}
}
if cfg.PendingDevices == nil {
cfg.PendingDevices = []ObservedDevice{}
}
if cfg.Options.AlwaysLocalNets == nil {
cfg.Options.AlwaysLocalNets = []string{}
}
if cfg.Options.UnackedNotificationIDs == nil {
cfg.Options.UnackedNotificationIDs = []string{}
} else if cfg.GUI.User != "" && cfg.GUI.Password != "" {
for i, key := range cfg.Options.UnackedNotificationIDs {
if key == "authenticationUserAndPassword" {
cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs[:i], cfg.Options.UnackedNotificationIDs[i+1:]...)
break
}
}
}
if cfg.Options.FeatureFlags == nil {
cfg.Options.FeatureFlags = []string{}
} }
return nil // Upgrade configuration versions as appropriate
migrationsMut.Lock()
migrations.apply(cfg)
migrationsMut.Unlock()
} }
// DeviceMap returns a map of device ID to device configuration for the given configuration. // DeviceMap returns a map of device ID to device configuration for the given configuration.

View File

@ -526,7 +526,7 @@ func TestNewSaveLoad(t *testing.T) {
} }
intCfg := New(device1) intCfg := New(device1)
cfg := wrap(path, intCfg) cfg := wrap(path, intCfg, device1)
if exists(path) { if exists(path) {
t.Error(path, "exists") t.Error(path, "exists")
@ -646,7 +646,7 @@ func TestPullOrder(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
wrapper = wrap("testdata/pullorder.xml", cfg) wrapper = wrap("testdata/pullorder.xml", cfg, device1)
folders = wrapper.Folders() folders = wrapper.Folders()
for _, tc := range expected { for _, tc := range expected {
@ -941,7 +941,8 @@ func TestIssue4219(t *testing.T) {
] ]
}`)) }`))
cfg, err := ReadJSON(r, protocol.LocalDeviceID) myID := protocol.LocalDeviceID
cfg, err := ReadJSON(r, myID)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -959,7 +960,7 @@ func TestIssue4219(t *testing.T) {
t.Errorf("There should be three ignored folders, not %d", ignoredFolders) t.Errorf("There should be three ignored folders, not %d", ignoredFolders)
} }
w := wrap("/tmp/cfg", cfg) w := wrap("/tmp/cfg", cfg, myID)
if !w.IgnoredFolder(device2, "t1") { if !w.IgnoredFolder(device2, "t1") {
t.Error("Folder device2 t1 should be ignored") t.Error("Folder device2 t1 should be ignored")
} }
@ -1119,12 +1120,16 @@ func TestRemoveDeviceWithEmptyID(t *testing.T) {
}, },
} }
cfg.clean() cfg.prepare(device1)
if len(cfg.Devices) != 0 { if len(cfg.Devices) != 1 {
t.Error("Expected one device")
} else if cfg.Devices[0].DeviceID != device1 {
t.Error("Expected device with empty ID to be removed from config:", cfg.Devices) t.Error("Expected device with empty ID to be removed from config:", cfg.Devices)
} }
if len(cfg.Folders[0].Devices) != 0 { if len(cfg.Folders[0].Devices) != 1 {
t.Error("Expected one device in folder")
} else if cfg.Folders[0].Devices[0].DeviceID != device1 {
t.Error("Expected device with empty ID to be removed from folder") t.Error("Expected device with empty ID to be removed from folder")
} }
} }
@ -1175,8 +1180,8 @@ func load(path string, myID protocol.DeviceID) (Wrapper, error) {
return cfg, err return cfg, err
} }
func wrap(path string, cfg Configuration) Wrapper { func wrap(path string, cfg Configuration, myID protocol.DeviceID) Wrapper {
return Wrap(path, cfg, events.NoopLogger) return Wrap(path, cfg, myID, events.NoopLogger)
} }
func TestInternalVersioningConfiguration(t *testing.T) { func TestInternalVersioningConfiguration(t *testing.T) {

View File

@ -10,6 +10,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"runtime" "runtime"
"sort"
"strings" "strings"
"time" "time"
@ -43,7 +44,7 @@ func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.
util.SetDefaults(&f) util.SetDefaults(&f)
f.prepare() f.prepare(myID, nil)
return f return f
} }
@ -182,7 +183,19 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
return deviceIDs return deviceIDs
} }
func (f *FolderConfiguration) prepare() { func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
// Ensure that
// - any loose devices are not present in the wrong places
// - there are no duplicate devices
// - we are part of the devices
f.Devices = ensureExistingDevices(f.Devices, existingDevices)
f.Devices = ensureNoDuplicateFolderDevices(f.Devices)
f.Devices = ensureDevicePresent(f.Devices, myID)
sort.Slice(f.Devices, func(a, b int) bool {
return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
})
if f.RescanIntervalS > MaxRescanIntervalS { if f.RescanIntervalS > MaxRescanIntervalS {
f.RescanIntervalS = MaxRescanIntervalS f.RescanIntervalS = MaxRescanIntervalS
} else if f.RescanIntervalS < 0 { } else if f.RescanIntervalS < 0 {
@ -194,9 +207,6 @@ func (f *FolderConfiguration) prepare() {
f.FSWatcherDelayS = 10 f.FSWatcherDelayS = 10
} }
if f.Versioning.Params == nil {
f.Versioning.Params = make(map[string]string)
}
if f.Versioning.CleanupIntervalS > MaxRescanIntervalS { if f.Versioning.CleanupIntervalS > MaxRescanIntervalS {
f.Versioning.CleanupIntervalS = MaxRescanIntervalS f.Versioning.CleanupIntervalS = MaxRescanIntervalS
} else if f.Versioning.CleanupIntervalS < 0 { } else if f.Versioning.CleanupIntervalS < 0 {

View File

@ -11,6 +11,8 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"github.com/syncthing/syncthing/lib/rand"
) )
func (c GUIConfiguration) IsAuthEnabled() bool { func (c GUIConfiguration) IsAuthEnabled() bool {
@ -126,6 +128,12 @@ func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
} }
} }
func (c *GUIConfiguration) prepare() {
if c.APIKey == "" {
c.APIKey = rand.String(32)
}
}
func (c GUIConfiguration) Copy() GUIConfiguration { func (c GUIConfiguration) Copy() GUIConfiguration {
return c return c
} }

View File

@ -28,6 +28,27 @@ func (opts OptionsConfiguration) Copy() OptionsConfiguration {
return optsCopy return optsCopy
} }
func (opts *OptionsConfiguration) prepare(guiPWIsSet bool) {
util.FillNilSlices(opts)
opts.RawListenAddresses = util.UniqueTrimmedStrings(opts.RawListenAddresses)
opts.RawGlobalAnnServers = util.UniqueTrimmedStrings(opts.RawGlobalAnnServers)
// Very short reconnection intervals are annoying
if opts.ReconnectIntervalS < 5 {
opts.ReconnectIntervalS = 5
}
if guiPWIsSet && len(opts.UnackedNotificationIDs) > 0 {
for i, key := range opts.UnackedNotificationIDs {
if key == "authenticationUserAndPassword" {
opts.UnackedNotificationIDs = append(opts.UnackedNotificationIDs[:i], opts.UnackedNotificationIDs[i+1:]...)
break
}
}
}
}
// RequiresRestartOnly returns a copy with only the attributes that require // RequiresRestartOnly returns a copy with only the attributes that require
// restart on change. // restart on change.
func (opts OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration { func (opts OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration {

View File

@ -55,6 +55,7 @@ func (noopWaiter) Wait() {}
// notifications of changes to registered Handlers // notifications of changes to registered Handlers
type Wrapper interface { type Wrapper interface {
ConfigPath() string ConfigPath() string
MyID() protocol.DeviceID
RawCopy() Configuration RawCopy() Configuration
Replace(cfg Configuration) (Waiter, error) Replace(cfg Configuration) (Waiter, error)
@ -97,6 +98,7 @@ type wrapper struct {
cfg Configuration cfg Configuration
path string path string
evLogger events.Logger evLogger events.Logger
myID protocol.DeviceID
waiter Waiter // Latest ongoing config change waiter Waiter // Latest ongoing config change
subs []Committer subs []Committer
@ -107,11 +109,12 @@ type wrapper struct {
// Wrap wraps an existing Configuration structure and ties it to a file on // Wrap wraps an existing Configuration structure and ties it to a file on
// disk. // disk.
func Wrap(path string, cfg Configuration, evLogger events.Logger) Wrapper { func Wrap(path string, cfg Configuration, myID protocol.DeviceID, evLogger events.Logger) Wrapper {
w := &wrapper{ w := &wrapper{
cfg: cfg, cfg: cfg,
path: path, path: path,
evLogger: evLogger, evLogger: evLogger,
myID: myID,
waiter: noopWaiter{}, // Noop until first config change waiter: noopWaiter{}, // Noop until first config change
mut: sync.NewMutex(), mut: sync.NewMutex(),
} }
@ -132,13 +135,17 @@ func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper,
return nil, 0, err return nil, 0, err
} }
return Wrap(path, cfg, evLogger), originalVersion, nil return Wrap(path, cfg, myID, evLogger), originalVersion, nil
} }
func (w *wrapper) ConfigPath() string { func (w *wrapper) ConfigPath() string {
return w.path return w.path
} }
func (w *wrapper) MyID() protocol.DeviceID {
return w.myID
}
// Subscribe registers the given handler to be called on any future // Subscribe registers the given handler to be called on any future
// configuration changes. // configuration changes.
func (w *wrapper) Subscribe(c Committer) { func (w *wrapper) Subscribe(c Committer) {
@ -184,7 +191,7 @@ func (w *wrapper) Replace(cfg Configuration) (Waiter, error) {
func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) { func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
from := w.cfg from := w.cfg
if err := to.clean(); err != nil { if err := to.prepare(w.myID); err != nil {
return noopWaiter{}, err return noopWaiter{}, err
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
) )
func TestIsLANHost(t *testing.T) { func TestIsLANHost(t *testing.T) {
@ -36,7 +37,7 @@ func TestIsLANHost(t *testing.T) {
Options: config.OptionsConfiguration{ Options: config.OptionsConfiguration{
AlwaysLocalNets: []string{"10.20.30.0/24"}, AlwaysLocalNets: []string{"10.20.30.0/24"},
}, },
}, events.NoopLogger) }, protocol.LocalDeviceID, events.NoopLogger)
s := &service{cfg: cfg} s := &service{cfg: cfg}
for _, tc := range cases { for _, tc := range cases {

View File

@ -30,7 +30,7 @@ func init() {
} }
func initConfig() config.Wrapper { func initConfig() config.Wrapper {
cfg := config.Wrap("/dev/null", config.New(device1), events.NoopLogger) cfg := config.Wrap("/dev/null", config.New(device1), device1, events.NoopLogger)
dev1Conf = config.NewDeviceConfiguration(device1, "device1") dev1Conf = config.NewDeviceConfiguration(device1, "device1")
dev2Conf = config.NewDeviceConfiguration(device2, "device2") dev2Conf = config.NewDeviceConfiguration(device2, "device2")
dev3Conf = config.NewDeviceConfiguration(device3, "device3") dev3Conf = config.NewDeviceConfiguration(device3, "device3")

View File

@ -23,7 +23,7 @@ func setupCache() *manager {
cfg.Options.LocalAnnEnabled = false cfg.Options.LocalAnnEnabled = false
cfg.Options.GlobalAnnEnabled = false cfg.Options.GlobalAnnEnabled = false
return NewManager(protocol.LocalDeviceID, config.Wrap("", cfg, events.NoopLogger), tls.Certificate{}, events.NoopLogger, nil).(*manager) return NewManager(protocol.LocalDeviceID, config.Wrap("", cfg, protocol.LocalDeviceID, events.NoopLogger), tls.Certificate{}, events.NoopLogger, nil).(*manager)
} }
func TestCacheUnique(t *testing.T) { func TestCacheUnique(t *testing.T) {

View File

@ -115,7 +115,7 @@ func createTmpWrapper(cfg config.Configuration) config.Wrapper {
if err != nil { if err != nil {
panic(err) panic(err)
} }
wrapper := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger) wrapper := config.Wrap(tmpFile.Name(), cfg, myID, events.NoopLogger)
tmpFile.Close() tmpFile.Close()
return wrapper return wrapper
} }
@ -331,7 +331,7 @@ func TestDeviceRename(t *testing.T) {
DeviceID: device1, DeviceID: device1,
}, },
} }
cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg, events.NoopLogger) cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg, device1, events.NoopLogger)
db := db.NewLowlevel(backend.OpenMemory()) db := db.NewLowlevel(backend.OpenMemory())
m := newModel(cfg, myID, "syncthing", "dev", db, nil) m := newModel(cfg, myID, "syncthing", "dev", db, nil)

View File

@ -64,7 +64,7 @@ func TestMappingClearAddresses(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
w := config.Wrap(tmpFile.Name(), config.Configuration{}, events.NoopLogger) w := config.Wrap(tmpFile.Name(), config.Configuration{}, protocol.LocalDeviceID, events.NoopLogger)
defer os.RemoveAll(tmpFile.Name()) defer os.RemoveAll(tmpFile.Name())
tmpFile.Close() tmpFile.Close()

View File

@ -37,7 +37,7 @@ func TestShortIDCheck(t *testing.T) {
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}}, {DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits {DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
}, },
}, events.NoopLogger) }, protocol.LocalDeviceID, events.NoopLogger)
defer os.Remove(cfg.ConfigPath()) defer os.Remove(cfg.ConfigPath())
if err := checkShortIDs(cfg); err != nil { if err := checkShortIDs(cfg); err != nil {
@ -49,7 +49,7 @@ func TestShortIDCheck(t *testing.T) {
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}}, {DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}},
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same {DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same
}, },
}, events.NoopLogger) }, protocol.LocalDeviceID, events.NoopLogger)
if err := checkShortIDs(cfg); err == nil { if err := checkShortIDs(cfg); err == nil {
t.Error("Should have gotten an error") t.Error("Should have gotten an error")
@ -76,7 +76,7 @@ func TestStartupFail(t *testing.T) {
{DeviceID: id}, {DeviceID: id},
{DeviceID: conflID}, {DeviceID: conflID},
}, },
}, events.NoopLogger) }, protocol.LocalDeviceID, events.NoopLogger)
defer os.Remove(cfg.ConfigPath()) defer os.Remove(cfg.ConfigPath())
db := backend.OpenMemory() db := backend.OpenMemory()

View File

@ -49,12 +49,12 @@ func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger,
if noDefaultFolder { if noDefaultFolder {
l.Infoln("We will skip creation of a default folder on first start") l.Infoln("We will skip creation of a default folder on first start")
return config.Wrap(path, newCfg, evLogger), nil return config.Wrap(path, newCfg, myID, evLogger), nil
} }
newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder))) newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
l.Infoln("Default folder created and/or linked to new config") l.Infoln("Default folder created and/or linked to new config")
return config.Wrap(path, newCfg, evLogger), nil return config.Wrap(path, newCfg, myID, evLogger), nil
} }
// LoadConfigAtStartup loads an existing config. If it doesn't yet exist, it // LoadConfigAtStartup loads an existing config. If it doesn't yet exist, it

View File

@ -83,6 +83,10 @@ func SetDefaults(data interface{}) {
default: default:
panic(f.Type()) panic(f.Type())
} }
} else if f.CanSet() && f.Kind() == reflect.Struct && f.CanAddr() {
if addr := f.Addr(); addr.CanInterface() {
SetDefaults(addr.Interface())
}
} }
} }
} }
@ -137,9 +141,22 @@ func UniqueTrimmedStrings(ss []string) []string {
return us return us
} }
func FillNilExceptDeprecated(data interface{}) {
fillNil(data, true)
}
func FillNil(data interface{}) { func FillNil(data interface{}) {
fillNil(data, false)
}
func fillNil(data interface{}, skipDeprecated bool) {
s := reflect.ValueOf(data).Elem() s := reflect.ValueOf(data).Elem()
t := s.Type()
for i := 0; i < s.NumField(); i++ { for i := 0; i < s.NumField(); i++ {
if skipDeprecated && strings.HasPrefix(t.Field(i).Name, "Deprecated") {
continue
}
f := s.Field(i) f := s.Field(i)
for f.Kind() == reflect.Ptr && f.IsZero() && f.CanSet() { for f.Kind() == reflect.Ptr && f.IsZero() && f.CanSet() {
@ -160,9 +177,19 @@ func FillNil(data interface{}) {
} }
} }
if f.Kind() == reflect.Struct && f.CanAddr() { switch f.Kind() {
if addr := f.Addr(); addr.CanInterface() { case reflect.Slice:
FillNil(addr.Interface()) if f.Type().Elem().Kind() != reflect.Struct {
continue
}
for i := 0; i < f.Len(); i++ {
fillNil(f.Index(i).Addr().Interface(), skipDeprecated)
}
case reflect.Struct:
if f.CanAddr() {
if addr := f.Addr(); addr.CanInterface() {
fillNil(addr.Interface(), skipDeprecated)
}
} }
} }
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -47,7 +48,7 @@ var (
} }
defaultCfg = config.Wrap("", config.Configuration{ defaultCfg = config.Wrap("", config.Configuration{
Folders: []config.FolderConfiguration{defaultFolderCfg}, Folders: []config.FolderConfiguration{defaultFolderCfg},
}, events.NoopLogger) }, protocol.LocalDeviceID, events.NoopLogger)
) )
// Represents possibly multiple (different event types) expected paths from // Represents possibly multiple (different event types) expected paths from