diff options
author | Audrius Butkevicius <audrius.butkevicius@gmail.com> | 2016-05-04 10:47:33 +0000 |
---|---|---|
committer | Jakob Borg <jakob@nym.se> | 2016-05-04 10:47:33 +0000 |
commit | eabd2fc936be685a0b1207fde49dba77a012c2ff (patch) | |
tree | 9e62ba51155eb46d80c2a688ddbe7ef14d3a9132 | |
parent | 6720906ee52a86902751abd4e6294eb4d56fc938 (diff) |
lib/model: Use factories for creating folders
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3029
-rw-r--r-- | cmd/syncthing/main.go | 10 | ||||
-rw-r--r-- | cmd/syncthing/usage_report.go | 2 | ||||
-rwxr-xr-x | gui/default/index.html | 15 | ||||
-rwxr-xr-x | gui/default/syncthing/core/syncthingController.js | 1 | ||||
-rw-r--r-- | gui/default/syncthing/folder/editFolderModalView.html | 14 | ||||
-rw-r--r-- | lib/config/config.go | 12 | ||||
-rw-r--r-- | lib/config/config_test.go | 2 | ||||
-rw-r--r-- | lib/config/folderconfiguration.go | 7 | ||||
-rw-r--r-- | lib/config/testdata/v13.xml | 2 | ||||
-rw-r--r-- | lib/model/model.go | 69 | ||||
-rw-r--r-- | lib/model/model_test.go | 18 | ||||
-rw-r--r-- | lib/model/rofolder.go | 7 | ||||
-rw-r--r-- | lib/model/rwfolder.go | 7 |
13 files changed, 84 insertions, 82 deletions
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 04e2d28164..3ac19dd735 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -691,13 +691,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) { } m.Index(device, folderCfg.ID, nil, 0, nil) } - // Routine to pull blocks from other devices to synchronize the local - // folder. Does not run when we are in read only (publish only) mode. - if folderCfg.ReadOnly { - m.StartFolderRO(folderCfg.ID) - } else { - m.StartFolderRW(folderCfg.ID) - } + m.StartFolder(folderCfg.ID) } mainService.Add(m) @@ -994,7 +988,7 @@ func defaultConfig(myName string) config.Configuration { if !noDefaultFolder { l.Infoln("Default folder created and/or linked to new config") - defaultFolder = config.NewFolderConfiguration("default", locations[locDefFolder]) + defaultFolder = config.NewFolderConfiguration("default", locations[locDefFolder], config.FolderTypeReadWrite) defaultFolder.RescanIntervalS = 60 defaultFolder.MinDiskFreePct = 1 defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}} diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go index 434f4f6547..bb20c9d2b4 100644 --- a/cmd/syncthing/usage_report.go +++ b/cmd/syncthing/usage_report.go @@ -133,7 +133,7 @@ func reportData(cfg configIntf, m modelIntf) map[string]interface{} { for _, cfg := range cfg.Folders() { rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS) - if cfg.ReadOnly { + if cfg.Type == config.FolderTypeReadOnly { folderUses["readonly"]++ } if cfg.IgnorePerms { diff --git a/gui/default/index.html b/gui/default/index.html index 0f2a65f676..33b7682c05 100755 --- a/gui/default/index.html +++ b/gui/default/index.html @@ -231,7 +231,7 @@ <div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div> <div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id)}}%"></div> <h4 class="panel-title"> - <span class="fa hidden-xs fa-fw" ng-class="[folder.readOnly ? 'fa-lock' : 'fa-folder']"></span> + <span class="fa hidden-xs fa-fw" ng-class="[folder.type == 'readonly' ? 'fa-lock' : 'fa-folder']"></span> <a href="#folder-{{$index}}"> <span ng-show="folder.label.length == 0">{{folder.id}}</span> <span tooltip data-original-title="{{folder.id}}" ng-show="folder.label.length != 0">{{folder.label}}</span> @@ -294,7 +294,7 @@ <span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span> </td> </tr> - <tr ng-if="!folder.readOnly && (folderStatus(folder) === 'outofsync' || hasFailedFiles(folder.id))"> + <tr ng-if="folder.type != 'readonly' && (folderStatus(folder) === 'outofsync' || hasFailedFiles(folder.id))"> <th><span class="fa fa-fw fa-exclamation-circle"></span> <span translate>Failed Items</span></th> <!-- Show the number of failed items as a link to bring up the list. --> <td ng-if="hasFailedFiles(folder.id)" class="text-right"> @@ -305,10 +305,11 @@ <span class="fa fa-spinner fa-pulse"></span> </td> </tr> - <tr ng-if="folder.readOnly"> - <th><span class="fa fa-fw fa-lock"></span> <span translate>Folder Master</span></th> + <tr ng-if="folder.type != 'readwrite'"> + <th><span class="fa fa-fw fa-lock"></span> <span translate>Folder Type</span></th> <td class="text-right"> - <span translate>Yes</span> + <span ng-if="folder.type == 'readonly'" translate>Master</span> + <span ng-if="folder.type != 'readonly'">{{ folder.type.charAt(0).toUpperCase() + folder.type.slice(1) }}</span> </td> </tr> <tr ng-if="model[folder.id].ignorePatterns"> @@ -351,7 +352,7 @@ <th><span class="fa fa-fw fa-share-alt"></span> <span translate>Shared With</span></th> <td class="text-right">{{sharesFolder(folder)}}</td> </tr> - <tr ng-if="!folder.readOnly && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename"> + <tr ng-if="folder.type != 'readonly' && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename"> <th><span class="fa fa-fw fa-exchange"></span> <span translate>Last File Received</span></th> <td class="text-right"> <span tooltip data-original-title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}"> @@ -365,7 +366,7 @@ </table> </div> <div class="panel-footer"> - <button type="button" class="btn btn-sm btn-danger pull-left" ng-click="override(folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.readOnly"> + <button type="button" class="btn btn-sm btn-danger pull-left" ng-click="override(folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.type = 'readonly'"> <span class="fa fa-arrow-circle-up"></span> <span translate>Override Changes</span> </button> <span class="pull-right"> diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 69be028c6e..07176b8454 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -1201,6 +1201,7 @@ angular.module('syncthing.core') $scope.currentFolder = { selectedDevices: {}, id: $scope.createRandomFolderId(), + type: "readwrite", rescanIntervalS: 60, minDiskFreePct: 1, maxConflicts: 10, diff --git a/gui/default/syncthing/folder/editFolderModalView.html b/gui/default/syncthing/folder/editFolderModalView.html index 7f7a299f06..4978cbfcb6 100644 --- a/gui/default/syncthing/folder/editFolderModalView.html +++ b/gui/default/syncthing/folder/editFolderModalView.html @@ -91,13 +91,13 @@ <!-- Left column --> <div class="col-md-6"> <div class="form-group"> - <div class="checkbox"> - <label> - <input type="checkbox" ng-model="currentFolder.readOnly"> <span translate>Folder Master</span> - </label> - <a href="http://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span> <span translate>Help</span></a> - </div> - <p translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p> + <label translate>Folder Type</label> + <a href="http://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span> <span translate>Help</span></a> + <select class="form-control" ng-model="currentFolder.type"> + <option value="readwrite" translate>Normal</option> + <option value="readonly" translate>Master</option> + </select> + <p ng-if="currentFolder.type == 'readonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p> </div> <div class="form-group"> <div class="checkbox"> diff --git a/lib/config/config.go b/lib/config/config.go index 664b565171..1e2699ba6e 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -23,6 +23,9 @@ const ( OldestHandledVersion = 10 CurrentVersion = 13 MaxRescanIntervalS = 365 * 24 * 60 * 60 + + FolderTypeReadWrite = "readwrite" + FolderTypeReadOnly = "readonly" ) var ( @@ -247,6 +250,15 @@ func convertV12V13(cfg *Configuration) { cfg.Options.NATRenewalM = cfg.Options.DeprecatedUPnPRenewalM cfg.Options.NATTimeoutS = cfg.Options.DeprecatedUPnPTimeoutS cfg.Version = 13 + + for i, fcfg := range cfg.Folders { + if fcfg.DeprecatedReadOnly { + cfg.Folders[i].Type = FolderTypeReadOnly + } else { + cfg.Folders[i].Type = FolderTypeReadWrite + } + cfg.Folders[i].DeprecatedReadOnly = false + } } func convertV11V12(cfg *Configuration) { diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 2067746b4a..e37ac51d72 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -94,7 +94,7 @@ func TestDeviceConfig(t *testing.T) { ID: "test", RawPath: "testdata", Devices: []FolderDeviceConfiguration{{DeviceID: device1}, {DeviceID: device4}}, - ReadOnly: true, + Type: FolderTypeReadOnly, RescanIntervalS: 600, Copiers: 0, Pullers: 0, diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index 3cab0d25d5..99834c2f9a 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -20,8 +20,8 @@ type FolderConfiguration struct { ID string `xml:"id,attr" json:"id"` Label string `xml:"label,attr" json:"label"` RawPath string `xml:"path,attr" json:"path"` + Type string `xml:"type,attr" json:"type"` Devices []FolderDeviceConfiguration `xml:"device" json:"devices"` - ReadOnly bool `xml:"ro,attr" json:"readOnly"` RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"` IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"` AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"` @@ -41,16 +41,19 @@ type FolderConfiguration struct { Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved cachedPath string + + DeprecatedReadOnly bool `xml:"ro,attr" json:"-"` } type FolderDeviceConfiguration struct { DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"` } -func NewFolderConfiguration(id, path string) FolderConfiguration { +func NewFolderConfiguration(id, path, foldertype string) FolderConfiguration { f := FolderConfiguration{ ID: id, RawPath: path, + Type: foldertype, } f.prepare() return f diff --git a/lib/config/testdata/v13.xml b/lib/config/testdata/v13.xml index 1fec6e1bd7..45dabe041d 100644 --- a/lib/config/testdata/v13.xml +++ b/lib/config/testdata/v13.xml @@ -1,5 +1,5 @@ <configuration version="13"> - <folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600" autoNormalize="true"> + <folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" autoNormalize="true"> <device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device> <device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device> <minDiskFreePct>1</minDiskFreePct> diff --git a/lib/model/model.go b/lib/model/model.go index e62e9bfd52..2bb503fe8b 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -100,8 +100,11 @@ type Model struct { pmut sync.RWMutex // protects the above } +type folderFactory func(*Model, config.FolderConfiguration, versioner.Versioner) service + var ( - symlinkWarning = stdsync.Once{} + symlinkWarning = stdsync.Once{} + folderFactories = make(map[string]folderFactory, 0) ) // NewModel creates and starts a new model. The model starts in read-only mode, @@ -158,10 +161,8 @@ func (m *Model) StartDeadlockDetector(timeout time.Duration) { deadlockDetect(m.pmut, timeout) } -// StartFolderRW starts read/write processing on the current model. When in -// read/write mode the model will attempt to keep in sync with the cluster by -// pulling needed files from peer devices. -func (m *Model) StartFolderRW(folder string) { +// StartFolder constrcuts the folder service and starts it. +func (m *Model) StartFolder(folder string) { m.fmut.Lock() cfg, ok := m.folderCfgs[folder] if !ok { @@ -172,37 +173,43 @@ func (m *Model) StartFolderRW(folder string) { if ok { panic("cannot start already running folder " + folder) } - p := newRWFolder(m, cfg) - m.folderRunners[folder] = p + folderFactory, ok := folderFactories[cfg.Type] + if !ok { + panic("unknown folder type " + cfg.Type) + } + + var ver versioner.Versioner if len(cfg.Versioning.Type) > 0 { - factory, ok := versioner.Factories[cfg.Versioning.Type] + versionerFactory, ok := versioner.Factories[cfg.Versioning.Type] if !ok { l.Fatalf("Requested versioning type %q that does not exist", cfg.Versioning.Type) } - versioner := factory(folder, cfg.Path(), cfg.Versioning.Params) - if service, ok := versioner.(suture.Service); ok { + ver = versionerFactory(folder, cfg.Path(), cfg.Versioning.Params) + if service, ok := ver.(suture.Service); ok { // The versioner implements the suture.Service interface, so // expects to be run in the background in addition to being called // when files are going to be archived. token := m.Add(service) m.folderRunnerTokens[folder] = append(m.folderRunnerTokens[folder], token) } - p.versioner = versioner } + p := folderFactory(m, cfg, ver) + m.folderRunners[folder] = p + m.warnAboutOverwritingProtectedFiles(folder) token := m.Add(p) m.folderRunnerTokens[folder] = append(m.folderRunnerTokens[folder], token) m.fmut.Unlock() - l.Infoln("Ready to synchronize", folder, "(read-write)") + l.Infoln("Ready to synchronize", folder, fmt.Sprintf("(%s)", cfg.Type)) } func (m *Model) warnAboutOverwritingProtectedFiles(folder string) { - if m.folderCfgs[folder].ReadOnly { + if m.folderCfgs[folder].Type == config.FolderTypeReadOnly { return } @@ -229,30 +236,6 @@ func (m *Model) warnAboutOverwritingProtectedFiles(folder string) { } } -// StartFolderRO starts read only processing on the current model. When in -// read only mode the model will announce files to the cluster but not pull in -// any external changes. -func (m *Model) StartFolderRO(folder string) { - m.fmut.Lock() - cfg, ok := m.folderCfgs[folder] - if !ok { - panic("cannot start nonexistent folder " + folder) - } - - _, ok = m.folderRunners[folder] - if ok { - panic("cannot start already running folder " + folder) - } - s := newROFolder(m, cfg) - m.folderRunners[folder] = s - - token := m.Add(s) - m.folderRunnerTokens[folder] = append(m.folderRunnerTokens[folder], token) - m.fmut.Unlock() - - l.Infoln("Ready to synchronize", folder, "(read only; no external updates accepted)") -} - func (m *Model) RemoveFolder(folder string) { m.fmut.Lock() m.pmut.Lock() @@ -1093,7 +1076,7 @@ func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, update cfg, ok := m.folderCfgs[folder] m.fmut.RUnlock() - if !ok || cfg.ReadOnly || cfg.DisableTempIndexes { + if !ok || cfg.Type == config.FolderTypeReadOnly || cfg.DisableTempIndexes { return } @@ -1606,7 +1589,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster Label: folderCfg.Label, } var flags uint32 - if folderCfg.ReadOnly { + if folderCfg.Type == config.FolderTypeReadOnly { flags |= protocol.FlagFolderReadOnly } if folderCfg.IgnorePerms { @@ -1874,7 +1857,7 @@ func (m *Model) CheckFolderHealth(id string) error { case !folder.HasMarker(): err = errors.New("folder marker missing") - case !folder.ReadOnly: + case folder.Type != config.FolderTypeReadOnly: // Check for free space, if it isn't a master folder. We aren't // going to change the contents of master folders, so we don't // care about the amount of free space there. @@ -1951,11 +1934,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool { // A folder was added. l.Debugln(m, "adding folder", folderID) m.AddFolder(cfg) - if cfg.ReadOnly { - m.StartFolderRO(folderID) - } else { - m.StartFolderRW(folderID) - } + m.StartFolder(folderID) // Drop connections to all devices that can now share the new // folder. diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 24c7352ffa..18d2b6ae2a 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -34,7 +34,7 @@ func init() { device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR") device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY") - defaultFolderConfig = config.NewFolderConfiguration("default", "testdata") + defaultFolderConfig = config.NewFolderConfiguration("default", "testdata", config.FolderTypeReadWrite) defaultFolderConfig.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}} _defaultConfig := config.Configuration{ Folders: []config.FolderConfiguration{defaultFolderConfig}, @@ -85,7 +85,7 @@ func TestRequest(t *testing.T) { // device1 shares default, but device2 doesn't m.AddFolder(defaultFolderConfig) - m.StartFolderRO("default") + m.StartFolder("default") m.ServeBackground() m.ScanFolder("default") @@ -159,7 +159,7 @@ func benchmarkIndex(b *testing.B, nfiles int) { db := db.OpenMemory() m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) - m.StartFolderRO("default") + m.StartFolder("default") m.ServeBackground() files := genFiles(nfiles) @@ -188,7 +188,7 @@ func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) { db := db.OpenMemory() m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) - m.StartFolderRO("default") + m.StartFolder("default") m.ServeBackground() files := genFiles(nfiles) @@ -492,7 +492,7 @@ func TestIgnores(t *testing.T) { db := db.OpenMemory() m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) - m.StartFolderRO("default") + m.StartFolder("default") m.ServeBackground() expected := []string{ @@ -609,6 +609,7 @@ func TestROScanRecovery(t *testing.T) { fcfg := config.FolderConfiguration{ ID: "default", RawPath: "testdata/rotestfolder", + Type: config.FolderTypeReadOnly, RescanIntervalS: 1, } cfg := config.Wrap("/tmp/test", config.Configuration{ @@ -624,7 +625,7 @@ func TestROScanRecovery(t *testing.T) { m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) - m.StartFolderRO("default") + m.StartFolder("default") m.ServeBackground() waitFor := func(status string) error { @@ -693,6 +694,7 @@ func TestRWScanRecovery(t *testing.T) { fcfg := config.FolderConfiguration{ ID: "default", RawPath: "testdata/rwtestfolder", + Type: config.FolderTypeReadWrite, RescanIntervalS: 1, } cfg := config.Wrap("/tmp/test", config.Configuration{ @@ -708,7 +710,7 @@ func TestRWScanRecovery(t *testing.T) { m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) - m.StartFolderRW("default") + m.StartFolder("default") m.ServeBackground() waitFor := func(status string) error { @@ -1219,7 +1221,7 @@ func TestIgnoreDelete(t *testing.T) { m.AddFolder(cfg) m.ServeBackground() - m.StartFolderRW("default") + m.StartFolder("default") m.ScanFolder("default") // Get a currently existing file diff --git a/lib/model/rofolder.go b/lib/model/rofolder.go index 5a146191ef..1f0d7bce6f 100644 --- a/lib/model/rofolder.go +++ b/lib/model/rofolder.go @@ -12,13 +12,18 @@ import ( "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/sync" + "github.com/syncthing/syncthing/lib/versioner" ) +func init() { + folderFactories[config.FolderTypeReadOnly] = newROFolder +} + type roFolder struct { folder } -func newROFolder(model *Model, cfg config.FolderConfiguration) *roFolder { +func newROFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner) service { return &roFolder{ folder: folder{ stateTracker: stateTracker{ diff --git a/lib/model/rwfolder.go b/lib/model/rwfolder.go index e0b49a8e97..695d57714f 100644 --- a/lib/model/rwfolder.go +++ b/lib/model/rwfolder.go @@ -31,6 +31,10 @@ import ( // TODO: Stop on errors +func init() { + folderFactories[config.FolderTypeReadWrite] = newRWFolder +} + // A pullBlockState is passed to the puller routine for each block that needs // to be fetched. type pullBlockState struct { @@ -98,7 +102,7 @@ type rwFolder struct { errorsMut sync.Mutex } -func newRWFolder(model *Model, cfg config.FolderConfiguration) *rwFolder { +func newRWFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner) service { f := &rwFolder{ folder: folder{ stateTracker: stateTracker{ @@ -124,6 +128,7 @@ func newRWFolder(model *Model, cfg config.FolderConfiguration) *rwFolder { maxConflicts: cfg.MaxConflicts, allowSparse: !cfg.DisableSparseFiles, checkFreeSpace: cfg.MinDiskFreePct != 0, + versioner: ver, queue: newJobQueue(), pullTimer: time.NewTimer(time.Second), |