gui, lib/config, lib/model: Support auto-accepting folders (fixes #2299)
Also introduces a new Waiter interface for config changes and segments the configuration GUI. GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4551
This commit is contained in:
committed by
Jakob Borg
parent
c005b8dcb0
commit
445c4edeca
@@ -52,7 +52,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
// Replace config. We should get back a clean response and the config
|
||||
// should change.
|
||||
|
||||
err := w.Replace(Configuration{Version: 1})
|
||||
_, err := w.Replace(Configuration{Version: 1})
|
||||
if err != nil {
|
||||
t.Fatal("Should not have a validation error:", err)
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
sub0 := requiresRestart{committed: make(chan struct{}, 1)}
|
||||
w.Subscribe(sub0)
|
||||
|
||||
err = w.Replace(Configuration{Version: 2})
|
||||
_, err = w.Replace(Configuration{Version: 2})
|
||||
if err != nil {
|
||||
t.Fatal("Should not have a validation error:", err)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
|
||||
w.Subscribe(validationError{})
|
||||
|
||||
err = w.Replace(Configuration{Version: 3})
|
||||
_, err = w.Replace(Configuration{Version: 3})
|
||||
if err == nil {
|
||||
t.Fatal("Should have a validation error")
|
||||
}
|
||||
|
||||
@@ -826,7 +826,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
|
||||
t.Error("Should have less devices")
|
||||
}
|
||||
|
||||
err = wrapper.Replace(raw)
|
||||
_, err = wrapper.Replace(raw)
|
||||
if err != nil {
|
||||
t.Errorf("Failed: %s", err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type DeviceConfiguration struct {
|
||||
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
|
||||
Paused bool `xml:"paused" json:"paused"`
|
||||
AllowedNetworks []string `xml:"allowedNetwork,omitempty" json:"allowedNetworks"`
|
||||
AutoAcceptFolders bool `xml:"autoAcceptFolders" json:"autoAcceptFolders"`
|
||||
}
|
||||
|
||||
func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {
|
||||
|
||||
@@ -24,7 +24,7 @@ var (
|
||||
const DefaultMarkerName = ".stfolder"
|
||||
|
||||
type FolderConfiguration struct {
|
||||
ID string `xml:"id,attr" json:"id"`
|
||||
ID string `xml:"id,attr" json:"id" restart:"false"`
|
||||
Label string `xml:"label,attr" json:"label"`
|
||||
FilesystemType fs.FilesystemType `xml:"filesystemType" json:"filesystemType"`
|
||||
Path string `xml:"path,attr" json:"path"`
|
||||
@@ -62,11 +62,18 @@ type FolderDeviceConfiguration struct {
|
||||
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
|
||||
}
|
||||
|
||||
func NewFolderConfiguration(id string, fsType fs.FilesystemType, path string) FolderConfiguration {
|
||||
func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration {
|
||||
f := FolderConfiguration{
|
||||
ID: id,
|
||||
FilesystemType: fsType,
|
||||
Path: path,
|
||||
ID: id,
|
||||
Label: label,
|
||||
RescanIntervalS: 60,
|
||||
FSWatcherDelayS: 10,
|
||||
MinDiskFree: Size{Value: 1, Unit: "%"},
|
||||
Devices: []FolderDeviceConfiguration{{DeviceID: myID}},
|
||||
AutoNormalize: true,
|
||||
MaxConflicts: -1,
|
||||
FilesystemType: fsType,
|
||||
Path: path,
|
||||
}
|
||||
f.prepare()
|
||||
return f
|
||||
|
||||
@@ -96,11 +96,11 @@ func (WeakHashSelectionMethod) ParseDefault(value string) (interface{}, error) {
|
||||
|
||||
type OptionsConfiguration struct {
|
||||
ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
|
||||
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default"`
|
||||
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
|
||||
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
|
||||
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027"`
|
||||
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
|
||||
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default" restart:"true"`
|
||||
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" restart:"true"`
|
||||
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" restart:"true"`
|
||||
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" restart:"true"`
|
||||
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027" restart:"true"`
|
||||
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
|
||||
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
|
||||
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
|
||||
@@ -117,21 +117,21 @@ type OptionsConfiguration struct {
|
||||
URURL string `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
|
||||
URPostInsecurely bool `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
|
||||
URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
|
||||
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
|
||||
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
|
||||
UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases"` // when auto upgrades are enabled
|
||||
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
|
||||
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false"`
|
||||
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" restart:"true"`
|
||||
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" restart:"true"` // 0 for off
|
||||
UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases" restart:"true"` // when auto upgrades are enabled
|
||||
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
|
||||
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false" restart:"true"`
|
||||
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
|
||||
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
|
||||
MinHomeDiskFree Size `xml:"minHomeDiskFree" json:"minHomeDiskFree" default:"1 %"`
|
||||
ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
|
||||
ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json" restart:"true"`
|
||||
AlwaysLocalNets []string `xml:"alwaysLocalNet" json:"alwaysLocalNets"`
|
||||
OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
|
||||
TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
|
||||
UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
|
||||
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
|
||||
WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod"`
|
||||
WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod" restart:"true"`
|
||||
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
|
||||
StunKeepaliveS int `xml:"stunKeepaliveSeconds" json:"stunKeepaliveSeconds" default:"24"`
|
||||
KCPNoDelay bool `xml:"kcpNoDelay" json:"kcpNoDelay" default:"false"`
|
||||
|
||||
@@ -45,6 +45,15 @@ type Committer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// Waiter allows to wait for the given config operation to complete.
|
||||
type Waiter interface {
|
||||
Wait()
|
||||
}
|
||||
|
||||
type noopWaiter struct{}
|
||||
|
||||
func (noopWaiter) Wait() {}
|
||||
|
||||
// A wrapper around a Configuration that manages loads, saves and published
|
||||
// notifications of changes to registered Handlers
|
||||
|
||||
@@ -130,37 +139,25 @@ func (w *Wrapper) RawCopy() Configuration {
|
||||
return w.cfg.Copy()
|
||||
}
|
||||
|
||||
// ReplaceBlocking swaps the current configuration object for the given one,
|
||||
// and waits for subscribers to be notified.
|
||||
func (w *Wrapper) ReplaceBlocking(cfg Configuration) error {
|
||||
w.mut.Lock()
|
||||
wg := sync.NewWaitGroup()
|
||||
err := w.replaceLocked(cfg, wg)
|
||||
w.mut.Unlock()
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace swaps the current configuration object for the given one.
|
||||
func (w *Wrapper) Replace(cfg Configuration) error {
|
||||
func (w *Wrapper) Replace(cfg Configuration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
return w.replaceLocked(cfg, nil)
|
||||
return w.replaceLocked(cfg)
|
||||
}
|
||||
|
||||
func (w *Wrapper) replaceLocked(to Configuration, wg sync.WaitGroup) error {
|
||||
func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
|
||||
from := w.cfg
|
||||
|
||||
if err := to.clean(); err != nil {
|
||||
return err
|
||||
return noopWaiter{}, err
|
||||
}
|
||||
|
||||
for _, sub := range w.subs {
|
||||
l.Debugln(sub, "verifying configuration")
|
||||
if err := sub.VerifyConfiguration(from, to); err != nil {
|
||||
l.Debugln(sub, "rejected config:", err)
|
||||
return err
|
||||
return noopWaiter{}, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,23 +165,19 @@ func (w *Wrapper) replaceLocked(to Configuration, wg sync.WaitGroup) error {
|
||||
w.deviceMap = nil
|
||||
w.folderMap = nil
|
||||
|
||||
w.notifyListeners(from, to, wg)
|
||||
|
||||
return nil
|
||||
return w.notifyListeners(from, to), nil
|
||||
}
|
||||
|
||||
func (w *Wrapper) notifyListeners(from, to Configuration, wg sync.WaitGroup) {
|
||||
if wg != nil {
|
||||
wg.Add(len(w.subs))
|
||||
}
|
||||
func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
|
||||
wg := sync.NewWaitGroup()
|
||||
wg.Add(len(w.subs))
|
||||
for _, sub := range w.subs {
|
||||
go func(commiter Committer) {
|
||||
w.notifyListener(commiter, from.Copy(), to.Copy())
|
||||
if wg != nil {
|
||||
wg.Done()
|
||||
}
|
||||
wg.Done()
|
||||
}(sub)
|
||||
}
|
||||
return wg
|
||||
}
|
||||
|
||||
func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
|
||||
@@ -211,7 +204,7 @@ func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
|
||||
|
||||
// SetDevices adds new devices to the configuration, or overwrites existing
|
||||
// devices with the same ID.
|
||||
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) error {
|
||||
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -231,17 +224,17 @@ func (w *Wrapper) SetDevices(devs []DeviceConfiguration) error {
|
||||
}
|
||||
}
|
||||
|
||||
return w.replaceLocked(newCfg, nil)
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// SetDevice adds a new device to the configuration, or overwrites an existing
|
||||
// device with the same ID.
|
||||
func (w *Wrapper) SetDevice(dev DeviceConfiguration) error {
|
||||
func (w *Wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
|
||||
return w.SetDevices([]DeviceConfiguration{dev})
|
||||
}
|
||||
|
||||
// RemoveDevice removes the device from the configuration
|
||||
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) error {
|
||||
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -255,10 +248,10 @@ func (w *Wrapper) RemoveDevice(id protocol.DeviceID) error {
|
||||
}
|
||||
}
|
||||
if !removed {
|
||||
return nil
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
return w.replaceLocked(newCfg, nil)
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// Folders returns a map of folders. Folder structures should not be changed,
|
||||
@@ -277,7 +270,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
|
||||
|
||||
// SetFolder adds a new folder to the configuration, or overwrites an existing
|
||||
// folder with the same ID.
|
||||
func (w *Wrapper) SetFolder(fld FolderConfiguration) error {
|
||||
func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -294,7 +287,7 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) error {
|
||||
newCfg.Folders = append(w.cfg.Folders, fld)
|
||||
}
|
||||
|
||||
return w.replaceLocked(newCfg, nil)
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// Options returns the current options configuration object.
|
||||
@@ -305,12 +298,12 @@ func (w *Wrapper) Options() OptionsConfiguration {
|
||||
}
|
||||
|
||||
// SetOptions replaces the current options configuration object.
|
||||
func (w *Wrapper) SetOptions(opts OptionsConfiguration) error {
|
||||
func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.Options = opts
|
||||
return w.replaceLocked(newCfg, nil)
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// GUI returns the current GUI configuration object.
|
||||
@@ -321,12 +314,12 @@ func (w *Wrapper) GUI() GUIConfiguration {
|
||||
}
|
||||
|
||||
// SetGUI replaces the current GUI configuration object.
|
||||
func (w *Wrapper) SetGUI(gui GUIConfiguration) error {
|
||||
func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.GUI = gui
|
||||
return w.replaceLocked(newCfg, nil)
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// IgnoredDevice returns whether or not connection attempts from the given
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
stdsync "sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
@@ -758,7 +759,7 @@ func dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (interna
|
||||
for _, prio := range priorities {
|
||||
tgts := dialTargetBuckets[prio]
|
||||
res := make(chan internalConn, len(tgts))
|
||||
wg := sync.NewWaitGroup()
|
||||
wg := stdsync.WaitGroup{}
|
||||
for _, tgt := range tgts {
|
||||
wg.Add(1)
|
||||
go func(tgt dialTarget) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/stats"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
"github.com/syncthing/syncthing/lib/weakhash"
|
||||
"github.com/thejerf/suture"
|
||||
@@ -892,6 +893,8 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
}
|
||||
|
||||
dbLocation := filepath.Dir(m.db.Location())
|
||||
changed := false
|
||||
deviceCfg := m.cfg.Devices()[deviceID]
|
||||
|
||||
// See issue #3802 - in short, we can't send modern symlink entries to older
|
||||
// clients.
|
||||
@@ -901,6 +904,13 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
dropSymlinks = true
|
||||
}
|
||||
|
||||
// Needs to happen outside of the fmut, as can cause CommitConfiguration
|
||||
if deviceCfg.AutoAcceptFolders {
|
||||
for _, folder := range cm.Folders {
|
||||
changed = m.handleAutoAccepts(deviceCfg, folder) || changed
|
||||
}
|
||||
}
|
||||
|
||||
m.fmut.Lock()
|
||||
var paused []string
|
||||
for _, folder := range cm.Folders {
|
||||
@@ -927,6 +937,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
|
||||
continue
|
||||
}
|
||||
|
||||
if !folder.DisableTempIndexes {
|
||||
tempIndexFolders = append(tempIndexFolders, folder.ID)
|
||||
}
|
||||
@@ -1021,8 +1032,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
}
|
||||
}
|
||||
|
||||
var changed = false
|
||||
if deviceCfg := m.cfg.Devices()[deviceID]; deviceCfg.Introducer {
|
||||
if deviceCfg.Introducer {
|
||||
foldersDevices, introduced := m.handleIntroductions(deviceCfg, cm)
|
||||
if introduced {
|
||||
changed = true
|
||||
@@ -1063,6 +1073,11 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
|
||||
// the folder.
|
||||
nextDevice:
|
||||
for _, device := range folder.Devices {
|
||||
// No need to share with self.
|
||||
if device.ID == m.id {
|
||||
continue
|
||||
}
|
||||
|
||||
foldersDevices.set(device.ID, folder.ID)
|
||||
|
||||
if _, ok := m.cfg.Devices()[device.ID]; !ok {
|
||||
@@ -1081,7 +1096,8 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
|
||||
|
||||
// We don't yet share this folder with this device. Add the device
|
||||
// to sharing list of the folder.
|
||||
m.introduceDeviceToFolder(device, folder, introducerCfg)
|
||||
l.Infof("Sharing folder %s with %v (vouched for by introducer %v)", folder.Description(), device.ID, introducerCfg.DeviceID)
|
||||
m.shareFolderWithDeviceLocked(device.ID, folder.ID, introducerCfg.DeviceID)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
@@ -1089,7 +1105,7 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
|
||||
return foldersDevices, changed
|
||||
}
|
||||
|
||||
// handleIntroductions handles removals of devices/shares that are removed by an introducer device
|
||||
// handleDeintroductions handles removals of devices/shares that are removed by an introducer device
|
||||
func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
|
||||
changed := false
|
||||
foldersIntroducedByOthers := make(folderDeviceSet)
|
||||
@@ -1142,6 +1158,51 @@ func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
|
||||
return changed
|
||||
}
|
||||
|
||||
// handleAutoAccepts handles adding and sharing folders for devices that have
|
||||
// AutoAcceptFolders set to true.
|
||||
func (m *Model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) bool {
|
||||
if _, ok := m.cfg.Folder(folder.ID); !ok {
|
||||
defaultPath := m.cfg.Options().DefaultFolderPath
|
||||
defaultPathFs := fs.NewFilesystem(fs.FilesystemTypeBasic, defaultPath)
|
||||
for _, path := range []string{folder.Label, folder.ID} {
|
||||
if _, err := defaultPathFs.Lstat(path); !fs.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
fcfg := config.NewFolderConfiguration(m.id, folder.ID, folder.Label, fs.FilesystemTypeBasic, filepath.Join(defaultPath, path))
|
||||
|
||||
// Need to wait for the waiter, as this calls CommitConfiguration,
|
||||
// which sets up the folder and as we return from this call,
|
||||
// ClusterConfig starts poking at m.folderFiles and other things
|
||||
// that might not exist until the config is committed.
|
||||
w, _ := m.cfg.SetFolder(fcfg)
|
||||
w.Wait()
|
||||
|
||||
// This needs to happen under a lock.
|
||||
m.fmut.Lock()
|
||||
w = m.shareFolderWithDeviceLocked(deviceCfg.DeviceID, folder.ID, protocol.DeviceID{})
|
||||
m.fmut.Unlock()
|
||||
w.Wait()
|
||||
l.Infof("Auto-accepted %s folder %s at path %s", deviceCfg.DeviceID, folder.Description(), fcfg.Path)
|
||||
return true
|
||||
}
|
||||
l.Infof("Failed to auto-accept folder %s from %s due to path conflict", folder.Description(), deviceCfg.DeviceID)
|
||||
return false
|
||||
}
|
||||
|
||||
// Folder already exists.
|
||||
if !m.folderSharedWith(folder.ID, deviceCfg.DeviceID) {
|
||||
m.fmut.Lock()
|
||||
w := m.shareFolderWithDeviceLocked(deviceCfg.DeviceID, folder.ID, protocol.DeviceID{})
|
||||
m.fmut.Unlock()
|
||||
w.Wait()
|
||||
l.Infof("Shared %s with %s due to auto-accept", folder.ID, deviceCfg.DeviceID)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
|
||||
addresses := []string{"dynamic"}
|
||||
for _, addr := range device.Addresses {
|
||||
@@ -1170,18 +1231,17 @@ func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.Dev
|
||||
m.cfg.SetDevice(newDeviceCfg)
|
||||
}
|
||||
|
||||
func (m *Model) introduceDeviceToFolder(device protocol.Device, folder protocol.Folder, introducerCfg config.DeviceConfiguration) {
|
||||
l.Infof("Sharing folder %s with %v (vouched for by introducer %v)", folder.Description(), device.ID, introducerCfg.DeviceID)
|
||||
func (m *Model) shareFolderWithDeviceLocked(deviceID protocol.DeviceID, folder string, introducer protocol.DeviceID) config.Waiter {
|
||||
m.deviceFolders[deviceID] = append(m.deviceFolders[deviceID], folder)
|
||||
m.folderDevices.set(deviceID, folder)
|
||||
|
||||
m.deviceFolders[device.ID] = append(m.deviceFolders[device.ID], folder.ID)
|
||||
m.folderDevices.set(device.ID, folder.ID)
|
||||
|
||||
folderCfg := m.cfg.Folders()[folder.ID]
|
||||
folderCfg := m.cfg.Folders()[folder]
|
||||
folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{
|
||||
DeviceID: device.ID,
|
||||
IntroducedBy: introducerCfg.DeviceID,
|
||||
DeviceID: deviceID,
|
||||
IntroducedBy: introducer,
|
||||
})
|
||||
m.cfg.SetFolder(folderCfg)
|
||||
w, _ := m.cfg.SetFolder(folderCfg)
|
||||
return w
|
||||
}
|
||||
|
||||
// Closed is called when a connection has been closed
|
||||
@@ -1486,7 +1546,7 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
|
||||
conn.ClusterConfig(cm)
|
||||
|
||||
device, ok := m.cfg.Devices()[deviceID]
|
||||
if ok && (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) {
|
||||
if ok && (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
|
||||
device.Name = hello.DeviceName
|
||||
m.cfg.SetDevice(device)
|
||||
m.cfg.Save()
|
||||
@@ -2366,10 +2426,10 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
if _, ok := fromFolders[folderID]; !ok {
|
||||
// A folder was added.
|
||||
if cfg.Paused {
|
||||
l.Infoln(m, "Paused folder", cfg.Description())
|
||||
l.Infoln("Paused folder", cfg.Description())
|
||||
cfg.CreateRoot()
|
||||
} else {
|
||||
l.Infoln(m, "Adding folder", cfg.Description())
|
||||
l.Infoln("Adding folder", cfg.Description())
|
||||
m.AddFolder(cfg)
|
||||
m.StartFolder(folderID)
|
||||
}
|
||||
@@ -2388,8 +2448,12 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// Check if anything differs, apart from the label.
|
||||
toCfgCopy := toCfg
|
||||
fromCfgCopy := fromCfg
|
||||
fromCfgCopy.Label = ""
|
||||
toCfgCopy.Label = ""
|
||||
util.CopyMatchingTag(&toCfgCopy, &fromCfgCopy, "restart", func(v string) bool {
|
||||
if len(v) > 0 && v != "false" {
|
||||
panic(fmt.Sprintf(`unexpected struct value: %s. expected untagged or "false"`, v))
|
||||
}
|
||||
return v == "false"
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(fromCfgCopy, toCfgCopy) {
|
||||
m.RestartFolder(toCfg)
|
||||
@@ -2432,17 +2496,15 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
|
||||
// Some options don't require restart as those components handle it fine
|
||||
// by themselves.
|
||||
from.Options.URAccepted = to.Options.URAccepted
|
||||
from.Options.URSeen = to.Options.URSeen
|
||||
from.Options.URUniqueID = to.Options.URUniqueID
|
||||
from.Options.ListenAddresses = to.Options.ListenAddresses
|
||||
from.Options.RelaysEnabled = to.Options.RelaysEnabled
|
||||
from.Options.UnackedNotificationIDs = to.Options.UnackedNotificationIDs
|
||||
from.Options.MaxRecvKbps = to.Options.MaxRecvKbps
|
||||
from.Options.MaxSendKbps = to.Options.MaxSendKbps
|
||||
from.Options.LimitBandwidthInLan = to.Options.LimitBandwidthInLan
|
||||
from.Options.StunKeepaliveS = to.Options.StunKeepaliveS
|
||||
from.Options.StunServers = to.Options.StunServers
|
||||
|
||||
// Copy fields that do not have the field set to true
|
||||
util.CopyMatchingTag(&from.Options, &to.Options, "restart", func(v string) bool {
|
||||
if len(v) > 0 && v != "true" {
|
||||
panic(fmt.Sprintf(`unexpected struct value: %s. expected untagged or "true"`, v))
|
||||
}
|
||||
return v != "true"
|
||||
})
|
||||
|
||||
// All of the other generic options require restart. Or at least they may;
|
||||
// removing this check requires going through those options carefully and
|
||||
// making sure there are individual services that handle them correctly.
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -36,13 +37,14 @@ var device1, device2 protocol.DeviceID
|
||||
var defaultConfig *config.Wrapper
|
||||
var defaultFolderConfig config.FolderConfiguration
|
||||
var defaultFs fs.Filesystem
|
||||
var defaultAutoAcceptCfg config.Configuration
|
||||
|
||||
func init() {
|
||||
device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
|
||||
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
|
||||
defaultFs = fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata")
|
||||
|
||||
defaultFolderConfig = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "testdata")
|
||||
defaultFolderConfig = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
|
||||
defaultFolderConfig.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
|
||||
_defaultConfig := config.Configuration{
|
||||
Folders: []config.FolderConfiguration{defaultFolderConfig},
|
||||
@@ -53,6 +55,17 @@ func init() {
|
||||
},
|
||||
}
|
||||
defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
|
||||
defaultAutoAcceptCfg = config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
AutoAcceptFolders: true,
|
||||
},
|
||||
},
|
||||
Options: config.OptionsConfiguration{
|
||||
DefaultFolderPath: "testdata",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var testDataExpected = map[string]protocol.FileInfo{
|
||||
@@ -87,6 +100,20 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func newState(cfg config.Configuration) (*config.Wrapper, *Model) {
|
||||
db := db.OpenMemory()
|
||||
|
||||
wcfg := config.Wrap("/tmp/test", cfg)
|
||||
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
for _, folder := range cfg.Folders {
|
||||
m.AddFolder(folder)
|
||||
}
|
||||
m.ServeBackground()
|
||||
m.AddConnection(&fakeConnection{id: device1}, protocol.HelloResult{})
|
||||
return wcfg, m
|
||||
}
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
db := db.OpenMemory()
|
||||
|
||||
@@ -609,20 +636,6 @@ func TestIntroducer(t *testing.T) {
|
||||
return false
|
||||
}
|
||||
|
||||
newState := func(cfg config.Configuration) (*config.Wrapper, *Model) {
|
||||
db := db.OpenMemory()
|
||||
|
||||
wcfg := config.Wrap("/tmp/test", cfg)
|
||||
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
for _, folder := range cfg.Folders {
|
||||
m.AddFolder(folder)
|
||||
}
|
||||
m.ServeBackground()
|
||||
m.AddConnection(&fakeConnection{id: device1}, protocol.HelloResult{})
|
||||
return wcfg, m
|
||||
}
|
||||
|
||||
wcfg, m := newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
@@ -970,6 +983,237 @@ func TestIntroducer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptRejected(t *testing.T) {
|
||||
// Nothing happens if AutoAcceptFolders not set
|
||||
tcfg := defaultAutoAcceptCfg.Copy()
|
||||
tcfg.Devices[0].AutoAcceptFolders = false
|
||||
wcfg, m := newState(tcfg)
|
||||
id := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if _, ok := wcfg.Folder(id); ok || m.folderSharedWith(id, device1) {
|
||||
t.Error("unexpected shared", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptNewFolder(t *testing.T) {
|
||||
// New folder
|
||||
wcfg, m := newState(defaultAutoAcceptCfg)
|
||||
id := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: id,
|
||||
},
|
||||
},
|
||||
})
|
||||
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
|
||||
t.Error("expected shared", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptMultipleFolders(t *testing.T) {
|
||||
// Multiple new folders
|
||||
wcfg, m := newState(defaultAutoAcceptCfg)
|
||||
id1 := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id1))
|
||||
id2 := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id2))
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id1,
|
||||
Label: id1,
|
||||
},
|
||||
{
|
||||
ID: id2,
|
||||
Label: id2,
|
||||
},
|
||||
},
|
||||
})
|
||||
if _, ok := wcfg.Folder(id1); !ok || !m.folderSharedWith(id1, device1) {
|
||||
t.Error("expected shared", id1)
|
||||
}
|
||||
if _, ok := wcfg.Folder(id2); !ok || !m.folderSharedWith(id2, device1) {
|
||||
t.Error("expected shared", id2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptExistingFolder(t *testing.T) {
|
||||
// Existing folder
|
||||
id := srand.String(8)
|
||||
idOther := srand.String(8) // To check that path does not get changed.
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
|
||||
tcfg := defaultAutoAcceptCfg.Copy()
|
||||
tcfg.Folders = []config.FolderConfiguration{
|
||||
{
|
||||
ID: id,
|
||||
Path: filepath.Join("testdata", idOther), // To check that path does not get changed.
|
||||
},
|
||||
}
|
||||
wcfg, m := newState(tcfg)
|
||||
if _, ok := wcfg.Folder(id); !ok || m.folderSharedWith(id, device1) {
|
||||
t.Error("missing folder, or shared", id)
|
||||
}
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || fcfg.Path != filepath.Join("testdata", idOther) {
|
||||
t.Error("missing folder, or unshared, or path changed", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptNewAndExistingFolder(t *testing.T) {
|
||||
// New and existing folder
|
||||
id1 := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id1))
|
||||
id2 := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id2))
|
||||
|
||||
tcfg := defaultAutoAcceptCfg.Copy()
|
||||
tcfg.Folders = []config.FolderConfiguration{
|
||||
{
|
||||
ID: id1,
|
||||
Path: filepath.Join("testdata", id1), // from previous test case, to verify that path doesn't get changed.
|
||||
},
|
||||
}
|
||||
wcfg, m := newState(tcfg)
|
||||
if _, ok := wcfg.Folder(id1); !ok || m.folderSharedWith(id1, device1) {
|
||||
t.Error("missing folder, or shared", id1)
|
||||
}
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id1,
|
||||
Label: id1,
|
||||
},
|
||||
{
|
||||
ID: id2,
|
||||
Label: id2,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
for i, id := range []string{id1, id2} {
|
||||
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
|
||||
t.Error("missing folder, or unshared", i, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptAlreadyShared(t *testing.T) {
|
||||
// Already shared
|
||||
id := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
tcfg := defaultAutoAcceptCfg.Copy()
|
||||
tcfg.Folders = []config.FolderConfiguration{
|
||||
{
|
||||
ID: id,
|
||||
Path: filepath.Join("testdata", id),
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
wcfg, m := newState(tcfg)
|
||||
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
|
||||
t.Error("missing folder, or not shared", id)
|
||||
}
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
|
||||
t.Error("missing folder, or not shared", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptNameConflict(t *testing.T) {
|
||||
id := srand.String(8)
|
||||
label := srand.String(8)
|
||||
os.MkdirAll(filepath.Join("testdata", id), 0777)
|
||||
os.MkdirAll(filepath.Join("testdata", label), 0777)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
defer os.RemoveAll(filepath.Join("testdata", label))
|
||||
wcfg, m := newState(defaultAutoAcceptCfg)
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: label,
|
||||
},
|
||||
},
|
||||
})
|
||||
if _, ok := wcfg.Folder(id); ok || m.folderSharedWith(id, device1) {
|
||||
t.Error("unexpected folder", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptPrefersLabel(t *testing.T) {
|
||||
// Prefers label, falls back to ID.
|
||||
wcfg, m := newState(defaultAutoAcceptCfg)
|
||||
id := srand.String(8)
|
||||
label := srand.String(8)
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
defer os.RemoveAll(filepath.Join("testdata", label))
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: label,
|
||||
},
|
||||
},
|
||||
})
|
||||
if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || !strings.HasSuffix(fcfg.Path, label) {
|
||||
t.Error("expected shared, or wrong path", id, label, fcfg.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoAcceptFallsBackToID(t *testing.T) {
|
||||
// Prefers label, falls back to ID.
|
||||
wcfg, m := newState(defaultAutoAcceptCfg)
|
||||
id := srand.String(8)
|
||||
label := srand.String(8)
|
||||
os.MkdirAll(filepath.Join("testdata", label), 0777)
|
||||
defer os.RemoveAll(filepath.Join("testdata", label))
|
||||
defer os.RemoveAll(filepath.Join("testdata", id))
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: id,
|
||||
Label: label,
|
||||
},
|
||||
},
|
||||
})
|
||||
if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || !strings.HasSuffix(fcfg.Path, id) {
|
||||
t.Error("expected shared, or wrong path", id, label, fcfg.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func changeIgnores(t *testing.T, m *Model, expected []string) {
|
||||
arrEqual := func(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
@@ -1920,7 +2164,9 @@ func TestIssue4357(t *testing.T) {
|
||||
defer m.Stop()
|
||||
|
||||
// Force the model to wire itself and add the folders
|
||||
if err := wrapper.ReplaceBlocking(cfg); err != nil {
|
||||
p, err := wrapper.Replace(cfg)
|
||||
p.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -1931,7 +2177,9 @@ func TestIssue4357(t *testing.T) {
|
||||
newCfg := wrapper.RawCopy()
|
||||
newCfg.Folders[0].Paused = true
|
||||
|
||||
if err := wrapper.ReplaceBlocking(newCfg); err != nil {
|
||||
p, err = wrapper.Replace(newCfg)
|
||||
p.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -1943,7 +2191,9 @@ func TestIssue4357(t *testing.T) {
|
||||
t.Error("should still have folder in config")
|
||||
}
|
||||
|
||||
if err := wrapper.ReplaceBlocking(config.Configuration{}); err != nil {
|
||||
p, err = wrapper.Replace(config.Configuration{})
|
||||
p.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -1952,7 +2202,9 @@ func TestIssue4357(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add the folder back, should be running
|
||||
if err := wrapper.ReplaceBlocking(cfg); err != nil {
|
||||
p, err = wrapper.Replace(cfg)
|
||||
p.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -1964,7 +2216,9 @@ func TestIssue4357(t *testing.T) {
|
||||
}
|
||||
|
||||
// Should not panic when removing a running folder.
|
||||
if err := wrapper.ReplaceBlocking(config.Configuration{}); err != nil {
|
||||
p, err = wrapper.Replace(config.Configuration{})
|
||||
p.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -2066,7 +2320,7 @@ func TestIssue2782(t *testing.T) {
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "~/"+testName+"/synclink/"))
|
||||
m.AddFolder(config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "~/"+testName+"/synclink/"))
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
defer m.Stop()
|
||||
@@ -2111,7 +2365,7 @@ func TestIndexesForUnknownDevicesDropped(t *testing.T) {
|
||||
func TestSharedWithClearedOnDisconnect(t *testing.T) {
|
||||
dbi := db.OpenMemory()
|
||||
|
||||
fcfg := config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "testdata")
|
||||
fcfg := config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
|
||||
fcfg.Devices = []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2},
|
||||
@@ -2177,7 +2431,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
|
||||
cfg = cfg.Copy()
|
||||
cfg.Devices = cfg.Devices[:1]
|
||||
|
||||
if err := wcfg.Replace(cfg); err != nil {
|
||||
if _, err := wcfg.Replace(cfg); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -2350,7 +2604,7 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
|
||||
|
||||
dbi := db.OpenMemory()
|
||||
|
||||
fcfg := config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "testdata")
|
||||
fcfg := config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
|
||||
fcfg.Devices = []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2},
|
||||
|
||||
@@ -216,7 +216,7 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
|
||||
panic("Failed to create temporary testing dir")
|
||||
}
|
||||
cfg := defaultConfig.RawCopy()
|
||||
cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
|
||||
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpFolder)
|
||||
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2},
|
||||
@@ -292,7 +292,7 @@ func setupModelWithConnection() (*Model, *fakeConnection, string) {
|
||||
panic("Failed to create temporary testing dir")
|
||||
}
|
||||
cfg := defaultConfig.RawCopy()
|
||||
cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
|
||||
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpFolder)
|
||||
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2},
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
type DiscoverFunc func(renewal, timeout time.Duration) []Device
|
||||
@@ -21,7 +20,7 @@ func Register(provider DiscoverFunc) {
|
||||
}
|
||||
|
||||
func discoverAll(renewal, timeout time.Duration) map[string]Device {
|
||||
wg := sync.NewWaitGroup()
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(providers))
|
||||
|
||||
c := make(chan Device)
|
||||
|
||||
@@ -44,11 +44,11 @@ import (
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/dialer"
|
||||
"github.com/syncthing/syncthing/lib/nat"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -85,7 +85,7 @@ func Discover(renewal, timeout time.Duration) []nat.Device {
|
||||
|
||||
resultChan := make(chan IGD)
|
||||
|
||||
wg := sync.NewWaitGroup()
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for _, intf := range interfaces {
|
||||
// Interface flags seem to always be 0 on Windows
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -70,6 +71,31 @@ func SetDefaults(data interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyMatchingTag copies fields tagged tag:"value" from "from" struct onto "to" struct.
|
||||
func CopyMatchingTag(from interface{}, to interface{}, tag string, shouldCopy func(value string) bool) {
|
||||
fromStruct := reflect.ValueOf(from).Elem()
|
||||
fromType := fromStruct.Type()
|
||||
|
||||
toStruct := reflect.ValueOf(to).Elem()
|
||||
toType := toStruct.Type()
|
||||
|
||||
if fromType != toType {
|
||||
panic(fmt.Sprintf("non equal types: %s != %s", fromType, toType))
|
||||
}
|
||||
|
||||
for i := 0; i < toStruct.NumField(); i++ {
|
||||
fromField := fromStruct.Field(i)
|
||||
toField := toStruct.Field(i)
|
||||
|
||||
structTag := toType.Field(i).Tag
|
||||
|
||||
v := structTag.Get(tag)
|
||||
if shouldCopy(v) {
|
||||
toField.Set(fromField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UniqueStrings returns a list on unique strings, trimming and sorting them
|
||||
// at the same time.
|
||||
func UniqueStrings(ss []string) []string {
|
||||
|
||||
@@ -169,3 +169,61 @@ func TestAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyMatching(t *testing.T) {
|
||||
type Nested struct {
|
||||
A int
|
||||
}
|
||||
type Test struct {
|
||||
CopyA int
|
||||
CopyB []string
|
||||
CopyC Nested
|
||||
CopyD *Nested
|
||||
NoCopy int `restart:"true"`
|
||||
}
|
||||
|
||||
from := Test{
|
||||
CopyA: 1,
|
||||
CopyB: []string{"friend", "foe"},
|
||||
CopyC: Nested{
|
||||
A: 2,
|
||||
},
|
||||
CopyD: &Nested{
|
||||
A: 3,
|
||||
},
|
||||
NoCopy: 4,
|
||||
}
|
||||
|
||||
to := Test{
|
||||
CopyA: 11,
|
||||
CopyB: []string{"foot", "toe"},
|
||||
CopyC: Nested{
|
||||
A: 22,
|
||||
},
|
||||
CopyD: &Nested{
|
||||
A: 33,
|
||||
},
|
||||
NoCopy: 44,
|
||||
}
|
||||
|
||||
// Copy empty fields
|
||||
CopyMatchingTag(&from, &to, "restart", func(v string) bool {
|
||||
return v != "true"
|
||||
})
|
||||
|
||||
if to.CopyA != 1 {
|
||||
t.Error("CopyA")
|
||||
}
|
||||
if len(to.CopyB) != 2 || to.CopyB[0] != "friend" || to.CopyB[1] != "foe" {
|
||||
t.Error("CopyB")
|
||||
}
|
||||
if to.CopyC.A != 2 {
|
||||
t.Error("CopyC")
|
||||
}
|
||||
if to.CopyD.A != 3 {
|
||||
t.Error("CopyC")
|
||||
}
|
||||
if to.NoCopy != 44 {
|
||||
t.Error("NoCopy")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user