all: Hide implementations behind interfaces for mocked testing (#5548)

* lib/model: Hide implementations behind interfaces for mocked testing

* review
This commit is contained in:
Simon Frei 2019-02-26 09:09:25 +01:00 committed by Audrius Butkevicius
parent 8a05492622
commit 722b3fce6a
30 changed files with 405 additions and 270 deletions

View File

@ -40,11 +40,9 @@ import (
"github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil" "github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade" "github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/versioner"
"github.com/vitrun/qart/qr" "github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -64,15 +62,15 @@ const (
type apiService struct { type apiService struct {
id protocol.DeviceID id protocol.DeviceID
cfg configIntf cfg config.Wrapper
httpsCertFile string httpsCertFile string
httpsKeyFile string httpsKeyFile string
statics *staticsServer statics *staticsServer
model modelIntf model model.Model
eventSubs map[events.EventType]events.BufferedSubscription eventSubs map[events.EventType]events.BufferedSubscription
eventSubsMut sync.Mutex eventSubsMut sync.Mutex
discoverer discover.CachingMux discoverer discover.CachingMux
connectionsService connectionsIntf connectionsService connections.Service
fss *folderSummaryService fss *folderSummaryService
systemConfigMut sync.Mutex // serializes posts to /rest/system/config systemConfigMut sync.Mutex // serializes posts to /rest/system/config
stop chan struct{} // signals intentional stop stop chan struct{} // signals intentional stop
@ -86,69 +84,11 @@ type apiService struct {
systemLog logger.Recorder systemLog logger.Recorder
} }
type modelIntf interface {
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
Completion(device protocol.DeviceID, folder string) model.FolderCompletion
Override(folder string)
Revert(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated
NeedSize(folder string) db.Counts
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
ResetFolder(folder string)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []model.Availability
GetIgnores(folder string) ([]string, []string, error)
GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
SetIgnores(folder string, content []string) error
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
ScanFolderSubdirs(folder string, subs []string) error
BringToFront(folder, file string)
Connection(deviceID protocol.DeviceID) (connections.Connection, bool)
GlobalSize(folder string) db.Counts
LocalSize(folder string) db.Counts
ReceiveOnlyChangedSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool)
State(folder string) (string, time.Time, error)
UsageReportingStats(version int, preview bool) map[string]interface{}
FolderErrors(folder string) ([]model.FileError, error)
WatchError(folder string) error
}
type configIntf interface {
GUI() config.GUIConfiguration
LDAP() config.LDAPConfiguration
RawCopy() config.Configuration
Options() config.OptionsConfiguration
Replace(cfg config.Configuration) (config.Waiter, error)
Subscribe(c config.Committer)
Folders() map[string]config.FolderConfiguration
Devices() map[protocol.DeviceID]config.DeviceConfiguration
SetDevice(config.DeviceConfiguration) (config.Waiter, error)
SetDevices([]config.DeviceConfiguration) (config.Waiter, error)
Save() error
ListenAddresses() []string
RequiresRestart() bool
}
type connectionsIntf interface {
Status() map[string]interface{}
NATType() string
}
type rater interface { type rater interface {
Rate() float64 Rate() float64
} }
func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder, cpu rater) *apiService { func newAPIService(id protocol.DeviceID, cfg config.Wrapper, httpsCertFile, httpsKeyFile, assetDir string, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, errors, systemLog logger.Recorder, cpu rater) *apiService {
service := &apiService{ service := &apiService{
id: id, id: id,
cfg: cfg, cfg: cfg,
@ -719,7 +659,7 @@ func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
} }
} }
func folderSummary(cfg configIntf, m modelIntf, folder string) (map[string]interface{}, error) { func folderSummary(cfg config.Wrapper, m model.Model, folder string) (map[string]interface{}, error) {
var res = make(map[string]interface{}) var res = make(map[string]interface{})
errors, err := m.FolderErrors(folder) errors, err := m.FolderErrors(folder)

View File

@ -956,7 +956,7 @@ func setupSignalHandling() {
}() }()
} }
func loadOrDefaultConfig() (*config.Wrapper, error) { func loadOrDefaultConfig() (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile) cfgFile := locations.Get(locations.ConfigFile)
cfg, err := config.Load(cfgFile, myID) cfg, err := config.Load(cfgFile, myID)
@ -967,7 +967,7 @@ func loadOrDefaultConfig() (*config.Wrapper, error) {
return cfg, err return cfg, err
} }
func loadConfigAtStartup() (*config.Wrapper, error) { func loadConfigAtStartup() (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile) cfgFile := locations.Get(locations.ConfigFile)
cfg, err := config.Load(cfgFile, myID) cfg, err := config.Load(cfgFile, myID)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -996,7 +996,7 @@ func loadConfigAtStartup() (*config.Wrapper, error) {
return cfg, nil return cfg, nil
} }
func archiveAndSaveConfig(cfg *config.Wrapper) error { func archiveAndSaveConfig(cfg config.Wrapper) error {
// Copy the existing config to an archive copy // Copy the existing config to an archive copy
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion) archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion)
l.Infoln("Archiving a copy of old config file format at:", archivePath) l.Infoln("Archiving a copy of old config file format at:", archivePath)
@ -1061,7 +1061,7 @@ func startAuditing(mainService *suture.Supervisor, auditFile string) {
l.Infoln("Audit log in", auditDest) l.Infoln("Audit log in", auditDest)
} }
func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) { func setupGUI(mainService *suture.Supervisor, cfg config.Wrapper, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) {
guiCfg := cfg.GUI() guiCfg := cfg.GUI()
if !guiCfg.Enabled { if !guiCfg.Enabled {
@ -1091,7 +1091,7 @@ func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Mode
} }
} }
func defaultConfig(cfgFile string) (*config.Wrapper, error) { func defaultConfig(cfgFile string) (config.Wrapper, error) {
newCfg, err := config.NewWithFreePorts(myID) newCfg, err := config.NewWithFreePorts(myID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1154,7 +1154,7 @@ func standbyMonitor() {
} }
} }
func autoUpgrade(cfg *config.Wrapper) { func autoUpgrade(cfg config.Wrapper) {
timer := time.NewTimer(0) timer := time.NewTimer(0)
sub := events.Default.Subscribe(events.DeviceConnected) sub := events.Default.Subscribe(events.DeviceConnected)
for { for {
@ -1256,7 +1256,7 @@ func cleanConfigDirectory() {
// checkShortIDs verifies that the configuration won't result in duplicate // checkShortIDs verifies that the configuration won't result in duplicate
// short ID:s; that is, that the devices in the cluster all have unique // short ID:s; that is, that the devices in the cluster all have unique
// initial 64 bits. // initial 64 bits.
func checkShortIDs(cfg *config.Wrapper) error { func checkShortIDs(cfg config.Wrapper) error {
exists := make(map[protocol.ShortID]protocol.DeviceID) exists := make(map[protocol.ShortID]protocol.DeviceID)
for deviceID := range cfg.Devices() { for deviceID := range cfg.Devices() {
shortID := deviceID.Short() shortID := deviceID.Short()
@ -1278,7 +1278,7 @@ func showPaths(options RuntimeOptions) {
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder)) fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
} }
func setPauseState(cfg *config.Wrapper, paused bool) { func setPauseState(cfg config.Wrapper, paused bool) {
raw := cfg.RawCopy() raw := cfg.RawCopy()
for i := range raw.Devices { for i := range raw.Devices {
raw.Devices[i].Paused = paused raw.Devices[i].Paused = paused

View File

@ -44,6 +44,8 @@ func (c *mockedConfig) Replace(cfg config.Configuration) (config.Waiter, error)
func (c *mockedConfig) Subscribe(cm config.Committer) {} func (c *mockedConfig) Subscribe(cm config.Committer) {}
func (c *mockedConfig) Unsubscribe(cm config.Committer) {}
func (c *mockedConfig) Folders() map[string]config.FolderConfiguration { func (c *mockedConfig) Folders() map[string]config.FolderConfiguration {
return nil return nil
} }
@ -68,6 +70,58 @@ func (c *mockedConfig) RequiresRestart() bool {
return false return false
} }
func (c *mockedConfig) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {}
func (c *mockedConfig) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {}
func (m *mockedConfig) MyName() string {
return ""
}
func (m *mockedConfig) ConfigPath() string {
return ""
}
func (m *mockedConfig) SetGUI(gui config.GUIConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) SetOptions(opts config.OptionsConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
return config.FolderConfiguration{}, false
}
func (m *mockedConfig) FolderList() []config.FolderConfiguration {
return nil
}
func (m *mockedConfig) SetFolder(fld config.FolderConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
return config.DeviceConfiguration{}, false
}
func (m *mockedConfig) RemoveDevice(id protocol.DeviceID) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
return false
}
func (m *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
return false
}
func (m *mockedConfig) GlobalDiscoveryServers() []string {
return nil
}
type noopWaiter struct{} type noopWaiter struct{}
func (noopWaiter) Wait() {} func (noopWaiter) Wait() {}

View File

@ -15,3 +15,7 @@ func (m *mockedConnections) Status() map[string]interface{} {
func (m *mockedConnections) NATType() string { func (m *mockedConnections) NATType() string {
return "" return ""
} }
func (m *mockedConnections) Serve() {}
func (m *mockedConnections) Stop() {}

View File

@ -7,8 +7,10 @@
package main package main
import ( import (
"net"
"time" "time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections" "github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model"
@ -150,3 +152,38 @@ func (m *mockedModel) WatchError(folder string) error {
func (m *mockedModel) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated { func (m *mockedModel) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated {
return nil return nil
} }
func (m *mockedModel) Serve() {}
func (m *mockedModel) Stop() {}
func (m *mockedModel) Index(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {}
func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {
}
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
return nil, nil
}
func (m *mockedModel) ClusterConfig(deviceID protocol.DeviceID, config protocol.ClusterConfig) {}
func (m *mockedModel) Closed(conn protocol.Connection, err error) {}
func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
}
func (m *mockedModel) AddConnection(conn connections.Connection, hello protocol.HelloResult) {}
func (m *mockedModel) OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error {
return nil
}
func (m *mockedModel) GetHello(protocol.DeviceID) protocol.HelloIntf {
return nil
}
func (m *mockedModel) AddFolder(cfg config.FolderConfiguration) {}
func (m *mockedModel) RestartFolder(from, to config.FolderConfiguration) {}
func (m *mockedModel) StartFolder(folder string) {}
func (m *mockedModel) StartDeadlockDetector(timeout time.Duration) {}

View File

@ -9,7 +9,9 @@ package main
import ( import (
"time" "time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture" "github.com/thejerf/suture"
@ -20,8 +22,8 @@ import (
type folderSummaryService struct { type folderSummaryService struct {
*suture.Supervisor *suture.Supervisor
cfg configIntf cfg config.Wrapper
model modelIntf model model.Model
stop chan struct{} stop chan struct{}
immediate chan string immediate chan string
@ -34,7 +36,7 @@ type folderSummaryService struct {
lastEventReqMut sync.Mutex lastEventReqMut sync.Mutex
} }
func newFolderSummaryService(cfg configIntf, m modelIntf) *folderSummaryService { func newFolderSummaryService(cfg config.Wrapper, m model.Model) *folderSummaryService {
service := &folderSummaryService{ service := &folderSummaryService{
Supervisor: suture.New("folderSummaryService", suture.Spec{ Supervisor: suture.New("folderSummaryService", suture.Spec{
PassThroughPanics: true, PassThroughPanics: true,

View File

@ -37,7 +37,7 @@ const usageReportVersion = 3
// reportData returns the data to be sent in a usage report. It's used in // reportData returns the data to be sent in a usage report. It's used in
// various places, so not part of the usageReportingManager object. // various places, so not part of the usageReportingManager object.
func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf, version int, preview bool) map[string]interface{} { func reportData(cfg config.Wrapper, m model.Model, connectionsService connections.Service, version int, preview bool) map[string]interface{} {
opts := cfg.Options() opts := cfg.Options()
res := make(map[string]interface{}) res := make(map[string]interface{})
res["urVersion"] = version res["urVersion"] = version
@ -323,16 +323,16 @@ func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf,
} }
type usageReportingService struct { type usageReportingService struct {
cfg *config.Wrapper cfg config.Wrapper
model *model.Model model model.Model
connectionsService *connections.Service connectionsService connections.Service
forceRun chan struct{} forceRun chan struct{}
stop chan struct{} stop chan struct{}
stopped chan struct{} stopped chan struct{}
stopMut sync.RWMutex stopMut sync.RWMutex
} }
func newUsageReportingService(cfg *config.Wrapper, model *model.Model, connectionsService *connections.Service) *usageReportingService { func newUsageReportingService(cfg config.Wrapper, model model.Model, connectionsService connections.Service) *usageReportingService {
svc := &usageReportingService{ svc := &usageReportingService{
cfg: cfg, cfg: cfg,
model: model, model: model,

View File

@ -93,7 +93,7 @@ func TestDeviceConfig(t *testing.T) {
t.Fatal("Unexpected file") t.Fatal("Unexpected file")
} }
cfg := wr.cfg cfg := wr.(*wrapper).cfg
expectedFolders := []FolderConfiguration{ expectedFolders := []FolderConfiguration{
{ {
@ -515,7 +515,7 @@ func TestNewSaveLoad(t *testing.T) {
cfg := Wrap(path, intCfg) cfg := Wrap(path, intCfg)
// To make the equality pass later // To make the equality pass later
cfg.cfg.XMLName.Local = "configuration" cfg.(*wrapper).cfg.XMLName.Local = "configuration"
if exists(path) { if exists(path) {
t.Error(path, "exists") t.Error(path, "exists")
@ -827,7 +827,7 @@ func TestIgnoredFolders(t *testing.T) {
// 2 for folder2, 1 for folder1, as non-existing device and device the folder is shared with is removed. // 2 for folder2, 1 for folder1, as non-existing device and device the folder is shared with is removed.
expectedIgnoredFolders := 3 expectedIgnoredFolders := 3
for _, dev := range wrapper.cfg.Devices { for _, dev := range wrapper.Devices() {
expectedIgnoredFolders -= len(dev.IgnoredFolders) expectedIgnoredFolders -= len(dev.IgnoredFolders)
} }
if expectedIgnoredFolders != 0 { if expectedIgnoredFolders != 0 {

View File

@ -52,10 +52,48 @@ type noopWaiter struct{}
func (noopWaiter) Wait() {} func (noopWaiter) Wait() {}
// A wrapper around a Configuration that manages loads, saves and published // A Wrapper around a Configuration that manages loads, saves and published
// notifications of changes to registered Handlers // notifications of changes to registered Handlers
type Wrapper interface {
MyName() string
ConfigPath() string
type Wrapper struct { RawCopy() Configuration
Replace(cfg Configuration) (Waiter, error)
RequiresRestart() bool
Save() error
GUI() GUIConfiguration
SetGUI(gui GUIConfiguration) (Waiter, error)
LDAP() LDAPConfiguration
Options() OptionsConfiguration
SetOptions(opts OptionsConfiguration) (Waiter, error)
Folder(id string) (FolderConfiguration, bool)
Folders() map[string]FolderConfiguration
FolderList() []FolderConfiguration
SetFolder(fld FolderConfiguration) (Waiter, error)
Device(id protocol.DeviceID) (DeviceConfiguration, bool)
Devices() map[protocol.DeviceID]DeviceConfiguration
RemoveDevice(id protocol.DeviceID) (Waiter, error)
SetDevice(DeviceConfiguration) (Waiter, error)
SetDevices([]DeviceConfiguration) (Waiter, error)
AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string)
AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID)
IgnoredDevice(id protocol.DeviceID) bool
IgnoredFolder(device protocol.DeviceID, folder string) bool
ListenAddresses() []string
GlobalDiscoveryServers() []string
Subscribe(c Committer)
Unsubscribe(c Committer)
}
type wrapper struct {
cfg Configuration cfg Configuration
path string path string
@ -69,8 +107,8 @@ 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) *Wrapper { func Wrap(path string, cfg Configuration) Wrapper {
w := &Wrapper{ w := &wrapper{
cfg: cfg, cfg: cfg,
path: path, path: path,
mut: sync.NewMutex(), mut: sync.NewMutex(),
@ -80,7 +118,7 @@ func Wrap(path string, cfg Configuration) *Wrapper {
// Load loads an existing file on disk and returns a new configuration // Load loads an existing file on disk and returns a new configuration
// wrapper. // wrapper.
func Load(path string, myID protocol.DeviceID) (*Wrapper, error) { func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
fd, err := os.Open(path) fd, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -95,13 +133,13 @@ func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
return Wrap(path, cfg), nil return Wrap(path, cfg), nil
} }
func (w *Wrapper) ConfigPath() string { func (w *wrapper) ConfigPath() string {
return w.path return w.path
} }
// 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) {
w.mut.Lock() w.mut.Lock()
w.subs = append(w.subs, c) w.subs = append(w.subs, c)
w.mut.Unlock() w.mut.Unlock()
@ -109,7 +147,7 @@ func (w *Wrapper) Subscribe(c Committer) {
// Unsubscribe de-registers the given handler from any future calls to // Unsubscribe de-registers the given handler from any future calls to
// configuration changes // configuration changes
func (w *Wrapper) Unsubscribe(c Committer) { func (w *wrapper) Unsubscribe(c Committer) {
w.mut.Lock() w.mut.Lock()
for i := range w.subs { for i := range w.subs {
if w.subs[i] == c { if w.subs[i] == c {
@ -123,20 +161,20 @@ func (w *Wrapper) Unsubscribe(c Committer) {
} }
// RawCopy returns a copy of the currently wrapped Configuration object. // RawCopy returns a copy of the currently wrapped Configuration object.
func (w *Wrapper) RawCopy() Configuration { func (w *wrapper) RawCopy() Configuration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
return w.cfg.Copy() return w.cfg.Copy()
} }
// Replace swaps the current configuration object for the given one. // Replace swaps the current configuration object for the given one.
func (w *Wrapper) Replace(cfg Configuration) (Waiter, error) { func (w *wrapper) Replace(cfg Configuration) (Waiter, error) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
return w.replaceLocked(cfg.Copy()) return w.replaceLocked(cfg.Copy())
} }
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.clean(); err != nil {
@ -158,7 +196,7 @@ func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
return w.notifyListeners(from.Copy(), to.Copy()), nil return w.notifyListeners(from.Copy(), to.Copy()), nil
} }
func (w *Wrapper) notifyListeners(from, to Configuration) Waiter { func (w *wrapper) notifyListeners(from, to Configuration) Waiter {
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
wg.Add(len(w.subs)) wg.Add(len(w.subs))
for _, sub := range w.subs { for _, sub := range w.subs {
@ -170,7 +208,7 @@ func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
return wg return wg
} }
func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) { func (w *wrapper) notifyListener(sub Committer, from, to Configuration) {
l.Debugln(sub, "committing configuration") l.Debugln(sub, "committing configuration")
if !sub.CommitConfiguration(from, to) { if !sub.CommitConfiguration(from, to) {
l.Debugln(sub, "requires restart") l.Debugln(sub, "requires restart")
@ -179,7 +217,7 @@ func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
} }
// Devices returns a map of devices. // Devices returns a map of devices.
func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration { func (w *wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
if w.deviceMap == nil { if w.deviceMap == nil {
@ -193,7 +231,7 @@ func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
// SetDevices adds new devices to the configuration, or overwrites existing // SetDevices adds new devices to the configuration, or overwrites existing
// devices with the same ID. // devices with the same ID.
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) { func (w *wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
@ -218,12 +256,12 @@ func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
// SetDevice adds a new device to the configuration, or overwrites an existing // SetDevice adds a new device to the configuration, or overwrites an existing
// device with the same ID. // device with the same ID.
func (w *Wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) { func (w *wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
return w.SetDevices([]DeviceConfiguration{dev}) return w.SetDevices([]DeviceConfiguration{dev})
} }
// RemoveDevice removes the device from the configuration // RemoveDevice removes the device from the configuration
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) { func (w *wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
@ -240,7 +278,7 @@ func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
// Folders returns a map of folders. Folder structures should not be changed, // Folders returns a map of folders. Folder structures should not be changed,
// other than for the purpose of updating via SetFolder(). // other than for the purpose of updating via SetFolder().
func (w *Wrapper) Folders() map[string]FolderConfiguration { func (w *wrapper) Folders() map[string]FolderConfiguration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
if w.folderMap == nil { if w.folderMap == nil {
@ -253,7 +291,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
} }
// FolderList returns a slice of folders. // FolderList returns a slice of folders.
func (w *Wrapper) FolderList() []FolderConfiguration { func (w *wrapper) FolderList() []FolderConfiguration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
return w.cfg.Copy().Folders return w.cfg.Copy().Folders
@ -261,7 +299,7 @@ func (w *Wrapper) FolderList() []FolderConfiguration {
// SetFolder adds a new folder to the configuration, or overwrites an existing // SetFolder adds a new folder to the configuration, or overwrites an existing
// folder with the same ID. // folder with the same ID.
func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) { func (w *wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
@ -280,14 +318,14 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
} }
// Options returns the current options configuration object. // Options returns the current options configuration object.
func (w *Wrapper) Options() OptionsConfiguration { func (w *wrapper) Options() OptionsConfiguration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
return w.cfg.Options.Copy() return w.cfg.Options.Copy()
} }
// SetOptions replaces the current options configuration object. // SetOptions replaces the current options configuration object.
func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) { func (w *wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
newCfg := w.cfg.Copy() newCfg := w.cfg.Copy()
@ -295,21 +333,21 @@ func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
return w.replaceLocked(newCfg) return w.replaceLocked(newCfg)
} }
func (w *Wrapper) LDAP() LDAPConfiguration { func (w *wrapper) LDAP() LDAPConfiguration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
return w.cfg.LDAP.Copy() return w.cfg.LDAP.Copy()
} }
// GUI returns the current GUI configuration object. // GUI returns the current GUI configuration object.
func (w *Wrapper) GUI() GUIConfiguration { func (w *wrapper) GUI() GUIConfiguration {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
return w.cfg.GUI.Copy() return w.cfg.GUI.Copy()
} }
// SetGUI replaces the current GUI configuration object. // SetGUI replaces the current GUI configuration object.
func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) { func (w *wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
newCfg := w.cfg.Copy() newCfg := w.cfg.Copy()
@ -319,7 +357,7 @@ func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
// IgnoredDevice returns whether or not connection attempts from the given // IgnoredDevice returns whether or not connection attempts from the given
// device should be silently ignored. // device should be silently ignored.
func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool { func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
for _, device := range w.cfg.IgnoredDevices { for _, device := range w.cfg.IgnoredDevices {
@ -332,7 +370,7 @@ func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
// IgnoredFolder returns whether or not share attempts for the given // IgnoredFolder returns whether or not share attempts for the given
// folder should be silently ignored. // folder should be silently ignored.
func (w *Wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool { func (w *wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
dev, ok := w.Device(device) dev, ok := w.Device(device)
if !ok { if !ok {
return false return false
@ -341,7 +379,7 @@ func (w *Wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
} }
// Device returns the configuration for the given device and an "ok" bool. // Device returns the configuration for the given device and an "ok" bool.
func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) { func (w *wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
for _, device := range w.cfg.Devices { for _, device := range w.cfg.Devices {
@ -353,7 +391,7 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
} }
// Folder returns the configuration for the given folder and an "ok" bool. // Folder returns the configuration for the given folder and an "ok" bool.
func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) { func (w *wrapper) Folder(id string) (FolderConfiguration, bool) {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
for _, folder := range w.cfg.Folders { for _, folder := range w.cfg.Folders {
@ -365,7 +403,7 @@ func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
} }
// Save writes the configuration to disk, and generates a ConfigSaved event. // Save writes the configuration to disk, and generates a ConfigSaved event.
func (w *Wrapper) Save() error { func (w *wrapper) Save() error {
w.mut.Lock() w.mut.Lock()
defer w.mut.Unlock() defer w.mut.Unlock()
@ -390,7 +428,7 @@ func (w *Wrapper) Save() error {
return nil return nil
} }
func (w *Wrapper) GlobalDiscoveryServers() []string { func (w *wrapper) GlobalDiscoveryServers() []string {
var servers []string var servers []string
for _, srv := range w.Options().GlobalAnnServers { for _, srv := range w.Options().GlobalAnnServers {
switch srv { switch srv {
@ -407,7 +445,7 @@ func (w *Wrapper) GlobalDiscoveryServers() []string {
return util.UniqueStrings(servers) return util.UniqueStrings(servers)
} }
func (w *Wrapper) ListenAddresses() []string { func (w *wrapper) ListenAddresses() []string {
var addresses []string var addresses []string
for _, addr := range w.Options().ListenAddresses { for _, addr := range w.Options().ListenAddresses {
switch addr { switch addr {
@ -420,15 +458,15 @@ func (w *Wrapper) ListenAddresses() []string {
return util.UniqueStrings(addresses) return util.UniqueStrings(addresses)
} }
func (w *Wrapper) RequiresRestart() bool { func (w *wrapper) RequiresRestart() bool {
return atomic.LoadUint32(&w.requiresRestart) != 0 return atomic.LoadUint32(&w.requiresRestart) != 0
} }
func (w *Wrapper) setRequiresRestart() { func (w *wrapper) setRequiresRestart() {
atomic.StoreUint32(&w.requiresRestart, 1) atomic.StoreUint32(&w.requiresRestart, 1)
} }
func (w *Wrapper) MyName() string { func (w *wrapper) MyName() string {
w.mut.Lock() w.mut.Lock()
myID := w.cfg.MyID myID := w.cfg.MyID
w.mut.Unlock() w.mut.Unlock()
@ -436,7 +474,7 @@ func (w *Wrapper) MyName() string {
return cfg.Name return cfg.Name
} }
func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) { func (w *wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
defer w.Save() defer w.Save()
w.mut.Lock() w.mut.Lock()
@ -459,7 +497,7 @@ func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, addre
}) })
} }
func (w *Wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) { func (w *wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
defer w.Save() defer w.Save()
w.mut.Lock() w.mut.Lock()

View File

@ -36,7 +36,7 @@ func TestIsLANHost(t *testing.T) {
AlwaysLocalNets: []string{"10.20.30.0/24"}, AlwaysLocalNets: []string{"10.20.30.0/24"},
}, },
}) })
s := &Service{cfg: cfg} s := &service{cfg: cfg}
for _, tc := range cases { for _, tc := range cases {
res := s.isLANHost(tc.addr) res := s.isLANHost(tc.addr)

View File

@ -36,7 +36,7 @@ type waiter interface {
const limiterBurstSize = 4 * 128 << 10 const limiterBurstSize = 4 * 128 << 10
func newLimiter(cfg *config.Wrapper) *limiter { func newLimiter(cfg config.Wrapper) *limiter {
l := &limiter{ l := &limiter{
write: rate.NewLimiter(rate.Inf, limiterBurstSize), write: rate.NewLimiter(rate.Inf, limiterBurstSize),
read: rate.NewLimiter(rate.Inf, limiterBurstSize), read: rate.NewLimiter(rate.Inf, limiterBurstSize),

View File

@ -24,7 +24,7 @@ func init() {
device4, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2") device4, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
} }
func initConfig() *config.Wrapper { func initConfig() config.Wrapper {
cfg := config.Wrap("/dev/null", config.New(device1)) cfg := config.Wrap("/dev/null", config.New(device1))
dev1Conf = config.NewDeviceConfiguration(device1, "device1") dev1Conf = config.NewDeviceConfiguration(device1, "device1")
dev2Conf = config.NewDeviceConfiguration(device2, "device2") dev2Conf = config.NewDeviceConfiguration(device2, "device2")

View File

@ -22,7 +22,7 @@ func init() {
} }
type relayDialer struct { type relayDialer struct {
cfg *config.Wrapper cfg config.Wrapper
tlsCfg *tls.Config tlsCfg *tls.Config
} }
@ -70,7 +70,7 @@ func (d *relayDialer) RedialFrequency() time.Duration {
type relayDialerFactory struct{} type relayDialerFactory struct{}
func (relayDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer { func (relayDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &relayDialer{ return &relayDialer{
cfg: cfg, cfg: cfg,
tlsCfg: tlsCfg, tlsCfg: tlsCfg,

View File

@ -29,7 +29,7 @@ type relayListener struct {
onAddressesChangedNotifier onAddressesChangedNotifier
uri *url.URL uri *url.URL
cfg *config.Wrapper cfg config.Wrapper
tlsCfg *tls.Config tlsCfg *tls.Config
conns chan internalConn conns chan internalConn
factory listenerFactory factory listenerFactory
@ -180,7 +180,7 @@ func (t *relayListener) NATType() string {
type relayListenerFactory struct{} type relayListenerFactory struct{}
func (f *relayListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener { func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
return &relayListener{ return &relayListener{
uri: uri, uri: uri,
cfg: cfg, cfg: cfg,

View File

@ -77,9 +77,15 @@ var tlsCipherSuiteNames = map[uint16]string{
// Service listens and dials all configured unconnected devices, via supported // Service listens and dials all configured unconnected devices, via supported
// dialers. Successful connections are handed to the model. // dialers. Successful connections are handed to the model.
type Service struct { type Service interface {
suture.Service
Status() map[string]interface{}
NATType() string
}
type service struct {
*suture.Supervisor *suture.Supervisor
cfg *config.Wrapper cfg config.Wrapper
myID protocol.DeviceID myID protocol.DeviceID
model Model model Model
tlsCfg *tls.Config tlsCfg *tls.Config
@ -97,10 +103,10 @@ type Service struct {
listenerSupervisor *suture.Supervisor listenerSupervisor *suture.Supervisor
} }
func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
bepProtocolName string, tlsDefaultCommonName string) *Service { bepProtocolName string, tlsDefaultCommonName string) *service {
service := &Service{ service := &service{
Supervisor: suture.New("connections.Service", suture.Spec{ Supervisor: suture.New("connections.Service", suture.Spec{
Log: func(line string) { Log: func(line string) {
l.Infoln(line) l.Infoln(line)
@ -156,7 +162,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
return service return service
} }
func (s *Service) handle() { func (s *service) handle() {
next: next:
for c := range s.conns { for c := range s.conns {
cs := c.ConnectionState() cs := c.ConnectionState()
@ -282,7 +288,7 @@ next:
} }
} }
func (s *Service) connect() { func (s *service) connect() {
nextDial := make(map[string]time.Time) nextDial := make(map[string]time.Time)
// Used as delay for the first few connection attempts, increases // Used as delay for the first few connection attempts, increases
@ -433,7 +439,7 @@ func (s *Service) connect() {
} }
} }
func (s *Service) isLANHost(host string) bool { func (s *service) isLANHost(host string) bool {
// Probably we are called with an ip:port combo which we can resolve as // Probably we are called with an ip:port combo which we can resolve as
// a TCP address. // a TCP address.
if addr, err := net.ResolveTCPAddr("tcp", host); err == nil { if addr, err := net.ResolveTCPAddr("tcp", host); err == nil {
@ -447,7 +453,7 @@ func (s *Service) isLANHost(host string) bool {
return false return false
} }
func (s *Service) isLAN(addr net.Addr) bool { func (s *service) isLAN(addr net.Addr) bool {
var ip net.IP var ip net.IP
switch addr := addr.(type) { switch addr := addr.(type) {
@ -488,7 +494,7 @@ func (s *Service) isLAN(addr net.Addr) bool {
return false return false
} }
func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool { func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
// must be called with listenerMut held // must be called with listenerMut held
l.Debugln("Starting listener", uri) l.Debugln("Starting listener", uri)
@ -500,7 +506,7 @@ func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
return true return true
} }
func (s *Service) logListenAddressesChangedEvent(l genericListener) { func (s *service) logListenAddressesChangedEvent(l genericListener) {
events.Default.Log(events.ListenAddressesChanged, map[string]interface{}{ events.Default.Log(events.ListenAddressesChanged, map[string]interface{}{
"address": l.URI(), "address": l.URI(),
"lan": l.LANAddresses(), "lan": l.LANAddresses(),
@ -508,11 +514,11 @@ func (s *Service) logListenAddressesChangedEvent(l genericListener) {
}) })
} }
func (s *Service) VerifyConfiguration(from, to config.Configuration) error { func (s *service) VerifyConfiguration(from, to config.Configuration) error {
return nil return nil
} }
func (s *Service) CommitConfiguration(from, to config.Configuration) bool { func (s *service) CommitConfiguration(from, to config.Configuration) bool {
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices)) newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
for _, dev := range to.Devices { for _, dev := range to.Devices {
newDevices[dev.DeviceID] = true newDevices[dev.DeviceID] = true
@ -589,7 +595,7 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
return true return true
} }
func (s *Service) AllAddresses() []string { func (s *service) AllAddresses() []string {
s.listenersMut.RLock() s.listenersMut.RLock()
var addrs []string var addrs []string
for _, listener := range s.listeners { for _, listener := range s.listeners {
@ -604,7 +610,7 @@ func (s *Service) AllAddresses() []string {
return util.UniqueStrings(addrs) return util.UniqueStrings(addrs)
} }
func (s *Service) ExternalAddresses() []string { func (s *service) ExternalAddresses() []string {
s.listenersMut.RLock() s.listenersMut.RLock()
var addrs []string var addrs []string
for _, listener := range s.listeners { for _, listener := range s.listeners {
@ -616,7 +622,7 @@ func (s *Service) ExternalAddresses() []string {
return util.UniqueStrings(addrs) return util.UniqueStrings(addrs)
} }
func (s *Service) Status() map[string]interface{} { func (s *service) Status() map[string]interface{} {
s.listenersMut.RLock() s.listenersMut.RLock()
result := make(map[string]interface{}) result := make(map[string]interface{})
for addr, listener := range s.listeners { for addr, listener := range s.listeners {
@ -636,7 +642,7 @@ func (s *Service) Status() map[string]interface{} {
return result return result
} }
func (s *Service) NATType() string { func (s *service) NATType() string {
s.listenersMut.RLock() s.listenersMut.RLock()
defer s.listenersMut.RUnlock() defer s.listenersMut.RUnlock()
for _, listener := range s.listeners { for _, listener := range s.listeners {

View File

@ -122,7 +122,7 @@ func (c internalConn) String() string {
} }
type dialerFactory interface { type dialerFactory interface {
New(*config.Wrapper, *tls.Config) genericDialer New(config.Wrapper, *tls.Config) genericDialer
Priority() int Priority() int
AlwaysWAN() bool AlwaysWAN() bool
Valid(config.Configuration) error Valid(config.Configuration) error
@ -135,7 +135,7 @@ type genericDialer interface {
} }
type listenerFactory interface { type listenerFactory interface {
New(*url.URL, *config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener New(*url.URL, config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
Valid(config.Configuration) error Valid(config.Configuration) error
} }

View File

@ -24,7 +24,7 @@ func init() {
} }
type tcpDialer struct { type tcpDialer struct {
cfg *config.Wrapper cfg config.Wrapper
tlsCfg *tls.Config tlsCfg *tls.Config
} }
@ -62,7 +62,7 @@ func (d *tcpDialer) RedialFrequency() time.Duration {
type tcpDialerFactory struct{} type tcpDialerFactory struct{}
func (tcpDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer { func (tcpDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &tcpDialer{ return &tcpDialer{
cfg: cfg, cfg: cfg,
tlsCfg: tlsCfg, tlsCfg: tlsCfg,

View File

@ -29,7 +29,7 @@ type tcpListener struct {
onAddressesChangedNotifier onAddressesChangedNotifier
uri *url.URL uri *url.URL
cfg *config.Wrapper cfg config.Wrapper
tlsCfg *tls.Config tlsCfg *tls.Config
stop chan struct{} stop chan struct{}
conns chan internalConn conns chan internalConn
@ -195,7 +195,7 @@ func (t *tcpListener) NATType() string {
type tcpListenerFactory struct{} type tcpListenerFactory struct{}
func (f *tcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener { func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
return &tcpListener{ return &tcpListener{
uri: fixupPort(uri, config.DefaultTCPPort), uri: fixupPort(uri, config.DefaultTCPPort),
cfg: cfg, cfg: cfg,

View File

@ -39,7 +39,7 @@ type folder struct {
config.FolderConfiguration config.FolderConfiguration
localFlags uint32 localFlags uint32
model *Model model *model
shortID protocol.ShortID shortID protocol.ShortID
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@ -73,7 +73,7 @@ type puller interface {
pull() bool // true when successfull and should not be retried pull() bool // true when successfull and should not be retried
} }
func newFolder(model *Model, cfg config.FolderConfiguration) folder { func newFolder(model *model, cfg config.FolderConfiguration) folder {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return folder{ return folder{

View File

@ -56,7 +56,7 @@ type receiveOnlyFolder struct {
*sendReceiveFolder *sendReceiveFolder
} }
func newReceiveOnlyFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service { func newReceiveOnlyFolder(model *model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
sr := newSendReceiveFolder(model, cfg, ver, fs).(*sendReceiveFolder) sr := newSendReceiveFolder(model, cfg, ver, fs).(*sendReceiveFolder)
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
return &receiveOnlyFolder{sr} return &receiveOnlyFolder{sr}

View File

@ -336,7 +336,7 @@ func setupKnownFiles(t *testing.T, data []byte) []protocol.FileInfo {
return knownFiles return knownFiles
} }
func setupROFolder() *Model { func setupROFolder() *model {
fcfg := config.NewFolderConfiguration(myID, "ro", "receive only test", fs.FilesystemTypeBasic, "_recvonly") fcfg := config.NewFolderConfiguration(myID, "ro", "receive only test", fs.FilesystemTypeBasic, "_recvonly")
fcfg.Type = config.FolderTypeReceiveOnly fcfg.Type = config.FolderTypeReceiveOnly
fcfg.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}} fcfg.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
@ -349,7 +349,7 @@ func setupROFolder() *Model {
wrp := createTmpWrapper(cfg) wrp := createTmpWrapper(cfg)
db := db.OpenMemory() db := db.OpenMemory()
m := NewModel(wrp, myID, "syncthing", "dev", db, nil) m := newModel(wrp, myID, "syncthing", "dev", db, nil)
m.ServeBackground() m.ServeBackground()
m.AddFolder(fcfg) m.AddFolder(fcfg)

View File

@ -22,7 +22,7 @@ type sendOnlyFolder struct {
folder folder
} }
func newSendOnlyFolder(model *Model, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service { func newSendOnlyFolder(model *model, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service {
f := &sendOnlyFolder{ f := &sendOnlyFolder{
folder: newFolder(model, cfg), folder: newFolder(model, cfg),
} }

View File

@ -104,7 +104,7 @@ type sendReceiveFolder struct {
pullErrorsMut sync.Mutex pullErrorsMut sync.Mutex
} }
func newSendReceiveFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service { func newSendReceiveFolder(model *model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
f := &sendReceiveFolder{ f := &sendReceiveFolder{
folder: newFolder(model, cfg), folder: newFolder(model, cfg),
fs: fs, fs: fs,

View File

@ -75,9 +75,9 @@ func setUpFile(filename string, blockNumbers []int) protocol.FileInfo {
} }
} }
func setupSendReceiveFolder(files ...protocol.FileInfo) (*Model, *sendReceiveFolder, string) { func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFolder, string) {
w := createTmpWrapper(defaultCfg) w := createTmpWrapper(defaultCfg)
model := NewModel(w, myID, "syncthing", "dev", db.OpenMemory(), nil) model := newModel(w, myID, "syncthing", "dev", db.OpenMemory(), nil)
fcfg, tmpDir := testFolderConfigTmp() fcfg, tmpDir := testFolderConfigTmp()
model.AddFolder(fcfg) model.AddFolder(fcfg)

View File

@ -76,10 +76,60 @@ type Availability struct {
FromTemporary bool `json:"fromTemporary"` FromTemporary bool `json:"fromTemporary"`
} }
type Model struct { type Model interface {
suture.Service
connections.Model
AddFolder(cfg config.FolderConfiguration)
RestartFolder(from, to config.FolderConfiguration)
StartFolder(folder string)
ResetFolder(folder string)
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
ScanFolderSubdirs(folder string, subs []string) error
State(folder string) (string, time.Time, error)
FolderErrors(folder string) ([]FileError, error)
WatchError(folder string) error
Override(folder string)
Revert(folder string)
BringToFront(folder, file string)
GetIgnores(folder string) ([]string, []string, error)
SetIgnores(folder string, content []string) error
GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability
GlobalSize(folder string) db.Counts
LocalSize(folder string) db.Counts
NeedSize(folder string) db.Counts
ReceiveOnlyChangedSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool)
Completion(device protocol.DeviceID, folder string) FolderCompletion
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
UsageReportingStats(version int, preview bool) map[string]interface{}
StartDeadlockDetector(timeout time.Duration)
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
}
type model struct {
*suture.Supervisor *suture.Supervisor
cfg *config.Wrapper cfg config.Wrapper
db *db.Lowlevel db *db.Lowlevel
finder *db.BlockFinder finder *db.BlockFinder
progressEmitter *ProgressEmitter progressEmitter *ProgressEmitter
@ -112,7 +162,7 @@ type Model struct {
foldersRunning int32 // for testing only foldersRunning int32 // for testing only
} }
type folderFactory func(*Model, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service type folderFactory func(*model, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service
var ( var (
folderFactories = make(map[config.FolderType]folderFactory) folderFactories = make(map[config.FolderType]folderFactory)
@ -134,8 +184,8 @@ var (
// NewModel creates and starts a new model. The model starts in read-only mode, // NewModel creates and starts a new model. The model starts in read-only mode,
// where it sends index information to connected peers and responds to requests // where it sends index information to connected peers and responds to requests
// for file data without altering the local folder in any way. // for file data without altering the local folder in any way.
func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) *Model { func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) Model {
m := &Model{ m := &model{
Supervisor: suture.New("model", suture.Spec{ Supervisor: suture.New("model", suture.Spec{
Log: func(line string) { Log: func(line string) {
l.Debugln(line) l.Debugln(line)
@ -180,7 +230,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersi
// StartDeadlockDetector starts a deadlock detector on the models locks which // StartDeadlockDetector starts a deadlock detector on the models locks which
// causes panics in case the locks cannot be acquired in the given timeout // causes panics in case the locks cannot be acquired in the given timeout
// period. // period.
func (m *Model) StartDeadlockDetector(timeout time.Duration) { func (m *model) StartDeadlockDetector(timeout time.Duration) {
l.Infof("Starting deadlock detector with %v timeout", timeout) l.Infof("Starting deadlock detector with %v timeout", timeout)
detector := newDeadlockDetector(timeout) detector := newDeadlockDetector(timeout)
detector.Watch("fmut", m.fmut) detector.Watch("fmut", m.fmut)
@ -188,7 +238,7 @@ func (m *Model) StartDeadlockDetector(timeout time.Duration) {
} }
// StartFolder constructs the folder service and starts it. // StartFolder constructs the folder service and starts it.
func (m *Model) StartFolder(folder string) { func (m *model) StartFolder(folder string) {
m.fmut.Lock() m.fmut.Lock()
m.pmut.Lock() m.pmut.Lock()
folderType := m.startFolderLocked(folder) folderType := m.startFolderLocked(folder)
@ -199,7 +249,7 @@ func (m *Model) StartFolder(folder string) {
l.Infof("Ready to synchronize %s (%s)", folderCfg.Description(), folderType) l.Infof("Ready to synchronize %s (%s)", folderCfg.Description(), folderType)
} }
func (m *Model) startFolderLocked(folder string) config.FolderType { func (m *model) startFolderLocked(folder string) config.FolderType {
if err := m.checkFolderRunningLocked(folder); err == errFolderMissing { if err := m.checkFolderRunningLocked(folder); err == errFolderMissing {
panic("cannot start nonexistent folder " + folder) panic("cannot start nonexistent folder " + folder)
} else if err == nil { } else if err == nil {
@ -274,7 +324,7 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
return cfg.Type return cfg.Type
} }
func (m *Model) warnAboutOverwritingProtectedFiles(folder string) { func (m *model) warnAboutOverwritingProtectedFiles(folder string) {
if m.folderCfgs[folder].Type == config.FolderTypeSendOnly { if m.folderCfgs[folder].Type == config.FolderTypeSendOnly {
return return
} }
@ -308,7 +358,7 @@ func (m *Model) warnAboutOverwritingProtectedFiles(folder string) {
} }
} }
func (m *Model) AddFolder(cfg config.FolderConfiguration) { func (m *model) AddFolder(cfg config.FolderConfiguration) {
if len(cfg.ID) == 0 { if len(cfg.ID) == 0 {
panic("cannot add empty folder id") panic("cannot add empty folder id")
} }
@ -322,7 +372,7 @@ func (m *Model) AddFolder(cfg config.FolderConfiguration) {
m.fmut.Unlock() m.fmut.Unlock()
} }
func (m *Model) addFolderLocked(cfg config.FolderConfiguration) { func (m *model) addFolderLocked(cfg config.FolderConfiguration) {
m.folderCfgs[cfg.ID] = cfg m.folderCfgs[cfg.ID] = cfg
folderFs := cfg.Filesystem() folderFs := cfg.Filesystem()
m.folderFiles[cfg.ID] = db.NewFileSet(cfg.ID, folderFs, m.db) m.folderFiles[cfg.ID] = db.NewFileSet(cfg.ID, folderFs, m.db)
@ -334,7 +384,7 @@ func (m *Model) addFolderLocked(cfg config.FolderConfiguration) {
m.folderIgnores[cfg.ID] = ignores m.folderIgnores[cfg.ID] = ignores
} }
func (m *Model) RemoveFolder(cfg config.FolderConfiguration) { func (m *model) RemoveFolder(cfg config.FolderConfiguration) {
m.fmut.Lock() m.fmut.Lock()
m.pmut.Lock() m.pmut.Lock()
// Delete syncthing specific files // Delete syncthing specific files
@ -348,7 +398,7 @@ func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
m.fmut.Unlock() m.fmut.Unlock()
} }
func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration, err error) { func (m *model) tearDownFolderLocked(cfg config.FolderConfiguration, err error) {
// Close connections to affected devices // Close connections to affected devices
// Must happen before stopping the folder service to abort ongoing // Must happen before stopping the folder service to abort ongoing
// transmissions and thus allow timely service termination. // transmissions and thus allow timely service termination.
@ -376,7 +426,7 @@ func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration, err error)
delete(m.folderStatRefs, cfg.ID) delete(m.folderStatRefs, cfg.ID)
} }
func (m *Model) RestartFolder(from, to config.FolderConfiguration) { func (m *model) RestartFolder(from, to config.FolderConfiguration) {
if len(to.ID) == 0 { if len(to.ID) == 0 {
panic("bug: cannot restart empty folder ID") panic("bug: cannot restart empty folder ID")
} }
@ -421,7 +471,7 @@ func (m *Model) RestartFolder(from, to config.FolderConfiguration) {
l.Infof("%v folder %v (%v)", infoMsg, to.Description(), to.Type) l.Infof("%v folder %v (%v)", infoMsg, to.Description(), to.Type)
} }
func (m *Model) UsageReportingStats(version int, preview bool) map[string]interface{} { func (m *model) UsageReportingStats(version int, preview bool) map[string]interface{} {
stats := make(map[string]interface{}) stats := make(map[string]interface{})
if version >= 3 { if version >= 3 {
// Block stats // Block stats
@ -540,7 +590,7 @@ func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
} }
// ConnectionStats returns a map with connection statistics for each device. // ConnectionStats returns a map with connection statistics for each device.
func (m *Model) ConnectionStats() map[string]interface{} { func (m *model) ConnectionStats() map[string]interface{} {
m.fmut.RLock() m.fmut.RLock()
m.pmut.RLock() m.pmut.RLock()
@ -587,7 +637,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
} }
// DeviceStatistics returns statistics about each device // DeviceStatistics returns statistics about each device
func (m *Model) DeviceStatistics() map[string]stats.DeviceStatistics { func (m *model) DeviceStatistics() map[string]stats.DeviceStatistics {
res := make(map[string]stats.DeviceStatistics) res := make(map[string]stats.DeviceStatistics)
for id := range m.cfg.Devices() { for id := range m.cfg.Devices() {
res[id.String()] = m.deviceStatRef(id).GetStatistics() res[id.String()] = m.deviceStatRef(id).GetStatistics()
@ -596,7 +646,7 @@ func (m *Model) DeviceStatistics() map[string]stats.DeviceStatistics {
} }
// FolderStatistics returns statistics about each folder // FolderStatistics returns statistics about each folder
func (m *Model) FolderStatistics() map[string]stats.FolderStatistics { func (m *model) FolderStatistics() map[string]stats.FolderStatistics {
res := make(map[string]stats.FolderStatistics) res := make(map[string]stats.FolderStatistics)
for id := range m.cfg.Folders() { for id := range m.cfg.Folders() {
res[id] = m.folderStatRef(id).GetStatistics() res[id] = m.folderStatRef(id).GetStatistics()
@ -614,7 +664,7 @@ type FolderCompletion struct {
// Completion returns the completion status, in percent, for the given device // Completion returns the completion status, in percent, for the given device
// and folder. // and folder.
func (m *Model) Completion(device protocol.DeviceID, folder string) FolderCompletion { func (m *model) Completion(device protocol.DeviceID, folder string) FolderCompletion {
m.fmut.RLock() m.fmut.RLock()
rf, ok := m.folderFiles[folder] rf, ok := m.folderFiles[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -696,7 +746,7 @@ func addSizeOfFile(s *db.Counts, f db.FileIntf) {
// GlobalSize returns the number of files, deleted files and total bytes for all // GlobalSize returns the number of files, deleted files and total bytes for all
// files in the global model. // files in the global model.
func (m *Model) GlobalSize(folder string) db.Counts { func (m *model) GlobalSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
@ -707,7 +757,7 @@ func (m *Model) GlobalSize(folder string) db.Counts {
// LocalSize returns the number of files, deleted files and total bytes for all // LocalSize returns the number of files, deleted files and total bytes for all
// files in the local folder. // files in the local folder.
func (m *Model) LocalSize(folder string) db.Counts { func (m *model) LocalSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
@ -719,7 +769,7 @@ func (m *Model) LocalSize(folder string) db.Counts {
// ReceiveOnlyChangedSize returns the number of files, deleted files and // ReceiveOnlyChangedSize returns the number of files, deleted files and
// total bytes for all files that have changed locally in a receieve only // total bytes for all files that have changed locally in a receieve only
// folder. // folder.
func (m *Model) ReceiveOnlyChangedSize(folder string) db.Counts { func (m *model) ReceiveOnlyChangedSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
@ -729,7 +779,7 @@ func (m *Model) ReceiveOnlyChangedSize(folder string) db.Counts {
} }
// NeedSize returns the number and total size of currently needed files. // NeedSize returns the number and total size of currently needed files.
func (m *Model) NeedSize(folder string) db.Counts { func (m *model) NeedSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
@ -753,7 +803,7 @@ func (m *Model) NeedSize(folder string) db.Counts {
// NeedFolderFiles returns paginated list of currently needed files in // NeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the // progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed. // total number of files currently needed.
func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) { func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
@ -820,7 +870,7 @@ func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfo
// LocalChangedFiles returns a paginated list of currently needed files in // LocalChangedFiles returns a paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the // progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed. // total number of files currently needed.
func (m *Model) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated { func (m *model) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
@ -861,7 +911,7 @@ func (m *Model) LocalChangedFiles(folder string, page, perpage int) []db.FileInf
// RemoteNeedFolderFiles returns paginated list of currently needed files in // RemoteNeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the // progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed. // total number of files currently needed.
func (m *Model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error) { func (m *model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
m.fmut.RLock() m.fmut.RLock()
m.pmut.RLock() m.pmut.RLock()
if err := m.checkDeviceFolderConnectedLocked(device, folder); err != nil { if err := m.checkDeviceFolderConnectedLocked(device, folder); err != nil {
@ -891,17 +941,17 @@ func (m *Model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, p
// Index is called when a new device is connected and we receive their full index. // Index is called when a new device is connected and we receive their full index.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *Model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) { func (m *model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) {
m.handleIndex(deviceID, folder, fs, false) m.handleIndex(deviceID, folder, fs, false)
} }
// IndexUpdate is called for incremental updates to connected devices' indexes. // IndexUpdate is called for incremental updates to connected devices' indexes.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) { func (m *model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) {
m.handleIndex(deviceID, folder, fs, true) m.handleIndex(deviceID, folder, fs, true)
} }
func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, update bool) { func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, update bool) {
op := "Index" op := "Index"
if update { if update {
op += " update" op += " update"
@ -956,7 +1006,7 @@ func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
}) })
} }
func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) { func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) {
// Check the peer device's announced folders against our own. Emits events // Check the peer device's announced folders against our own. Emits events
// for folders that we don't expect (unknown or not shared). // for folders that we don't expect (unknown or not shared).
// Also, collect a list of folders we do share, and if he's interested in // Also, collect a list of folders we do share, and if he's interested in
@ -1136,7 +1186,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
} }
// handleIntroductions handles adding devices/shares that are shared by an introducer device // handleIntroductions handles adding devices/shares that are shared by an introducer device
func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (folderDeviceSet, bool) { func (m *model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (folderDeviceSet, bool) {
// This device is an introducer. Go through the announced lists of folders // This device is an introducer. Go through the announced lists of folders
// and devices and add what we are missing, remove what we have extra that // and devices and add what we are missing, remove what we have extra that
// has been introducer by the introducer. // has been introducer by the introducer.
@ -1192,7 +1242,7 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
} }
// handleDeintroductions 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 { func (m *model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
changed := false changed := false
devicesNotIntroduced := make(map[protocol.DeviceID]struct{}) devicesNotIntroduced := make(map[protocol.DeviceID]struct{})
@ -1249,7 +1299,7 @@ func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
// handleAutoAccepts handles adding and sharing folders for devices that have // handleAutoAccepts handles adding and sharing folders for devices that have
// AutoAcceptFolders set to true. // AutoAcceptFolders set to true.
func (m *Model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) bool { func (m *model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) bool {
if cfg, ok := m.cfg.Folder(folder.ID); !ok { if cfg, ok := m.cfg.Folder(folder.ID); !ok {
defaultPath := m.cfg.Options().DefaultFolderPath defaultPath := m.cfg.Options().DefaultFolderPath
defaultPathFs := fs.NewFilesystem(fs.FilesystemTypeBasic, defaultPath) defaultPathFs := fs.NewFilesystem(fs.FilesystemTypeBasic, defaultPath)
@ -1295,7 +1345,7 @@ func (m *Model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder p
} }
} }
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) { func (m *model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
addresses := []string{"dynamic"} addresses := []string{"dynamic"}
for _, addr := range device.Addresses { for _, addr := range device.Addresses {
if addr != "dynamic" { if addr != "dynamic" {
@ -1324,7 +1374,7 @@ func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.Dev
} }
// Closed is called when a connection has been closed // Closed is called when a connection has been closed
func (m *Model) Closed(conn protocol.Connection, err error) { func (m *model) Closed(conn protocol.Connection, err error) {
device := conn.ID() device := conn.ID()
m.pmut.Lock() m.pmut.Lock()
@ -1350,14 +1400,14 @@ func (m *Model) Closed(conn protocol.Connection, err error) {
} }
// close will close the underlying connection for a given device // close will close the underlying connection for a given device
func (m *Model) close(device protocol.DeviceID, err error) { func (m *model) close(device protocol.DeviceID, err error) {
m.pmut.Lock() m.pmut.Lock()
m.closeLocked(device, err) m.closeLocked(device, err)
m.pmut.Unlock() m.pmut.Unlock()
} }
// closeLocked will close the underlying connection for a given device // closeLocked will close the underlying connection for a given device
func (m *Model) closeLocked(device protocol.DeviceID, err error) { func (m *model) closeLocked(device protocol.DeviceID, err error) {
conn, ok := m.conn[device] conn, ok := m.conn[device]
if !ok { if !ok {
// There is no connection to close // There is no connection to close
@ -1398,7 +1448,7 @@ func (r *requestResponse) Wait() {
// Request returns the specified data segment by reading it from local disk. // Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) { func (m *model) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) {
if size < 0 || offset < 0 { if size < 0 || offset < 0 {
return nil, protocol.ErrInvalid return nil, protocol.ErrInvalid
} }
@ -1519,7 +1569,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, size in
return res, nil return res, nil
} }
func (m *Model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem, folder, name string, blockIndex int, hash []byte) { func (m *model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem, folder, name string, blockIndex int, hash []byte) {
cf, ok := m.CurrentFolderFile(folder, name) cf, ok := m.CurrentFolderFile(folder, name)
if !ok { if !ok {
l.Debugf("%v recheckFile: %s: %q / %q: no current file", m, deviceID, folder, name) l.Debugf("%v recheckFile: %s: %q / %q: no current file", m, deviceID, folder, name)
@ -1560,7 +1610,7 @@ func (m *Model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem,
} }
} }
func (m *Model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) { func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
m.fmut.RLock() m.fmut.RLock()
fs, ok := m.folderFiles[folder] fs, ok := m.folderFiles[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -1570,7 +1620,7 @@ func (m *Model) CurrentFolderFile(folder string, file string) (protocol.FileInfo
return fs.Get(protocol.LocalDeviceID, file) return fs.Get(protocol.LocalDeviceID, file)
} }
func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) { func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
m.fmut.RLock() m.fmut.RLock()
fs, ok := m.folderFiles[folder] fs, ok := m.folderFiles[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -1581,7 +1631,7 @@ func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo
} }
type cFiler struct { type cFiler struct {
m *Model m Model
r string r string
} }
@ -1591,7 +1641,7 @@ func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
} }
// Connection returns the current connection for device, and a boolean whether a connection was found. // Connection returns the current connection for device, and a boolean whether a connection was found.
func (m *Model) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) { func (m *model) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) {
m.pmut.RLock() m.pmut.RLock()
cn, ok := m.conn[deviceID] cn, ok := m.conn[deviceID]
m.pmut.RUnlock() m.pmut.RUnlock()
@ -1601,7 +1651,7 @@ func (m *Model) Connection(deviceID protocol.DeviceID) (connections.Connection,
return cn, ok return cn, ok
} }
func (m *Model) GetIgnores(folder string) ([]string, []string, error) { func (m *model) GetIgnores(folder string) ([]string, []string, error) {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
@ -1630,7 +1680,7 @@ func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
return ignores.Lines(), ignores.Patterns(), nil return ignores.Lines(), ignores.Patterns(), nil
} }
func (m *Model) SetIgnores(folder string, content []string) error { func (m *model) SetIgnores(folder string, content []string) error {
cfg, ok := m.cfg.Folders()[folder] cfg, ok := m.cfg.Folders()[folder]
if !ok { if !ok {
return fmt.Errorf("folder %s does not exist", cfg.Description()) return fmt.Errorf("folder %s does not exist", cfg.Description())
@ -1664,7 +1714,7 @@ func (m *Model) SetIgnores(folder string, content []string) error {
// OnHello is called when an device connects to us. // OnHello is called when an device connects to us.
// This allows us to extract some information from the Hello message // This allows us to extract some information from the Hello message
// and add it to a list of known devices ahead of any checks. // and add it to a list of known devices ahead of any checks.
func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) error { func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) error {
if m.cfg.IgnoredDevice(remoteID) { if m.cfg.IgnoredDevice(remoteID) {
return errDeviceIgnored return errDeviceIgnored
} }
@ -1694,7 +1744,7 @@ func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco
} }
// GetHello is called when we are about to connect to some remote device. // GetHello is called when we are about to connect to some remote device.
func (m *Model) GetHello(id protocol.DeviceID) protocol.HelloIntf { func (m *model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
name := "" name := ""
if _, ok := m.cfg.Device(id); ok { if _, ok := m.cfg.Device(id); ok {
name = m.cfg.MyName() name = m.cfg.MyName()
@ -1709,7 +1759,7 @@ func (m *Model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
// AddConnection adds a new peer connection to the model. An initial index will // AddConnection adds a new peer connection to the model. An initial index will
// be sent to the connected peer, thereafter index updates whenever the local // be sent to the connected peer, thereafter index updates whenever the local
// folder changes. // folder changes.
func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloResult) { func (m *model) AddConnection(conn connections.Connection, hello protocol.HelloResult) {
deviceID := conn.ID() deviceID := conn.ID()
device, ok := m.cfg.Device(deviceID) device, ok := m.cfg.Device(deviceID)
if !ok { if !ok {
@ -1778,7 +1828,7 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
m.deviceWasSeen(deviceID) m.deviceWasSeen(deviceID)
} }
func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) { func (m *model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
m.fmut.RLock() m.fmut.RLock()
cfg, ok := m.folderCfgs[folder] cfg, ok := m.folderCfgs[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -1799,7 +1849,7 @@ func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, update
}) })
} }
func (m *Model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatisticsReference { func (m *model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatisticsReference {
m.fmut.Lock() m.fmut.Lock()
defer m.fmut.Unlock() defer m.fmut.Unlock()
@ -1812,11 +1862,11 @@ func (m *Model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatistic
return sr return sr
} }
func (m *Model) deviceWasSeen(deviceID protocol.DeviceID) { func (m *model) deviceWasSeen(deviceID protocol.DeviceID) {
m.deviceStatRef(deviceID).WasSeen() m.deviceStatRef(deviceID).WasSeen()
} }
func (m *Model) folderStatRef(folder string) *stats.FolderStatisticsReference { func (m *model) folderStatRef(folder string) *stats.FolderStatisticsReference {
m.fmut.Lock() m.fmut.Lock()
defer m.fmut.Unlock() defer m.fmut.Unlock()
@ -1828,7 +1878,7 @@ func (m *Model) folderStatRef(folder string) *stats.FolderStatisticsReference {
return sr return sr
} }
func (m *Model) receivedFile(folder string, file protocol.FileInfo) { func (m *model) receivedFile(folder string, file protocol.FileInfo) {
m.folderStatRef(folder).ReceivedFile(file.Name, file.IsDeleted()) m.folderStatRef(folder).ReceivedFile(file.Name, file.IsDeleted())
} }
@ -1949,7 +1999,7 @@ func sendIndexTo(prevSequence int64, conn protocol.Connection, folder string, fs
return f.Sequence, err return f.Sequence, err
} }
func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) { func (m *model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) {
m.updateLocals(folder, fs) m.updateLocals(folder, fs)
m.fmut.RLock() m.fmut.RLock()
@ -1959,7 +2009,7 @@ func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo)
m.diskChangeDetected(folderCfg, fs, events.LocalChangeDetected) m.diskChangeDetected(folderCfg, fs, events.LocalChangeDetected)
} }
func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) { func (m *model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) {
m.updateLocals(folder, fs) m.updateLocals(folder, fs)
m.fmut.RLock() m.fmut.RLock()
@ -1969,7 +2019,7 @@ func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) {
m.diskChangeDetected(folderCfg, fs, events.RemoteChangeDetected) m.diskChangeDetected(folderCfg, fs, events.RemoteChangeDetected)
} }
func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) { func (m *model) updateLocals(folder string, fs []protocol.FileInfo) {
m.fmut.RLock() m.fmut.RLock()
files := m.folderFiles[folder] files := m.folderFiles[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -1992,7 +2042,7 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
}) })
} }
func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo, typeOfEvent events.EventType) { func (m *model) diskChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo, typeOfEvent events.EventType) {
for _, file := range files { for _, file := range files {
if file.IsInvalid() { if file.IsInvalid() {
continue continue
@ -2035,7 +2085,7 @@ func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files [
} }
} }
func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) { func (m *model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
m.pmut.RLock() m.pmut.RLock()
nc, ok := m.conn[deviceID] nc, ok := m.conn[deviceID]
m.pmut.RUnlock() m.pmut.RUnlock()
@ -2049,7 +2099,7 @@ func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, o
return nc.Request(folder, name, offset, size, hash, weakHash, fromTemporary) return nc.Request(folder, name, offset, size, hash, weakHash, fromTemporary)
} }
func (m *Model) ScanFolders() map[string]error { func (m *model) ScanFolders() map[string]error {
m.fmut.RLock() m.fmut.RLock()
folders := make([]string, 0, len(m.folderCfgs)) folders := make([]string, 0, len(m.folderCfgs))
for folder := range m.folderCfgs { for folder := range m.folderCfgs {
@ -2087,11 +2137,11 @@ func (m *Model) ScanFolders() map[string]error {
return errors return errors
} }
func (m *Model) ScanFolder(folder string) error { func (m *model) ScanFolder(folder string) error {
return m.ScanFolderSubdirs(folder, nil) return m.ScanFolderSubdirs(folder, nil)
} }
func (m *Model) ScanFolderSubdirs(folder string, subs []string) error { func (m *model) ScanFolderSubdirs(folder string, subs []string) error {
m.fmut.RLock() m.fmut.RLock()
if err := m.checkFolderRunningLocked(folder); err != nil { if err := m.checkFolderRunningLocked(folder); err != nil {
m.fmut.RUnlock() m.fmut.RUnlock()
@ -2103,7 +2153,7 @@ func (m *Model) ScanFolderSubdirs(folder string, subs []string) error {
return runner.Scan(subs) return runner.Scan(subs)
} }
func (m *Model) DelayScan(folder string, next time.Duration) { func (m *model) DelayScan(folder string, next time.Duration) {
m.fmut.Lock() m.fmut.Lock()
runner, ok := m.folderRunners[folder] runner, ok := m.folderRunners[folder]
m.fmut.Unlock() m.fmut.Unlock()
@ -2115,7 +2165,7 @@ func (m *Model) DelayScan(folder string, next time.Duration) {
// numHashers returns the number of hasher routines to use for a given folder, // numHashers returns the number of hasher routines to use for a given folder,
// taking into account configuration and available CPU cores. // taking into account configuration and available CPU cores.
func (m *Model) numHashers(folder string) int { func (m *model) numHashers(folder string) int {
m.fmut.Lock() m.fmut.Lock()
folderCfg := m.folderCfgs[folder] folderCfg := m.folderCfgs[folder]
numFolders := len(m.folderCfgs) numFolders := len(m.folderCfgs)
@ -2144,7 +2194,7 @@ func (m *Model) numHashers(folder string) int {
// generateClusterConfig returns a ClusterConfigMessage that is correct for // generateClusterConfig returns a ClusterConfigMessage that is correct for
// the given peer device // the given peer device
func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfig { func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfig {
var message protocol.ClusterConfig var message protocol.ClusterConfig
m.fmut.RLock() m.fmut.RLock()
@ -2201,7 +2251,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
return message return message
} }
func (m *Model) State(folder string) (string, time.Time, error) { func (m *model) State(folder string) (string, time.Time, error) {
m.fmut.RLock() m.fmut.RLock()
runner, ok := m.folderRunners[folder] runner, ok := m.folderRunners[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -2215,7 +2265,7 @@ func (m *Model) State(folder string) (string, time.Time, error) {
return state.String(), changed, err return state.String(), changed, err
} }
func (m *Model) FolderErrors(folder string) ([]FileError, error) { func (m *model) FolderErrors(folder string) ([]FileError, error) {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if err := m.checkFolderRunningLocked(folder); err != nil { if err := m.checkFolderRunningLocked(folder); err != nil {
@ -2224,7 +2274,7 @@ func (m *Model) FolderErrors(folder string) ([]FileError, error) {
return m.folderRunners[folder].Errors(), nil return m.folderRunners[folder].Errors(), nil
} }
func (m *Model) WatchError(folder string) error { func (m *model) WatchError(folder string) error {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if err := m.checkFolderRunningLocked(folder); err != nil { if err := m.checkFolderRunningLocked(folder); err != nil {
@ -2233,7 +2283,7 @@ func (m *Model) WatchError(folder string) error {
return m.folderRunners[folder].WatchError() return m.folderRunners[folder].WatchError()
} }
func (m *Model) Override(folder string) { func (m *model) Override(folder string) {
// Grab the runner and the file set. // Grab the runner and the file set.
m.fmut.RLock() m.fmut.RLock()
@ -2251,7 +2301,7 @@ func (m *Model) Override(folder string) {
}) })
} }
func (m *Model) Revert(folder string) { func (m *model) Revert(folder string) {
// Grab the runner and the file set. // Grab the runner and the file set.
m.fmut.RLock() m.fmut.RLock()
@ -2272,7 +2322,7 @@ func (m *Model) Revert(folder string) {
// CurrentSequence returns the change version for the given folder. // CurrentSequence returns the change version for the given folder.
// This is guaranteed to increment if the contents of the local folder has // This is guaranteed to increment if the contents of the local folder has
// changed. // changed.
func (m *Model) CurrentSequence(folder string) (int64, bool) { func (m *model) CurrentSequence(folder string) (int64, bool) {
m.fmut.RLock() m.fmut.RLock()
fs, ok := m.folderFiles[folder] fs, ok := m.folderFiles[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -2288,7 +2338,7 @@ func (m *Model) CurrentSequence(folder string) (int64, bool) {
// RemoteSequence returns the change version for the given folder, as // RemoteSequence returns the change version for the given folder, as
// sent by remote peers. This is guaranteed to increment if the contents of // sent by remote peers. This is guaranteed to increment if the contents of
// the remote or global folder has changed. // the remote or global folder has changed.
func (m *Model) RemoteSequence(folder string) (int64, bool) { func (m *model) RemoteSequence(folder string) (int64, bool) {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
@ -2308,7 +2358,7 @@ func (m *Model) RemoteSequence(folder string) (int64, bool) {
return ver, true return ver, true
} }
func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} { func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} {
m.fmut.RLock() m.fmut.RLock()
files, ok := m.folderFiles[folder] files, ok := m.folderFiles[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
@ -2372,7 +2422,7 @@ func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly
return output return output
} }
func (m *Model) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) { func (m *model) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) {
fcfg, ok := m.cfg.Folder(folder) fcfg, ok := m.cfg.Folder(folder)
if !ok { if !ok {
return nil, errFolderMissing return nil, errFolderMissing
@ -2432,7 +2482,7 @@ func (m *Model) GetFolderVersions(folder string) (map[string][]versioner.FileVer
return files, nil return files, nil
} }
func (m *Model) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error) { func (m *model) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error) {
fcfg, ok := m.cfg.Folder(folder) fcfg, ok := m.cfg.Folder(folder)
if !ok { if !ok {
return nil, errFolderMissing return nil, errFolderMissing
@ -2503,7 +2553,7 @@ func (m *Model) RestoreFolderVersions(folder string, versions map[string]time.Ti
return errors, nil return errors, nil
} }
func (m *Model) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability { func (m *model) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
// The slightly unusual locking sequence here is because we need to hold // The slightly unusual locking sequence here is because we need to hold
// pmut for the duration (as the value returned from foldersFiles can // pmut for the duration (as the value returned from foldersFiles can
// get heavily modified on Close()), but also must acquire fmut before // get heavily modified on Close()), but also must acquire fmut before
@ -2544,7 +2594,7 @@ next:
} }
// BringToFront bumps the given files priority in the job queue. // BringToFront bumps the given files priority in the job queue.
func (m *Model) BringToFront(folder, file string) { func (m *model) BringToFront(folder, file string) {
m.pmut.RLock() m.pmut.RLock()
defer m.pmut.RUnlock() defer m.pmut.RUnlock()
@ -2554,20 +2604,20 @@ func (m *Model) BringToFront(folder, file string) {
} }
} }
func (m *Model) ResetFolder(folder string) { func (m *model) ResetFolder(folder string) {
l.Infof("Cleaning data for folder %q", folder) l.Infof("Cleaning data for folder %q", folder)
db.DropFolder(m.db, folder) db.DropFolder(m.db, folder)
} }
func (m *Model) String() string { func (m *model) String() string {
return fmt.Sprintf("model@%p", m) return fmt.Sprintf("model@%p", m)
} }
func (m *Model) VerifyConfiguration(from, to config.Configuration) error { func (m *model) VerifyConfiguration(from, to config.Configuration) error {
return nil return nil
} }
func (m *Model) CommitConfiguration(from, to config.Configuration) bool { func (m *model) CommitConfiguration(from, to config.Configuration) bool {
// TODO: This should not use reflect, and should take more care to try to handle stuff without restart. // TODO: This should not use reflect, and should take more care to try to handle stuff without restart.
// Go through the folder configs and figure out if we need to restart or not. // Go through the folder configs and figure out if we need to restart or not.
@ -2661,7 +2711,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
// checkFolderRunningLocked returns nil if the folder is up and running and a // checkFolderRunningLocked returns nil if the folder is up and running and a
// descriptive error if not. // descriptive error if not.
// Need to hold (read) lock on m.fmut when calling this. // Need to hold (read) lock on m.fmut when calling this.
func (m *Model) checkFolderRunningLocked(folder string) error { func (m *model) checkFolderRunningLocked(folder string) error {
_, ok := m.folderRunners[folder] _, ok := m.folderRunners[folder]
if ok { if ok {
return nil return nil
@ -2679,7 +2729,7 @@ func (m *Model) checkFolderRunningLocked(folder string) error {
// checkFolderDeviceStatusLocked first checks the folder and then whether the // checkFolderDeviceStatusLocked first checks the folder and then whether the
// given device is connected and shares this folder. // given device is connected and shares this folder.
// Need to hold (read) lock on both m.fmut and m.pmut when calling this. // Need to hold (read) lock on both m.fmut and m.pmut when calling this.
func (m *Model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folder string) error { func (m *model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folder string) error {
if err := m.checkFolderRunningLocked(folder); err != nil { if err := m.checkFolderRunningLocked(folder); err != nil {
return err return err
} }

View File

@ -38,7 +38,7 @@ import (
) )
var myID, device1, device2 protocol.DeviceID var myID, device1, device2 protocol.DeviceID
var defaultCfgWrapper *config.Wrapper var defaultCfgWrapper config.Wrapper
var defaultFolderConfig config.FolderConfiguration var defaultFolderConfig config.FolderConfiguration
var defaultFs fs.Filesystem var defaultFs fs.Filesystem
var defaultCfg config.Configuration var defaultCfg config.Configuration
@ -161,7 +161,7 @@ func prepareTmpFile(to fs.Filesystem) (string, error) {
return tmpName, nil return tmpName, nil
} }
func createTmpWrapper(cfg config.Configuration) *config.Wrapper { func createTmpWrapper(cfg config.Configuration) config.Wrapper {
tmpFile, err := ioutil.TempFile(tmpLocation, "syncthing-testConfig-") tmpFile, err := ioutil.TempFile(tmpLocation, "syncthing-testConfig-")
if err != nil { if err != nil {
panic(err) panic(err)
@ -171,7 +171,11 @@ func createTmpWrapper(cfg config.Configuration) *config.Wrapper {
return wrapper return wrapper
} }
func newState(cfg config.Configuration) (*config.Wrapper, *Model) { func newModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) *model {
return NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles).(*model)
}
func newState(cfg config.Configuration) (config.Wrapper, *model) {
wcfg := createTmpWrapper(cfg) wcfg := createTmpWrapper(cfg)
m := setupModel(wcfg) m := setupModel(wcfg)
@ -183,9 +187,9 @@ func newState(cfg config.Configuration) (*config.Wrapper, *Model) {
return wcfg, m return wcfg, m
} }
func setupModel(w *config.Wrapper) *Model { func setupModel(w config.Wrapper) *model {
db := db.OpenMemory() db := db.OpenMemory()
m := NewModel(w, myID, "syncthing", "dev", db, nil) m := newModel(w, myID, "syncthing", "dev", db, nil)
m.ServeBackground() m.ServeBackground()
for id, cfg := range w.Folders() { for id, cfg := range w.Folders() {
if !cfg.Paused { if !cfg.Paused {
@ -322,7 +326,7 @@ type fakeConnection struct {
files []protocol.FileInfo files []protocol.FileInfo
fileData map[string][]byte fileData map[string][]byte
folder string folder string
model *Model model *model
indexFn func(string, []protocol.FileInfo) indexFn func(string, []protocol.FileInfo)
requestFn func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) requestFn func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error)
mut sync.Mutex mut sync.Mutex
@ -563,7 +567,7 @@ func TestDeviceRename(t *testing.T) {
cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg) cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg)
db := db.OpenMemory() db := db.OpenMemory()
m := NewModel(cfg, myID, "syncthing", "dev", db, nil) m := newModel(cfg, myID, "syncthing", "dev", db, nil)
if cfg.Devices()[device1].Name != "" { if cfg.Devices()[device1].Name != "" {
t.Errorf("Device already has a name") t.Errorf("Device already has a name")
@ -662,7 +666,7 @@ func TestClusterConfig(t *testing.T) {
wrapper := createTmpWrapper(cfg) wrapper := createTmpWrapper(cfg)
defer os.Remove(wrapper.ConfigPath()) defer os.Remove(wrapper.ConfigPath())
m := NewModel(wrapper, myID, "syncthing", "dev", db, nil) m := newModel(wrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(cfg.Folders[0]) m.AddFolder(cfg.Folders[0])
m.AddFolder(cfg.Folders[1]) m.AddFolder(cfg.Folders[1])
m.ServeBackground() m.ServeBackground()
@ -1644,7 +1648,7 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
} }
} }
func changeIgnores(t *testing.T, m *Model, expected []string) { func changeIgnores(t *testing.T, m *model, expected []string) {
arrEqual := func(a, b []string) bool { arrEqual := func(a, b []string) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false
@ -1796,7 +1800,7 @@ func TestROScanRecovery(t *testing.T) {
testOs.RemoveAll(fcfg.Path) testOs.RemoveAll(fcfg.Path)
m := NewModel(cfg, myID, "syncthing", "dev", ldb, nil) m := newModel(cfg, myID, "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg) m.AddFolder(fcfg)
m.StartFolder("default") m.StartFolder("default")
m.ServeBackground() m.ServeBackground()
@ -1883,7 +1887,7 @@ func TestRWScanRecovery(t *testing.T) {
testOs.RemoveAll(fcfg.Path) testOs.RemoveAll(fcfg.Path)
m := NewModel(cfg, myID, "syncthing", "dev", ldb, nil) m := newModel(cfg, myID, "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg) m.AddFolder(fcfg)
m.StartFolder("default") m.StartFolder("default")
m.ServeBackground() m.ServeBackground()
@ -1941,7 +1945,7 @@ func TestRWScanRecovery(t *testing.T) {
func TestGlobalDirectoryTree(t *testing.T) { func TestGlobalDirectoryTree(t *testing.T) {
db := db.OpenMemory() db := db.OpenMemory()
m := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil) m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig) m.AddFolder(defaultFolderConfig)
m.ServeBackground() m.ServeBackground()
defer m.Stop() defer m.Stop()
@ -2193,7 +2197,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
func TestGlobalDirectorySelfFixing(t *testing.T) { func TestGlobalDirectorySelfFixing(t *testing.T) {
db := db.OpenMemory() db := db.OpenMemory()
m := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil) m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig) m.AddFolder(defaultFolderConfig)
m.ServeBackground() m.ServeBackground()
@ -2368,7 +2372,7 @@ func BenchmarkTree_100_10(b *testing.B) {
func benchmarkTree(b *testing.B, n1, n2 int) { func benchmarkTree(b *testing.B, n1, n2 int) {
db := db.OpenMemory() db := db.OpenMemory()
m := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil) m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig) m.AddFolder(defaultFolderConfig)
m.ServeBackground() m.ServeBackground()
@ -2438,7 +2442,7 @@ func TestIssue4357(t *testing.T) {
// Create a separate wrapper not to pollute other tests. // Create a separate wrapper not to pollute other tests.
wrapper := createTmpWrapper(config.Configuration{}) wrapper := createTmpWrapper(config.Configuration{})
defer os.Remove(wrapper.ConfigPath()) defer os.Remove(wrapper.ConfigPath())
m := NewModel(wrapper, myID, "syncthing", "dev", db, nil) m := newModel(wrapper, myID, "syncthing", "dev", db, nil)
m.ServeBackground() m.ServeBackground()
defer m.Stop() defer m.Stop()
@ -2568,7 +2572,7 @@ func TestIndexesForUnknownDevicesDropped(t *testing.T) {
t.Error("expected two devices") t.Error("expected two devices")
} }
m := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", dbi, nil) m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", dbi, nil)
m.AddFolder(defaultFolderConfig) m.AddFolder(defaultFolderConfig)
m.StartFolder("default") m.StartFolder("default")
@ -3055,7 +3059,7 @@ func TestCustomMarkerName(t *testing.T) {
testOs.RemoveAll(fcfg.Path) testOs.RemoveAll(fcfg.Path)
defer testOs.RemoveAll(fcfg.Path) defer testOs.RemoveAll(fcfg.Path)
m := NewModel(cfg, myID, "syncthing", "dev", ldb, nil) m := newModel(cfg, myID, "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg) m.AddFolder(fcfg)
m.StartFolder("default") m.StartFolder("default")
m.ServeBackground() m.ServeBackground()
@ -3466,7 +3470,7 @@ func TestIssue4094(t *testing.T) {
// Create a separate wrapper not to pollute other tests. // Create a separate wrapper not to pollute other tests.
wrapper := createTmpWrapper(config.Configuration{}) wrapper := createTmpWrapper(config.Configuration{})
defer os.Remove(wrapper.ConfigPath()) defer os.Remove(wrapper.ConfigPath())
m := NewModel(wrapper, myID, "syncthing", "dev", db, nil) m := newModel(wrapper, myID, "syncthing", "dev", db, nil)
m.ServeBackground() m.ServeBackground()
defer m.Stop() defer m.Stop()
@ -3505,7 +3509,7 @@ func TestIssue4903(t *testing.T) {
// Create a separate wrapper not to pollute other tests. // Create a separate wrapper not to pollute other tests.
wrapper := createTmpWrapper(config.Configuration{}) wrapper := createTmpWrapper(config.Configuration{})
defer os.Remove(wrapper.ConfigPath()) defer os.Remove(wrapper.ConfigPath())
m := NewModel(wrapper, myID, "syncthing", "dev", db, nil) m := newModel(wrapper, myID, "syncthing", "dev", db, nil)
m.ServeBackground() m.ServeBackground()
defer m.Stop() defer m.Stop()
@ -3575,7 +3579,7 @@ func TestParentOfUnignored(t *testing.T) {
} }
} }
func addFakeConn(m *Model, dev protocol.DeviceID) *fakeConnection { func addFakeConn(m *model, dev protocol.DeviceID) *fakeConnection {
fc := &fakeConnection{id: dev, model: m} fc := &fakeConnection{id: dev, model: m}
m.AddConnection(fc, protocol.HelloResult{}) m.AddConnection(fc, protocol.HelloResult{})

View File

@ -31,7 +31,7 @@ type ProgressEmitter struct {
// NewProgressEmitter creates a new progress emitter which emits // NewProgressEmitter creates a new progress emitter which emits
// DownloadProgress events every interval. // DownloadProgress events every interval.
func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter { func NewProgressEmitter(cfg config.Wrapper) *ProgressEmitter {
t := &ProgressEmitter{ t := &ProgressEmitter{
stop: make(chan struct{}), stop: make(chan struct{}),
registry: make(map[string]*sharedPullerState), registry: make(map[string]*sharedPullerState),

View File

@ -684,7 +684,7 @@ func TestRequestSymlinkWindows(t *testing.T) {
} }
} }
func tmpDefaultWrapper() (*config.Wrapper, string) { func tmpDefaultWrapper() (config.Wrapper, string) {
w := createTmpWrapper(defaultCfgWrapper.RawCopy()) w := createTmpWrapper(defaultCfgWrapper.RawCopy())
fcfg, tmpDir := testFolderConfigTmp() fcfg, tmpDir := testFolderConfigTmp()
w.SetFolder(fcfg) w.SetFolder(fcfg)
@ -703,13 +703,13 @@ func testFolderConfig(path string) config.FolderConfiguration {
return cfg return cfg
} }
func setupModelWithConnection() (*Model, *fakeConnection, string, *config.Wrapper) { func setupModelWithConnection() (*model, *fakeConnection, string, config.Wrapper) {
w, tmpDir := tmpDefaultWrapper() w, tmpDir := tmpDefaultWrapper()
m, fc := setupModelWithConnectionFromWrapper(w) m, fc := setupModelWithConnectionFromWrapper(w)
return m, fc, tmpDir, w return m, fc, tmpDir, w
} }
func setupModelWithConnectionFromWrapper(w *config.Wrapper) (*Model, *fakeConnection) { func setupModelWithConnectionFromWrapper(w config.Wrapper) (*model, *fakeConnection) {
m := setupModel(w) m := setupModel(w)
fc := addFakeConn(m, device1) fc := addFakeConn(m, device1)

View File

@ -23,7 +23,7 @@ import (
// setup/renewal of a port mapping. // setup/renewal of a port mapping.
type Service struct { type Service struct {
id protocol.DeviceID id protocol.DeviceID
cfg *config.Wrapper cfg config.Wrapper
stop chan struct{} stop chan struct{}
mappings []*Mapping mappings []*Mapping
@ -31,7 +31,7 @@ type Service struct {
mut sync.RWMutex mut sync.RWMutex
} }
func NewService(id protocol.DeviceID, cfg *config.Wrapper) *Service { func NewService(id protocol.DeviceID, cfg config.Wrapper) *Service {
return &Service{ return &Service{
id: id, id: id,
cfg: cfg, cfg: cfg,

View File

@ -125,14 +125,14 @@ func newAggregator(folderCfg config.FolderConfiguration, ctx context.Context) *a
return a return a
} }
func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg *config.Wrapper, ctx context.Context) { func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg config.Wrapper, ctx context.Context) {
a := newAggregator(folderCfg, ctx) a := newAggregator(folderCfg, ctx)
// Necessary for unit tests where the backend is mocked // Necessary for unit tests where the backend is mocked
go a.mainLoop(in, out, cfg) go a.mainLoop(in, out, cfg)
} }
func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg *config.Wrapper) { func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg config.Wrapper) {
a.notifyTimer = time.NewTimer(a.notifyDelay) a.notifyTimer = time.NewTimer(a.notifyDelay)
defer a.notifyTimer.Stop() defer a.notifyTimer.Stop()