From 24af89c8e2ebd74933c76f72dc17a80b5db9d6ee Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Fri, 20 Nov 2020 14:21:54 +0100 Subject: [PATCH] all: Refactor preparing configuration (#7127) --- cmd/strelaysrv/main.go | 2 +- lib/api/api_test.go | 4 +- lib/api/mocked_config_test.go | 4 + lib/config/commit_test.go | 2 +- lib/config/config.go | 198 +++++++++++-------------- lib/config/config_test.go | 23 +-- lib/config/folderconfiguration.go | 20 ++- lib/config/guiconfiguration.go | 8 + lib/config/optionsconfiguration.go | 21 +++ lib/config/wrapper.go | 13 +- lib/connections/lan_test.go | 3 +- lib/connections/limiter_test.go | 2 +- lib/discover/cache_test.go | 2 +- lib/model/model_test.go | 4 +- lib/nat/structs_test.go | 2 +- lib/syncthing/syncthing_test.go | 6 +- lib/syncthing/utils.go | 4 +- lib/util/utils.go | 33 ++++- lib/watchaggregator/aggregator_test.go | 3 +- 19 files changed, 207 insertions(+), 147 deletions(-) diff --git a/cmd/strelaysrv/main.go b/cmd/strelaysrv/main.go index 964f4b0cc..1c0f86d42 100644 --- a/cmd/strelaysrv/main.go +++ b/cmd/strelaysrv/main.go @@ -185,7 +185,7 @@ func main() { 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{ NATLeaseM: natLease, NATRenewalM: natRenewal, diff --git a/lib/api/api_test.go b/lib/api/api_test.go index f81c12c7f..ed38f207e 100644 --- a/lib/api/api_test.go +++ b/lib/api/api_test.go @@ -113,7 +113,7 @@ func TestStopAfterBrokenConfig(t *testing.T) { 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) defer os.Remove(token) @@ -1251,7 +1251,7 @@ func TestConfigChanges(t *testing.T) { panic(err) } 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() baseURL, cancel, err := startHTTP(w) if err != nil { diff --git a/lib/api/mocked_config_test.go b/lib/api/mocked_config_test.go index 2e23b86f1..93f5f3068 100644 --- a/lib/api/mocked_config_test.go +++ b/lib/api/mocked_config_test.go @@ -142,6 +142,10 @@ func (c *mockedConfig) StunServers() []string { return nil } +func (c *mockedConfig) MyID() protocol.DeviceID { + return protocol.DeviceID{} +} + type noopWaiter struct{} func (noopWaiter) Wait() {} diff --git a/lib/config/commit_test.go b/lib/config/commit_test.go index 7c2cf9296..aca431cbc 100644 --- a/lib/config/commit_test.go +++ b/lib/config/commit_test.go @@ -44,7 +44,7 @@ func (validationError) String() string { func TestReplaceCommit(t *testing.T) { 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 { t.Fatal("Config incorrect") } diff --git a/lib/config/config.go b/lib/config/config.go index e6efd2f79..545bd6f7b 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -25,7 +25,6 @@ import ( "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/util" ) @@ -105,8 +104,6 @@ func New(myID protocol.DeviceID) Configuration { cfg.Options.UnackedNotificationIDs = []string{"authenticationUserAndPassword"} util.SetDefaults(&cfg) - util.SetDefaults(&cfg.Options) - util.SetDefaults(&cfg.GUI) // Can't happen. 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 util.SetDefaults(&cfg) - util.SetDefaults(&cfg.Options) - util.SetDefaults(&cfg.GUI) if err := xml.NewDecoder(r).Decode(&cfg); err != nil { return Configuration{}, 0, err @@ -171,8 +166,6 @@ func ReadJSON(r io.Reader, myID protocol.DeviceID) (Configuration, error) { var cfg Configuration util.SetDefaults(&cfg) - util.SetDefaults(&cfg.Options) - util.SetDefaults(&cfg.GUI) bs, err := ioutil.ReadAll(r) if err != nil { @@ -230,38 +223,61 @@ func (cfg *Configuration) WriteXML(w io.Writer) error { } func (cfg *Configuration) prepare(myID protocol.DeviceID) error { - var myName string + cfg.ensureMyDevice(myID) - // Ensure this device is present in the config - for _, device := range cfg.Devices { - 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 { + existingDevices, err := cfg.prepareFoldersAndDevices(myID) + if err != nil { return err } - // Ensure that we are part of the devices - for i := range cfg.Folders { - cfg.Folders[i].Devices = ensureDevicePresent(cfg.Folders[i].Devices, myID) - } + cfg.GUI.prepare() + + 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 } -func (cfg *Configuration) clean() error { - util.FillNilSlices(&cfg.Options) +func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) { + // 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 // - free from duplicates // - 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 }) + // 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 // 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 // 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 { folder := &cfg.Folders[i] - folder.prepare() if folder.ID == "" { - return errFolderIDEmpty + return nil, errFolderIDEmpty } 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 { - 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 + + 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 sort.Slice(cfg.Folders, func(a, b int) bool { return cfg.Folders[a].ID < cfg.Folders[b].ID }) + return sharedFolders, nil +} - // Ensure that in all folder configs - // - 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) - } - } - +func (cfg *Configuration) prepareDevices(sharedFolders map[protocol.DeviceID][]string) { for i := range cfg.Devices { cfg.Devices[i].prepare(sharedFolders[cfg.Devices[i].DeviceID]) } +} - // Very short reconnection intervals are annoying - if cfg.Options.ReconnectIntervalS < 5 { - cfg.Options.ReconnectIntervalS = 5 - } - - if cfg.GUI.APIKey == "" { - cfg.GUI.APIKey = rand.String(32) - } - +func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]bool) map[protocol.DeviceID]bool { // The list of ignored devices should not contain any devices that have // been manually added to the config. - var newIgnoredDevices []ObservedDevice - ignoredDevices := make(map[protocol.DeviceID]bool) + newIgnoredDevices := cfg.IgnoredDevices[:0] + ignoredDevices := make(map[protocol.DeviceID]bool, len(cfg.IgnoredDevices)) for _, dev := range cfg.IgnoredDevices { if !existingDevices[dev.ID] { ignoredDevices[dev.ID] = true @@ -362,7 +351,10 @@ func (cfg *Configuration) clean() error { } } 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 // ignored devices. @@ -371,7 +363,7 @@ func (cfg *Configuration) clean() error { return cfg.PendingDevices[i].Time.Before(cfg.PendingDevices[j].Time) }) - var newPendingDevices []ObservedDevice + newPendingDevices := cfg.PendingDevices[:0] nextPendingDevice: for _, pendingDevice := range cfg.PendingDevices { if !existingDevices[pendingDevice.ID] && !ignoredDevices[pendingDevice.ID] { @@ -385,7 +377,9 @@ nextPendingDevice: } } cfg.PendingDevices = newPendingDevices +} +func (cfg *Configuration) removeDeprecatedProtocols() { // Deprecated protocols are removed from the list of listeners and // device addresses. So far just kcp*. for _, prefix := range []string{"kcp"} { @@ -395,35 +389,17 @@ nextPendingDevice: dev.Addresses = filterURLSchemePrefix(dev.Addresses, prefix) } } +} - // Initialize any empty slices - if cfg.Folders == nil { - cfg.Folders = []FolderConfiguration{} - } - 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{} +func (cfg *Configuration) applyMigrations() { + if cfg.Version > 0 && cfg.Version < OldestHandledVersion { + l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version) } - 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. diff --git a/lib/config/config_test.go b/lib/config/config_test.go index fd667ab31..375e6957e 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -526,7 +526,7 @@ func TestNewSaveLoad(t *testing.T) { } intCfg := New(device1) - cfg := wrap(path, intCfg) + cfg := wrap(path, intCfg, device1) if exists(path) { t.Error(path, "exists") @@ -646,7 +646,7 @@ func TestPullOrder(t *testing.T) { if err != nil { t.Fatal(err) } - wrapper = wrap("testdata/pullorder.xml", cfg) + wrapper = wrap("testdata/pullorder.xml", cfg, device1) folders = wrapper.Folders() 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 { t.Fatal(err) } @@ -959,7 +960,7 @@ func TestIssue4219(t *testing.T) { 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") { 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) } - 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") } } @@ -1175,8 +1180,8 @@ func load(path string, myID protocol.DeviceID) (Wrapper, error) { return cfg, err } -func wrap(path string, cfg Configuration) Wrapper { - return Wrap(path, cfg, events.NoopLogger) +func wrap(path string, cfg Configuration, myID protocol.DeviceID) Wrapper { + return Wrap(path, cfg, myID, events.NoopLogger) } func TestInternalVersioningConfiguration(t *testing.T) { diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index 25f840a7e..6996bbfdb 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "runtime" + "sort" "strings" "time" @@ -43,7 +44,7 @@ func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs. util.SetDefaults(&f) - f.prepare() + f.prepare(myID, nil) return f } @@ -182,7 +183,19 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID { 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 { f.RescanIntervalS = MaxRescanIntervalS } else if f.RescanIntervalS < 0 { @@ -194,9 +207,6 @@ func (f *FolderConfiguration) prepare() { f.FSWatcherDelayS = 10 } - if f.Versioning.Params == nil { - f.Versioning.Params = make(map[string]string) - } if f.Versioning.CleanupIntervalS > MaxRescanIntervalS { f.Versioning.CleanupIntervalS = MaxRescanIntervalS } else if f.Versioning.CleanupIntervalS < 0 { diff --git a/lib/config/guiconfiguration.go b/lib/config/guiconfiguration.go index 826870cce..7b9830b30 100644 --- a/lib/config/guiconfiguration.go +++ b/lib/config/guiconfiguration.go @@ -11,6 +11,8 @@ import ( "os" "strconv" "strings" + + "github.com/syncthing/syncthing/lib/rand" ) 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 { return c } diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go index f469a3f4b..e7e5bab80 100644 --- a/lib/config/optionsconfiguration.go +++ b/lib/config/optionsconfiguration.go @@ -28,6 +28,27 @@ func (opts OptionsConfiguration) Copy() OptionsConfiguration { 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 // restart on change. func (opts OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration { diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go index 87782ffa5..9ebe0cd31 100644 --- a/lib/config/wrapper.go +++ b/lib/config/wrapper.go @@ -55,6 +55,7 @@ func (noopWaiter) Wait() {} // notifications of changes to registered Handlers type Wrapper interface { ConfigPath() string + MyID() protocol.DeviceID RawCopy() Configuration Replace(cfg Configuration) (Waiter, error) @@ -97,6 +98,7 @@ type wrapper struct { cfg Configuration path string evLogger events.Logger + myID protocol.DeviceID waiter Waiter // Latest ongoing config change subs []Committer @@ -107,11 +109,12 @@ type wrapper struct { // Wrap wraps an existing Configuration structure and ties it to a file on // 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{ cfg: cfg, path: path, evLogger: evLogger, + myID: myID, waiter: noopWaiter{}, // Noop until first config change mut: sync.NewMutex(), } @@ -132,13 +135,17 @@ func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper, return nil, 0, err } - return Wrap(path, cfg, evLogger), originalVersion, nil + return Wrap(path, cfg, myID, evLogger), originalVersion, nil } func (w *wrapper) ConfigPath() string { return w.path } +func (w *wrapper) MyID() protocol.DeviceID { + return w.myID +} + // Subscribe registers the given handler to be called on any future // configuration changes. 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) { from := w.cfg - if err := to.clean(); err != nil { + if err := to.prepare(w.myID); err != nil { return noopWaiter{}, err } diff --git a/lib/connections/lan_test.go b/lib/connections/lan_test.go index 99f7d1310..ec2301dee 100644 --- a/lib/connections/lan_test.go +++ b/lib/connections/lan_test.go @@ -11,6 +11,7 @@ import ( "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" + "github.com/syncthing/syncthing/lib/protocol" ) func TestIsLANHost(t *testing.T) { @@ -36,7 +37,7 @@ func TestIsLANHost(t *testing.T) { Options: config.OptionsConfiguration{ AlwaysLocalNets: []string{"10.20.30.0/24"}, }, - }, events.NoopLogger) + }, protocol.LocalDeviceID, events.NoopLogger) s := &service{cfg: cfg} for _, tc := range cases { diff --git a/lib/connections/limiter_test.go b/lib/connections/limiter_test.go index 43baf27a1..6483e3e20 100644 --- a/lib/connections/limiter_test.go +++ b/lib/connections/limiter_test.go @@ -30,7 +30,7 @@ func init() { } 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") dev2Conf = config.NewDeviceConfiguration(device2, "device2") dev3Conf = config.NewDeviceConfiguration(device3, "device3") diff --git a/lib/discover/cache_test.go b/lib/discover/cache_test.go index dd79d3ec8..bcc571efd 100644 --- a/lib/discover/cache_test.go +++ b/lib/discover/cache_test.go @@ -23,7 +23,7 @@ func setupCache() *manager { cfg.Options.LocalAnnEnabled = 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) { diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 2293ea8ba..24ef57e3f 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -115,7 +115,7 @@ func createTmpWrapper(cfg config.Configuration) config.Wrapper { if err != nil { panic(err) } - wrapper := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger) + wrapper := config.Wrap(tmpFile.Name(), cfg, myID, events.NoopLogger) tmpFile.Close() return wrapper } @@ -331,7 +331,7 @@ func TestDeviceRename(t *testing.T) { 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()) m := newModel(cfg, myID, "syncthing", "dev", db, nil) diff --git a/lib/nat/structs_test.go b/lib/nat/structs_test.go index 626d89271..661a552f8 100644 --- a/lib/nat/structs_test.go +++ b/lib/nat/structs_test.go @@ -64,7 +64,7 @@ func TestMappingClearAddresses(t *testing.T) { if err != nil { 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()) tmpFile.Close() diff --git a/lib/syncthing/syncthing_test.go b/lib/syncthing/syncthing_test.go index 4d5c3065e..e52f80cbb 100644 --- a/lib/syncthing/syncthing_test.go +++ b/lib/syncthing/syncthing_test.go @@ -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, 1, 1}}, // first 56 bits same, differ in the first 64 bits }, - }, events.NoopLogger) + }, protocol.LocalDeviceID, events.NoopLogger) defer os.Remove(cfg.ConfigPath()) 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, 1}}, // first 64 bits same }, - }, events.NoopLogger) + }, protocol.LocalDeviceID, events.NoopLogger) if err := checkShortIDs(cfg); err == nil { t.Error("Should have gotten an error") @@ -76,7 +76,7 @@ func TestStartupFail(t *testing.T) { {DeviceID: id}, {DeviceID: conflID}, }, - }, events.NoopLogger) + }, protocol.LocalDeviceID, events.NoopLogger) defer os.Remove(cfg.ConfigPath()) db := backend.OpenMemory() diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index f2a119e32..2b92a862f 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -49,12 +49,12 @@ func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, if noDefaultFolder { 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))) 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 diff --git a/lib/util/utils.go b/lib/util/utils.go index 2d3315bdd..8f26c4634 100644 --- a/lib/util/utils.go +++ b/lib/util/utils.go @@ -83,6 +83,10 @@ func SetDefaults(data interface{}) { default: 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 } +func FillNilExceptDeprecated(data interface{}) { + fillNil(data, true) +} + func FillNil(data interface{}) { + fillNil(data, false) +} + +func fillNil(data interface{}, skipDeprecated bool) { s := reflect.ValueOf(data).Elem() + t := s.Type() for i := 0; i < s.NumField(); i++ { + if skipDeprecated && strings.HasPrefix(t.Field(i).Name, "Deprecated") { + continue + } + f := s.Field(i) for f.Kind() == reflect.Ptr && f.IsZero() && f.CanSet() { @@ -160,9 +177,19 @@ func FillNil(data interface{}) { } } - if f.Kind() == reflect.Struct && f.CanAddr() { - if addr := f.Addr(); addr.CanInterface() { - FillNil(addr.Interface()) + switch f.Kind() { + case reflect.Slice: + 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) + } } } } diff --git a/lib/watchaggregator/aggregator_test.go b/lib/watchaggregator/aggregator_test.go index aa6d4f731..20deed05d 100644 --- a/lib/watchaggregator/aggregator_test.go +++ b/lib/watchaggregator/aggregator_test.go @@ -18,6 +18,7 @@ import ( "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" + "github.com/syncthing/syncthing/lib/protocol" ) func TestMain(m *testing.M) { @@ -47,7 +48,7 @@ var ( } defaultCfg = config.Wrap("", config.Configuration{ Folders: []config.FolderConfiguration{defaultFolderCfg}, - }, events.NoopLogger) + }, protocol.LocalDeviceID, events.NoopLogger) ) // Represents possibly multiple (different event types) expected paths from