mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-10 06:49:31 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
803da92ca9 | ||
|
|
b49bbe82dd | ||
|
|
3959eb26fb | ||
|
|
1235cead35 | ||
|
|
dd6bb6d5fd | ||
|
|
91d37f35bc | ||
|
|
51518490c6 | ||
|
|
2c10beed0b |
59
cmd/syncthing/cpuusage.go
Normal file
59
cmd/syncthing/cpuusage.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (C) 2017 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
metrics "github.com/rcrowley/go-metrics"
|
||||
)
|
||||
|
||||
const cpuTickRate = 5 * time.Second
|
||||
|
||||
type cpuService struct {
|
||||
avg metrics.EWMA
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func newCPUService() *cpuService {
|
||||
return &cpuService{
|
||||
// 10 second average. Magic alpha value comes from looking at EWMA package
|
||||
// definitions of EWMA1, EWMA5. The tick rate *must* be five seconds (hard
|
||||
// coded in the EWMA package).
|
||||
avg: metrics.NewEWMA(1 - math.Exp(-float64(cpuTickRate)/float64(time.Second)/10.0)),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cpuService) Serve() {
|
||||
// Initialize prevUsage to an actual value returned by cpuUsage
|
||||
// instead of zero, because at least Windows returns a huge negative
|
||||
// number here that then slowly increments...
|
||||
prevUsage := cpuUsage()
|
||||
ticker := time.NewTicker(cpuTickRate)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
curUsage := cpuUsage()
|
||||
s.avg.Update(int64((curUsage - prevUsage) / time.Millisecond))
|
||||
prevUsage = curUsage
|
||||
s.avg.Tick()
|
||||
case <-s.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cpuService) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
func (s *cpuService) Rate() float64 {
|
||||
return s.avg.Rate()
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -69,6 +68,7 @@ type apiService struct {
|
||||
configChanged chan struct{} // signals intentional listener close due to config change
|
||||
started chan string // signals startup complete by sending the listener address, for testing only
|
||||
startedOnce chan struct{} // the service has started successfully at least once
|
||||
cpu rater
|
||||
|
||||
guiErrors logger.Recorder
|
||||
systemLog logger.Recorder
|
||||
@@ -121,7 +121,11 @@ type connectionsIntf interface {
|
||||
Status() map[string]interface{}
|
||||
}
|
||||
|
||||
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) *apiService {
|
||||
type rater interface {
|
||||
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 {
|
||||
service := &apiService{
|
||||
id: id,
|
||||
cfg: cfg,
|
||||
@@ -142,6 +146,7 @@ func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKey
|
||||
startedOnce: make(chan struct{}),
|
||||
guiErrors: errors,
|
||||
systemLog: systemLog,
|
||||
cpu: cpu,
|
||||
}
|
||||
|
||||
return service
|
||||
@@ -847,30 +852,6 @@ func (s *apiService) flushResponse(resp string, w http.ResponseWriter) {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
// 10 second average. Magic alpha value comes from looking at EWMA package
|
||||
// definitions of EWMA1, EWMA5. The tick rate *must* be five seconds (hard
|
||||
// coded in the EWMA package).
|
||||
var cpuTickRate = 5 * time.Second
|
||||
var cpuAverage = metrics.NewEWMA(1 - math.Exp(-float64(cpuTickRate)/float64(time.Second)/10.0))
|
||||
|
||||
func init() {
|
||||
if !innerProcess {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
// Initialize prevUsage to an actual value returned by cpuUsage
|
||||
// instead of zero, because at least Windows returns a huge negative
|
||||
// number here that then slowly increments...
|
||||
prevUsage := cpuUsage()
|
||||
for range time.NewTicker(cpuTickRate).C {
|
||||
curUsage := cpuUsage()
|
||||
cpuAverage.Update(int64((curUsage - prevUsage) / time.Millisecond))
|
||||
prevUsage = curUsage
|
||||
cpuAverage.Tick()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
@@ -899,7 +880,7 @@ func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
res["connectionServiceStatus"] = s.connectionsService.Status()
|
||||
// cpuUsage.Rate() is in milliseconds per second, so dividing by ten
|
||||
// gives us percent
|
||||
res["cpuPercent"] = cpuAverage.Rate() / 10 / float64(runtime.NumCPU())
|
||||
res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU())
|
||||
res["pathSeparator"] = string(filepath.Separator)
|
||||
res["uptime"] = int(time.Since(startTime).Seconds())
|
||||
res["startTime"] = startTime
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
|
||||
}
|
||||
w := config.Wrap("/dev/null", cfg)
|
||||
|
||||
srv := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil, nil)
|
||||
srv := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
srv.started = make(chan string)
|
||||
|
||||
sup := suture.NewSimple("test")
|
||||
@@ -475,11 +475,12 @@ func startHTTP(cfg *mockedConfig) (string, error) {
|
||||
connections := new(mockedConnections)
|
||||
errorLog := new(mockedLoggerRecorder)
|
||||
systemLog := new(mockedLoggerRecorder)
|
||||
cpu := new(mockedCPUService)
|
||||
addrChan := make(chan string)
|
||||
|
||||
// Instantiate the API service
|
||||
svc := newAPIService(protocol.LocalDeviceID, cfg, httpsCertFile, httpsKeyFile, assetDir, model,
|
||||
eventSub, diskEventSub, discoverer, connections, errorLog, systemLog)
|
||||
eventSub, diskEventSub, discoverer, connections, errorLog, systemLog, cpu)
|
||||
svc.started = addrChan
|
||||
|
||||
// Actually start the API service
|
||||
@@ -930,7 +931,7 @@ func TestEventMasks(t *testing.T) {
|
||||
cfg := new(mockedConfig)
|
||||
defSub := new(mockedEventSub)
|
||||
diskSub := new(mockedEventSub)
|
||||
svc := newAPIService(protocol.LocalDeviceID, cfg, "", "", "", nil, defSub, diskSub, nil, nil, nil, nil)
|
||||
svc := newAPIService(protocol.LocalDeviceID, cfg, "", "", "", nil, defSub, diskSub, nil, nil, nil, nil, nil)
|
||||
|
||||
if mask := svc.getEventMask(""); mask != defaultEventMask {
|
||||
t.Errorf("incorrect default mask %x != %x", int64(mask), int64(defaultEventMask))
|
||||
|
||||
@@ -427,34 +427,6 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// ---BEGIN TEMPORARY HACK---
|
||||
//
|
||||
// Remove once v0.14.21-v0.14.22 are rare enough. Those versions,
|
||||
// essentially:
|
||||
//
|
||||
// 1. os.Setenv("STMONITORED", "yes")
|
||||
// 2. os.Setenv("STNORESTART", "")
|
||||
//
|
||||
// where the intention was for 2 to cancel out 1 instead of setting
|
||||
// STNORESTART to the empty value. We check for exactly this combination
|
||||
// and pretend that neither was set. Looking through os.Environ lets us
|
||||
// distinguish. Luckily, we weren't smart enough to use os.Unsetenv.
|
||||
|
||||
matches := 0
|
||||
for _, str := range os.Environ() {
|
||||
if str == "STNORESTART=" {
|
||||
matches++
|
||||
}
|
||||
if str == "STMONITORED=yes" {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
if matches == 2 {
|
||||
innerProcess = false
|
||||
}
|
||||
|
||||
// ---END TEMPORARY HACK---
|
||||
|
||||
if innerProcess || options.noRestart {
|
||||
syncthingMain(options)
|
||||
} else {
|
||||
@@ -795,7 +767,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
ldb.ConvertSymlinkTypes()
|
||||
}
|
||||
|
||||
m := model.NewModel(cfg, myID, myDeviceName(cfg), "syncthing", Version, ldb, protectedFiles)
|
||||
m := model.NewModel(cfg, myID, "syncthing", Version, ldb, protectedFiles)
|
||||
|
||||
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
|
||||
it, err := strconv.Atoi(t)
|
||||
@@ -964,15 +936,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func myDeviceName(cfg *config.Wrapper) string {
|
||||
devices := cfg.Devices()
|
||||
myName := devices[myID].Name
|
||||
if myName == "" {
|
||||
myName, _ = os.Hostname()
|
||||
}
|
||||
return myName
|
||||
}
|
||||
|
||||
func setupSignalHandling() {
|
||||
// Exit cleanly with "restarting" code on SIGHUP.
|
||||
|
||||
@@ -1102,7 +1065,10 @@ func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Mode
|
||||
l.Warnln("Insecure admin access is enabled.")
|
||||
}
|
||||
|
||||
api := newAPIService(myID, cfg, locations[locHTTPSCertFile], locations[locHTTPSKeyFile], runtimeOptions.assetDir, m, defaultSub, diskSub, discoverer, connectionsService, errors, systemLog)
|
||||
cpu := newCPUService()
|
||||
mainService.Add(cpu)
|
||||
|
||||
api := newAPIService(myID, cfg, locations[locHTTPSCertFile], locations[locHTTPSKeyFile], runtimeOptions.assetDir, m, defaultSub, diskSub, discoverer, connectionsService, errors, systemLog, cpu)
|
||||
cfg.Subscribe(api)
|
||||
mainService.Add(api)
|
||||
|
||||
|
||||
13
cmd/syncthing/mocked_cpuusage_test.go
Normal file
13
cmd/syncthing/mocked_cpuusage_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2017 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
type mockedCPUService struct{}
|
||||
|
||||
func (*mockedCPUService) Rate() float64 {
|
||||
return 42
|
||||
}
|
||||
@@ -158,6 +158,8 @@
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Please set a GUI Authentication User and Password in the Settings dialog.",
|
||||
"Please wait": "Please wait",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
|
||||
"Preview": "Preview",
|
||||
"Preview Usage Report": "Preview Usage Report",
|
||||
"Quick guide to supported patterns": "Quick guide to supported patterns",
|
||||
|
||||
@@ -216,6 +216,9 @@
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="dismissFolderRejection(event.data.folder, event.data.device)">
|
||||
<span class="fa fa-clock-o"></span> <span translate>Later</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="ignoreRejectedFolder(event.data.folder, event.data.device)">
|
||||
<span class="fa fa-times"></span> <span translate>Ignore</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,22 +20,22 @@ angular.module('syncthing.core')
|
||||
}).every(function(e) { return e });
|
||||
}
|
||||
|
||||
scope.pathIsSubFolder = false;
|
||||
scope.pathIsParentFolder = false;
|
||||
scope.otherFolder = "";
|
||||
scope.otherFolderLabel = "";
|
||||
scope.folderPathErrors.isSub = false;
|
||||
scope.folderPathErrors.isParent = false;
|
||||
scope.folderPathErrors.otherID = "";
|
||||
scope.folderPathErrors.otherLabel = "";
|
||||
for (var folderID in scope.folders) {
|
||||
if (isSubDir(scope.folders[folderID].path, viewValue)) {
|
||||
scope.otherFolder = folderID;
|
||||
scope.otherFolderLabel = scope.folders[folderID].label;
|
||||
scope.pathIsSubFolder = true;
|
||||
scope.folderPathErrors.otherID = folderID;
|
||||
scope.folderPathErrors.otherLabel = scope.folders[folderID].label;
|
||||
scope.folderPathErrors.isSub = true;
|
||||
break;
|
||||
}
|
||||
if (viewValue !== "" &&
|
||||
isSubDir(viewValue, scope.folders[folderID].path)) {
|
||||
scope.otherFolder = folderID;
|
||||
scope.otherFolderLabel = scope.folders[folderID].label;
|
||||
scope.pathIsParentFolder = true;
|
||||
scope.folderPathErrors.otherID = folderID;
|
||||
scope.folderPathErrors.otherLabel = scope.folders[folderID].label;
|
||||
scope.folderPathErrors.isParent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,27 +53,28 @@ angular.module('syncthing.core')
|
||||
$scope.themes = [];
|
||||
$scope.globalChangeEvents = {};
|
||||
$scope.metricRates = false;
|
||||
$scope.folderPathErrors = {};
|
||||
|
||||
try {
|
||||
$scope.metricRates = (window.localStorage["metricRates"] == "true");
|
||||
} catch (exception) { }
|
||||
|
||||
$scope.folderDefaults = {
|
||||
selectedDevices: {},
|
||||
type: "readwrite",
|
||||
rescanIntervalS: 60,
|
||||
minDiskFree: {value: 1, unit: "%"},
|
||||
maxConflicts: 10,
|
||||
fsync: true,
|
||||
order: "random",
|
||||
fileVersioningSelector: "none",
|
||||
trashcanClean: 0,
|
||||
simpleKeep: 5,
|
||||
staggeredMaxAge: 365,
|
||||
staggeredCleanInterval: 3600,
|
||||
staggeredVersionsPath: "",
|
||||
externalCommand: "",
|
||||
autoNormalize: true
|
||||
selectedDevices: {},
|
||||
type: "readwrite",
|
||||
rescanIntervalS: 60,
|
||||
minDiskFree: {value: 1, unit: "%"},
|
||||
maxConflicts: 10,
|
||||
fsync: true,
|
||||
order: "random",
|
||||
fileVersioningSelector: "none",
|
||||
trashcanClean: 0,
|
||||
simpleKeep: 5,
|
||||
staggeredMaxAge: 365,
|
||||
staggeredCleanInterval: 3600,
|
||||
staggeredVersionsPath: "",
|
||||
externalCommand: "",
|
||||
autoNormalize: true
|
||||
};
|
||||
|
||||
$scope.localStateTotal = {
|
||||
@@ -1409,6 +1410,7 @@ angular.module('syncthing.core')
|
||||
$scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
|
||||
|
||||
$scope.editingExisting = true;
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.folderEditor.$setPristine();
|
||||
$('#editFolder').modal();
|
||||
};
|
||||
@@ -1417,6 +1419,7 @@ angular.module('syncthing.core')
|
||||
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
||||
$scope.editingExisting = false;
|
||||
$('#editIgnores textarea').val("");
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.folderEditor.$setPristine();
|
||||
$http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
|
||||
$scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
|
||||
@@ -1435,6 +1438,7 @@ angular.module('syncthing.core')
|
||||
$scope.currentFolder.selectedDevices[device] = true;
|
||||
|
||||
$scope.editingExisting = false;
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.folderEditor.$setPristine();
|
||||
$('#editFolder').modal();
|
||||
};
|
||||
@@ -1528,6 +1532,12 @@ angular.module('syncthing.core')
|
||||
delete $scope.folderRejections[folder + "-" + device];
|
||||
};
|
||||
|
||||
$scope.ignoreRejectedFolder = function (folder, device) {
|
||||
$scope.config.ignoredFolders.push(folder);
|
||||
$scope.saveConfig();
|
||||
$scope.dismissFolderRejection(folder, device);
|
||||
};
|
||||
|
||||
$scope.sharesFolder = function (folderCfg) {
|
||||
var names = [];
|
||||
folderCfg.devices.forEach(function (device) {
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
|
||||
</datalist>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine">Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.
|
||||
<span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{otherFolder}}" ng-if="pathIsSubFolder && otherFolderLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{otherFolder}}" translate-value-other-folder-label="{{otherFolderLabel}}" ng-if="pathIsSubFolder && otherFolderLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{otherFolder}}" ng-if="pathIsParentFolder && otherFolderLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{otherFolder}}" translate-value-other-folder-label="{{otherFolderLabel}}" ng-if="pathIsParentFolder && otherFolderLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
|
||||
<dl class="dl-horizontal dl-narrow small">
|
||||
<dt><code>(?d)</code></dt> <dd><b><span translate>Prefix indicating that the file can be deleted if preventing directory removal</span></b></dd>
|
||||
<dt><code>(?i)</code></dt> <dd><span translate>Prefix indicating that the pattern should be matched without case sensitivity</span></dd>
|
||||
<dt><code>!</code></dt> <dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
|
||||
<dt><code>*</code></dt> <dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
|
||||
<dt><code>**</code></dt> <dd><span translate>Multi level wildcard (matches multiple directory levels)</span></dd>
|
||||
|
||||
@@ -155,9 +155,11 @@ type Configuration struct {
|
||||
GUI GUIConfiguration `xml:"gui" json:"gui"`
|
||||
Options OptionsConfiguration `xml:"options" json:"options"`
|
||||
IgnoredDevices []protocol.DeviceID `xml:"ignoredDevice" json:"ignoredDevices"`
|
||||
IgnoredFolders []string `xml:"ignoredFolder" json:"ignoredFolders"`
|
||||
XMLName xml.Name `xml:"configuration" json:"-"`
|
||||
|
||||
OriginalVersion int `xml:"-" json:"-"` // The version we read from disk, before any conversion
|
||||
MyID protocol.DeviceID `xml:"-" json:"-"` // Provided by the instantiator.
|
||||
OriginalVersion int `xml:"-" json:"-"` // The version we read from disk, before any conversion
|
||||
}
|
||||
|
||||
func (cfg Configuration) Copy() Configuration {
|
||||
@@ -181,6 +183,10 @@ func (cfg Configuration) Copy() Configuration {
|
||||
newCfg.IgnoredDevices = make([]protocol.DeviceID, len(cfg.IgnoredDevices))
|
||||
copy(newCfg.IgnoredDevices, cfg.IgnoredDevices)
|
||||
|
||||
// FolderConfiguraion.ID is type string
|
||||
newCfg.IgnoredFolders = make([]string, len(cfg.IgnoredFolders))
|
||||
copy(newCfg.IgnoredFolders, cfg.IgnoredFolders)
|
||||
|
||||
return newCfg
|
||||
}
|
||||
|
||||
@@ -198,6 +204,8 @@ func (cfg *Configuration) WriteXML(w io.Writer) error {
|
||||
func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
|
||||
var myName string
|
||||
|
||||
cfg.MyID = myID
|
||||
|
||||
// Ensure this device is present in the config
|
||||
for _, device := range cfg.Devices {
|
||||
if device.DeviceID == myID {
|
||||
@@ -235,6 +243,9 @@ func (cfg *Configuration) clean() error {
|
||||
if cfg.IgnoredDevices == nil {
|
||||
cfg.IgnoredDevices = []protocol.DeviceID{}
|
||||
}
|
||||
if cfg.IgnoredFolders == nil {
|
||||
cfg.IgnoredFolders = []string{}
|
||||
}
|
||||
if cfg.Options.AlwaysLocalNets == nil {
|
||||
cfg.Options.AlwaysLocalNets = []string{}
|
||||
}
|
||||
|
||||
@@ -321,6 +321,19 @@ func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoredFolder returns whether or not share attempts for the given
|
||||
// folder should be silently ignored.
|
||||
func (w *Wrapper) IgnoredFolder(folder string) bool {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
for _, nfolder := range w.cfg.IgnoredFolders {
|
||||
if folder == nfolder {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Device returns the configuration for the given device and an "ok" bool.
|
||||
func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
|
||||
w.mut.Lock()
|
||||
@@ -431,3 +444,11 @@ func (w *Wrapper) StunServers() []string {
|
||||
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (w *Wrapper) MyName() string {
|
||||
w.mut.Lock()
|
||||
myID := w.cfg.MyID
|
||||
w.mut.Unlock()
|
||||
cfg, _ := w.Device(myID)
|
||||
return cfg.Name
|
||||
}
|
||||
|
||||
@@ -61,11 +61,14 @@ func (d *kcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, erro
|
||||
conn.Close()
|
||||
return internalConn{}, err
|
||||
}
|
||||
|
||||
ses.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
stream, err := ses.OpenStream()
|
||||
if err != nil {
|
||||
ses.Close()
|
||||
return internalConn{}, err
|
||||
}
|
||||
ses.SetDeadline(time.Time{})
|
||||
|
||||
tc := tls.Client(&sessionClosingStream{stream, ses}, d.tlsCfg)
|
||||
tc.SetDeadline(time.Now().Add(time.Second * 10))
|
||||
|
||||
@@ -123,12 +123,14 @@ func (t *kcpListener) Serve() {
|
||||
continue
|
||||
}
|
||||
|
||||
ses.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
stream, err := ses.AcceptStream()
|
||||
if err != nil {
|
||||
l.Debugln("smux accept:", err)
|
||||
ses.Close()
|
||||
continue
|
||||
}
|
||||
ses.SetDeadline(time.Time{})
|
||||
|
||||
tc := tls.Server(&sessionClosingStream{stream, ses}, t.tlsCfg)
|
||||
tc.SetDeadline(time.Now().Add(time.Second * 10))
|
||||
|
||||
@@ -77,7 +77,6 @@ type Model struct {
|
||||
cacheIgnoredFiles bool
|
||||
protectedFiles []string
|
||||
|
||||
deviceName string
|
||||
clientName string
|
||||
clientVersion string
|
||||
|
||||
@@ -123,7 +122,7 @@ var (
|
||||
// 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
|
||||
// for file data without altering the local folder in any way.
|
||||
func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, clientVersion string, ldb *db.Instance, protectedFiles []string) *Model {
|
||||
func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Instance, protectedFiles []string) *Model {
|
||||
m := &Model{
|
||||
Supervisor: suture.New("model", suture.Spec{
|
||||
Log: func(line string) {
|
||||
@@ -138,7 +137,6 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
|
||||
shortID: id.Short(),
|
||||
cacheIgnoredFiles: cfg.Options().CacheIgnoredFiles,
|
||||
protectedFiles: protectedFiles,
|
||||
deviceName: deviceName,
|
||||
clientName: clientName,
|
||||
clientVersion: clientVersion,
|
||||
folderCfgs: make(map[string]config.FolderConfiguration),
|
||||
@@ -811,6 +809,11 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
continue
|
||||
}
|
||||
|
||||
if m.cfg.IgnoredFolder(folder.ID) {
|
||||
l.Infof("Ignoring folder %s from device %s since we are configured to", folder.Description(), deviceID)
|
||||
continue
|
||||
}
|
||||
|
||||
if !m.folderSharedWithLocked(folder.ID, deviceID) {
|
||||
events.Default.Log(events.FolderRejected, map[string]string{
|
||||
"folder": folder.ID,
|
||||
@@ -1320,9 +1323,13 @@ 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.
|
||||
func (m *Model) GetHello(protocol.DeviceID) protocol.HelloIntf {
|
||||
func (m *Model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
|
||||
name := ""
|
||||
if _, ok := m.cfg.Device(id); ok {
|
||||
name = m.cfg.MyName()
|
||||
}
|
||||
return &protocol.Hello{
|
||||
DeviceName: m.deviceName,
|
||||
DeviceName: name,
|
||||
ClientName: m.clientName,
|
||||
ClientVersion: m.clientVersion,
|
||||
}
|
||||
@@ -1634,7 +1641,8 @@ func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files [
|
||||
|
||||
// Two different events can be fired here based on what EventType is passed into function
|
||||
events.Default.Log(typeOfEvent, map[string]string{
|
||||
"folderID": folderCfg.ID,
|
||||
"folder": folderCfg.ID,
|
||||
"folderID": folderCfg.ID, // incorrect, deprecated, kept for historical compliance
|
||||
"label": folderCfg.Label,
|
||||
"action": action,
|
||||
"type": objType,
|
||||
|
||||
@@ -88,7 +88,7 @@ func init() {
|
||||
func TestRequest(t *testing.T) {
|
||||
db := db.OpenMemory()
|
||||
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
|
||||
// device1 shares default, but device2 doesn't
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
@@ -166,7 +166,7 @@ func BenchmarkIndex_100(b *testing.B) {
|
||||
|
||||
func benchmarkIndex(b *testing.B, nfiles int) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -196,7 +196,7 @@ func BenchmarkIndexUpdate_10000_1(b *testing.B) {
|
||||
|
||||
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -356,7 +356,7 @@ func (f *fakeConnection) sendIndexUpdate() {
|
||||
|
||||
func BenchmarkRequestOut(b *testing.B) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ServeBackground()
|
||||
defer m.Stop()
|
||||
@@ -386,7 +386,7 @@ func BenchmarkRequestOut(b *testing.B) {
|
||||
|
||||
func BenchmarkRequestInSingleFile(b *testing.B) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ServeBackground()
|
||||
defer m.Stop()
|
||||
@@ -426,7 +426,7 @@ func TestDeviceRename(t *testing.T) {
|
||||
cfg := config.Wrap("tmpconfig.xml", rawCfg)
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
|
||||
if cfg.Devices()[device1].Name != "" {
|
||||
t.Errorf("Device already has a name")
|
||||
@@ -512,7 +512,7 @@ func TestClusterConfig(t *testing.T) {
|
||||
|
||||
db := db.OpenMemory()
|
||||
|
||||
m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(cfg.Folders[0])
|
||||
m.AddFolder(cfg.Folders[1])
|
||||
m.ServeBackground()
|
||||
@@ -586,7 +586,7 @@ func TestIntroducer(t *testing.T) {
|
||||
|
||||
wcfg := config.Wrap("/tmp/test", cfg)
|
||||
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
for _, folder := range cfg.Folders {
|
||||
m.AddFolder(folder)
|
||||
}
|
||||
@@ -1002,7 +1002,7 @@ func TestIgnores(t *testing.T) {
|
||||
ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644)
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.ServeBackground()
|
||||
defer m.Stop()
|
||||
|
||||
@@ -1073,7 +1073,7 @@ func TestROScanRecovery(t *testing.T) {
|
||||
|
||||
os.RemoveAll(fcfg.RawPath)
|
||||
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "syncthing", "dev", ldb, nil)
|
||||
m.AddFolder(fcfg)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -1160,7 +1160,7 @@ func TestRWScanRecovery(t *testing.T) {
|
||||
|
||||
os.RemoveAll(fcfg.RawPath)
|
||||
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "syncthing", "dev", ldb, nil)
|
||||
m.AddFolder(fcfg)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -1225,7 +1225,7 @@ func TestRWScanRecovery(t *testing.T) {
|
||||
|
||||
func TestGlobalDirectoryTree(t *testing.T) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ServeBackground()
|
||||
defer m.Stop()
|
||||
@@ -1477,7 +1477,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
|
||||
|
||||
func TestGlobalDirectorySelfFixing(t *testing.T) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ServeBackground()
|
||||
|
||||
@@ -1652,7 +1652,7 @@ func BenchmarkTree_100_10(b *testing.B) {
|
||||
|
||||
func benchmarkTree(b *testing.B, n1, n2 int) {
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ServeBackground()
|
||||
|
||||
@@ -1787,7 +1787,7 @@ func TestIssue3028(t *testing.T) {
|
||||
// Create a model and default folder
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
defCfg := defaultFolderConfig.Copy()
|
||||
defCfg.RescanIntervalS = 86400
|
||||
m.AddFolder(defCfg)
|
||||
@@ -1865,7 +1865,7 @@ func TestScanNoDatabaseWrite(t *testing.T) {
|
||||
// something actually changed.
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -1949,7 +1949,7 @@ func TestIssue2782(t *testing.T) {
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(config.NewFolderConfiguration("default", "~/"+testName+"/synclink/"))
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -1975,7 +1975,7 @@ func TestIndexesForUnknownDevicesDropped(t *testing.T) {
|
||||
t.Error("expected two devices")
|
||||
}
|
||||
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
|
||||
@@ -2009,7 +2009,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
|
||||
|
||||
wcfg := config.Wrap("/tmp/test", cfg)
|
||||
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
||||
m.AddFolder(fcfg)
|
||||
m.StartFolder(fcfg.ID)
|
||||
m.ServeBackground()
|
||||
@@ -2123,7 +2123,7 @@ func TestIssue3496(t *testing.T) {
|
||||
// checks on the completion calculation stuff.
|
||||
|
||||
dbi := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -2196,7 +2196,7 @@ func TestIssue3496(t *testing.T) {
|
||||
|
||||
func TestIssue3804(t *testing.T) {
|
||||
dbi := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -2211,7 +2211,7 @@ func TestIssue3804(t *testing.T) {
|
||||
|
||||
func TestIssue3829(t *testing.T) {
|
||||
dbi := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.StartFolder("default")
|
||||
m.ServeBackground()
|
||||
@@ -2248,7 +2248,7 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
|
||||
|
||||
wcfg := config.Wrap("/tmp/test", cfg)
|
||||
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
||||
m.AddFolder(fcfg)
|
||||
m.StartFolder(fcfg.ID)
|
||||
m.ServeBackground()
|
||||
|
||||
@@ -215,7 +215,7 @@ func setupModelWithConnection() (*Model, *fakeConnection) {
|
||||
w := config.Wrap("/tmp/cfg", cfg)
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(w, device1, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(w, device1, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(cfg.Folders[0])
|
||||
m.ServeBackground()
|
||||
m.StartFolder("default")
|
||||
|
||||
@@ -852,7 +852,9 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo) {
|
||||
// of deleting. Also merge with the version vector we had, to indicate
|
||||
// we have resolved the conflict.
|
||||
file.Version = file.Version.Merge(cur.Version)
|
||||
err = osutil.InWritableDir(f.moveForConflict, realName)
|
||||
err = osutil.InWritableDir(func(name string) error {
|
||||
return f.moveForConflict(name, file.ModifiedBy.String())
|
||||
}, realName)
|
||||
} else if f.versioner != nil {
|
||||
err = osutil.InWritableDir(f.versioner.Archive, realName)
|
||||
} else {
|
||||
@@ -1454,7 +1456,10 @@ func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
|
||||
// we have resolved the conflict.
|
||||
|
||||
state.file.Version = state.file.Version.Merge(state.version)
|
||||
if err = osutil.InWritableDir(f.moveForConflict, state.realName); err != nil {
|
||||
err = osutil.InWritableDir(func(name string) error {
|
||||
return f.moveForConflict(name, state.file.ModifiedBy.String())
|
||||
}, state.realName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1661,7 +1666,7 @@ func removeAvailability(availabilities []Availability, availability Availability
|
||||
return availabilities
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) moveForConflict(name string) error {
|
||||
func (f *sendReceiveFolder) moveForConflict(name string, lastModBy string) error {
|
||||
if strings.Contains(filepath.Base(name), ".sync-conflict-") {
|
||||
l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")
|
||||
if err := os.Remove(name); err != nil && !os.IsNotExist(err) {
|
||||
@@ -1679,7 +1684,7 @@ func (f *sendReceiveFolder) moveForConflict(name string) error {
|
||||
|
||||
ext := filepath.Ext(name)
|
||||
withoutExt := name[:len(name)-len(ext)]
|
||||
newName := withoutExt + time.Now().Format(".sync-conflict-20060102-150405") + ext
|
||||
newName := withoutExt + time.Now().Format(".sync-conflict-20060102-150405-") + lastModBy + ext
|
||||
err := os.Rename(name, newName)
|
||||
if os.IsNotExist(err) {
|
||||
// We were supposed to move a file away but it does not exist. Either
|
||||
@@ -1689,7 +1694,7 @@ func (f *sendReceiveFolder) moveForConflict(name string) error {
|
||||
err = nil
|
||||
}
|
||||
if f.MaxConflicts > -1 {
|
||||
matches, gerr := osutil.Glob(withoutExt + ".sync-conflict-????????-??????" + ext)
|
||||
matches, gerr := osutil.Glob(withoutExt + ".sync-conflict-????????-??????*" + ext)
|
||||
if gerr == nil && len(matches) > f.MaxConflicts {
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(matches)))
|
||||
for _, match := range matches[f.MaxConflicts:] {
|
||||
|
||||
@@ -71,7 +71,7 @@ func setUpFile(filename string, blockNumbers []int) protocol.FileInfo {
|
||||
|
||||
func setUpModel(file protocol.FileInfo) *Model {
|
||||
db := db.OpenMemory()
|
||||
model := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
model := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
model.AddFolder(defaultFolderConfig)
|
||||
// Update index
|
||||
model.updateLocalsFromScanning("default", []protocol.FileInfo{file})
|
||||
@@ -476,7 +476,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
|
||||
db := db.OpenMemory()
|
||||
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
|
||||
f := setUpSendReceiveFolder(m)
|
||||
@@ -549,7 +549,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
|
||||
defer os.Remove("testdata/" + ignore.TempName("filex"))
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
|
||||
f := setUpSendReceiveFolder(m)
|
||||
|
||||
@@ -154,6 +154,7 @@ Returns the current configuration.
|
||||
"tempIndexMinBlocks": 10
|
||||
},
|
||||
"ignoredDevices": []
|
||||
"ignoredFolders": []
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
|
||||
Reference in New Issue
Block a user