Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99427d649e |
@@ -67,22 +67,28 @@ func main() {
|
|||||||
|
|
||||||
checkRequiredGoVersion()
|
checkRequiredGoVersion()
|
||||||
|
|
||||||
|
if check() != nil {
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
if flag.NArg() == 0 {
|
if flag.NArg() == 0 {
|
||||||
install("./cmd/...")
|
install("./cmd/...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cmd := range flag.Args() {
|
switch flag.Arg(0) {
|
||||||
switch cmd {
|
|
||||||
case "setup":
|
|
||||||
setup()
|
|
||||||
|
|
||||||
case "install":
|
case "install":
|
||||||
pkg := "./cmd/..."
|
pkg := "./cmd/..."
|
||||||
|
if flag.NArg() > 2 {
|
||||||
|
pkg = flag.Arg(1)
|
||||||
|
}
|
||||||
install(pkg)
|
install(pkg)
|
||||||
|
|
||||||
case "build":
|
case "build":
|
||||||
pkg := "./cmd/syncthing"
|
pkg := "./cmd/syncthing"
|
||||||
|
if flag.NArg() > 2 {
|
||||||
|
pkg = flag.Arg(1)
|
||||||
|
}
|
||||||
var tags []string
|
var tags []string
|
||||||
if noupgrade {
|
if noupgrade {
|
||||||
tags = []string{"noupgrade"}
|
tags = []string{"noupgrade"}
|
||||||
@@ -91,6 +97,9 @@ func main() {
|
|||||||
|
|
||||||
case "test":
|
case "test":
|
||||||
pkg := "./..."
|
pkg := "./..."
|
||||||
|
if flag.NArg() > 2 {
|
||||||
|
pkg = flag.Arg(1)
|
||||||
|
}
|
||||||
test(pkg)
|
test(pkg)
|
||||||
|
|
||||||
case "assets":
|
case "assets":
|
||||||
@@ -118,9 +127,13 @@ func main() {
|
|||||||
clean()
|
clean()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown command %q", cmd)
|
log.Fatalf("Unknown command %q", flag.Arg(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func check() error {
|
||||||
|
_, err := exec.LookPath("godep")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkRequiredGoVersion() {
|
func checkRequiredGoVersion() {
|
||||||
@@ -150,25 +163,24 @@ func setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test(pkg string) {
|
func test(pkg string) {
|
||||||
setBuildEnv()
|
runPrint("godep", "go", "test", "-short", "-timeout", "10s", pkg)
|
||||||
runPrint("go", "test", "-short", "-timeout", "10s", pkg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(pkg string) {
|
func install(pkg string) {
|
||||||
os.Setenv("GOBIN", "./bin")
|
os.Setenv("GOBIN", "./bin")
|
||||||
setBuildEnv()
|
setBuildEnv()
|
||||||
runPrint("go", "install", "-ldflags", ldflags(), pkg)
|
runPrint("godep", "go", "install", "-ldflags", ldflags(), pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(pkg string, tags []string) {
|
func build(pkg string, tags []string) {
|
||||||
rmr("syncthing", "syncthing.exe")
|
rmr("syncthing", "syncthing.exe")
|
||||||
args := []string{"build", "-ldflags", ldflags()}
|
args := []string{"go", "build", "-ldflags", ldflags()}
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
args = append(args, "-tags", strings.Join(tags, ","))
|
args = append(args, "-tags", strings.Join(tags, ","))
|
||||||
}
|
}
|
||||||
args = append(args, pkg)
|
args = append(args, pkg)
|
||||||
setBuildEnv()
|
setBuildEnv()
|
||||||
runPrint("go", args...)
|
runPrint("godep", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTar() {
|
func buildTar() {
|
||||||
@@ -218,18 +230,10 @@ func setBuildEnv() {
|
|||||||
if goarch == "386" {
|
if goarch == "386" {
|
||||||
os.Setenv("GO386", "387")
|
os.Setenv("GO386", "387")
|
||||||
}
|
}
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Warning: can't determine current dir:", err)
|
|
||||||
log.Println("Build might not work as expected")
|
|
||||||
}
|
|
||||||
os.Setenv("GOPATH", fmt.Sprintf("%s%c%s", filepath.Join(wd, "Godeps", "_workspace"), os.PathListSeparator, os.Getenv("GOPATH")))
|
|
||||||
log.Println("GOPATH=" + os.Getenv("GOPATH"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func assets() {
|
func assets() {
|
||||||
setBuildEnv()
|
runPipe("internal/auto/gui.files.go", "godep", "go", "run", "cmd/genassets/main.go", "gui")
|
||||||
runPipe("internal/auto/gui.files.go", "go", "run", "cmd/genassets/main.go", "gui")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func xdr() {
|
func xdr() {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ missing-contribs() {
|
|||||||
|
|
||||||
no-docs-typos() {
|
no-docs-typos() {
|
||||||
# Commits that are known to not change code
|
# Commits that are known to not change code
|
||||||
|
grep -v 63bd0136fb40a91efaa279cb4b4159d82e8e6904 |\
|
||||||
|
grep -v 4e2feb6fbc791bb8a2daf0ab8efb10775d66343e |\
|
||||||
grep -v f2459ef3319b2f060dbcdacd0c35a1788a94b8bd |\
|
grep -v f2459ef3319b2f060dbcdacd0c35a1788a94b8bd |\
|
||||||
grep -v b61f418bf2d1f7d5a9d7088a20a2a448e5e66801 |\
|
grep -v b61f418bf2d1f7d5a9d7088a20a2a448e5e66801 |\
|
||||||
grep -v f0621207e3953711f9ab86d99724f1d0faac45b1 |\
|
grep -v f0621207e3953711f9ab86d99724f1d0faac45b1 |\
|
||||||
|
|||||||
+1
-13
@@ -87,7 +87,6 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
|||||||
getRestMux.HandleFunc("/rest/ignores", withModel(m, restGetIgnores))
|
getRestMux.HandleFunc("/rest/ignores", withModel(m, restGetIgnores))
|
||||||
getRestMux.HandleFunc("/rest/lang", restGetLang)
|
getRestMux.HandleFunc("/rest/lang", restGetLang)
|
||||||
getRestMux.HandleFunc("/rest/model", withModel(m, restGetModel))
|
getRestMux.HandleFunc("/rest/model", withModel(m, restGetModel))
|
||||||
getRestMux.HandleFunc("/rest/model/version", withModel(m, restGetModelVersion))
|
|
||||||
getRestMux.HandleFunc("/rest/need", withModel(m, restGetNeed))
|
getRestMux.HandleFunc("/rest/need", withModel(m, restGetNeed))
|
||||||
getRestMux.HandleFunc("/rest/nodeid", restGetNodeID)
|
getRestMux.HandleFunc("/rest/nodeid", restGetNodeID)
|
||||||
getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport))
|
getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport))
|
||||||
@@ -238,17 +237,6 @@ func restGetCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(res)
|
json.NewEncoder(w).Encode(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restGetModelVersion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
|
||||||
var qs = r.URL.Query()
|
|
||||||
var repo = qs.Get("repo")
|
|
||||||
var res = make(map[string]interface{})
|
|
||||||
|
|
||||||
res["version"] = m.LocalVersion(repo)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
json.NewEncoder(w).Encode(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||||
var qs = r.URL.Query()
|
var qs = r.URL.Query()
|
||||||
var repo = qs.Get("repo")
|
var repo = qs.Get("repo")
|
||||||
@@ -273,7 +261,7 @@ func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
|||||||
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
|
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
|
||||||
|
|
||||||
res["state"], res["stateChanged"] = m.State(repo)
|
res["state"], res["stateChanged"] = m.State(repo)
|
||||||
res["version"] = m.LocalVersion(repo)
|
res["version"] = m.CurrentLocalVersion(repo) + m.RemoteLocalVersion(repo)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
json.NewEncoder(w).Encode(res)
|
json.NewEncoder(w).Encode(res)
|
||||||
|
|||||||
+5
-42
@@ -442,7 +442,7 @@ nextRepo:
|
|||||||
m.AddRepo(repo)
|
m.AddRepo(repo)
|
||||||
|
|
||||||
fi, err := os.Stat(repo.Directory)
|
fi, err := os.Stat(repo.Directory)
|
||||||
if m.LocalVersion(repo.ID) > 0 {
|
if m.CurrentLocalVersion(repo.ID) > 0 {
|
||||||
// Safety check. If the cached index contains files but the
|
// Safety check. If the cached index contains files but the
|
||||||
// repository doesn't exist, we have a problem. We would assume
|
// repository doesn't exist, we have a problem. We would assume
|
||||||
// that all files have been deleted which might not be the case,
|
// that all files have been deleted which might not be the case,
|
||||||
@@ -453,8 +453,8 @@ nextRepo:
|
|||||||
continue nextRepo
|
continue nextRepo
|
||||||
}
|
}
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
// If we don't have ny files in the index, and the directory does
|
// If we don't have any files in the index, and the directory
|
||||||
// exist, try creating it.
|
// doesn't exist, try creating it.
|
||||||
err = os.MkdirAll(repo.Directory, 0700)
|
err = os.MkdirAll(repo.Directory, 0700)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@ nextRepo:
|
|||||||
m.StartRepoRO(repo.ID)
|
m.StartRepoRO(repo.ID)
|
||||||
} else {
|
} else {
|
||||||
l.Okf("Ready to synchronize %s (read-write)", repo.ID)
|
l.Okf("Ready to synchronize %s (read-write)", repo.ID)
|
||||||
m.StartRepoRW(repo.ID, cfg.Options.ParallelRequests)
|
m.StartRepoRW(repo.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,10 +620,6 @@ nextRepo:
|
|||||||
go standbyMonitor()
|
go standbyMonitor()
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Options.AutoUpgradeIntervalH > 0 {
|
|
||||||
go autoUpgrade()
|
|
||||||
}
|
|
||||||
|
|
||||||
events.Default.Log(events.StartupComplete, nil)
|
events.Default.Log(events.StartupComplete, nil)
|
||||||
go generateEvents()
|
go generateEvents()
|
||||||
|
|
||||||
@@ -1163,7 +1159,7 @@ func standbyMonitor() {
|
|||||||
for {
|
for {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
if time.Since(now) > 2*time.Minute {
|
if time.Since(now) > 2*time.Minute {
|
||||||
l.Infoln("Paused state detected, possibly woke up from standby. Restarting in", restartDelay)
|
l.Infof("Paused state detected, possibly woke up from standby. Restarting in %v.", restartDelay)
|
||||||
|
|
||||||
// We most likely just woke from standby. If we restart
|
// We most likely just woke from standby. If we restart
|
||||||
// immediately chances are we won't have networking ready. Give
|
// immediately chances are we won't have networking ready. Give
|
||||||
@@ -1176,36 +1172,3 @@ func standbyMonitor() {
|
|||||||
now = time.Now()
|
now = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func autoUpgrade() {
|
|
||||||
var skipped bool
|
|
||||||
interval := time.Duration(cfg.Options.AutoUpgradeIntervalH) * time.Hour
|
|
||||||
for {
|
|
||||||
if skipped {
|
|
||||||
time.Sleep(interval)
|
|
||||||
} else {
|
|
||||||
skipped = true
|
|
||||||
}
|
|
||||||
|
|
||||||
rel, err := upgrade.LatestRelease(strings.Contains(Version, "-beta"))
|
|
||||||
if err != nil {
|
|
||||||
l.Warnln("Automatic upgrade:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if upgrade.CompareVersions(rel.Tag, Version) <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Infof("Automatic upgrade (current %q < latest %q)", Version, rel.Tag)
|
|
||||||
err = upgrade.UpgradeTo(rel, GoArchExtra)
|
|
||||||
if err != nil {
|
|
||||||
l.Warnln("Automatic upgrade:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
|
|
||||||
time.Sleep(time.Minute)
|
|
||||||
stop <- exitUpgrading
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -533,7 +533,6 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
|||||||
$scope.tmpOptions = angular.copy($scope.config.Options);
|
$scope.tmpOptions = angular.copy($scope.config.Options);
|
||||||
$scope.tmpOptions.UREnabled = ($scope.tmpOptions.URAccepted > 0);
|
$scope.tmpOptions.UREnabled = ($scope.tmpOptions.URAccepted > 0);
|
||||||
$scope.tmpOptions.NodeName = $scope.thisNode().Name;
|
$scope.tmpOptions.NodeName = $scope.thisNode().Name;
|
||||||
$scope.tmpOptions.AutoUpgradeEnabled = ($scope.tmpOptions.AutoUpgradeIntervalH > 0);
|
|
||||||
$scope.tmpGUI = angular.copy($scope.config.GUI);
|
$scope.tmpGUI = angular.copy($scope.config.GUI);
|
||||||
$('#settings').modal();
|
$('#settings').modal();
|
||||||
};
|
};
|
||||||
@@ -564,13 +563,6 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
|||||||
$scope.tmpOptions.URAccepted = -1;
|
$scope.tmpOptions.URAccepted = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if auto-upgrade has been enabled or disabled
|
|
||||||
if ($scope.tmpOptions.AutoUpgradeEnabled) {
|
|
||||||
$scope.tmpOptions.AutoUpgradeIntervalH = $scope.tmpOptions.AutoUpgradeIntervalH || 12;
|
|
||||||
} else {
|
|
||||||
$scope.tmpOptions.AutoUpgradeIntervalH = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if protocol will need to be changed on restart
|
// Check if protocol will need to be changed on restart
|
||||||
if ($scope.config.GUI.UseTLS !== $scope.tmpGUI.UseTLS) {
|
if ($scope.config.GUI.UseTLS !== $scope.tmpGUI.UseTLS) {
|
||||||
$scope.protocolChanged = true;
|
$scope.protocolChanged = true;
|
||||||
|
|||||||
+14
-24
@@ -383,13 +383,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label translate for="name">Node Name</label>
|
<label translate for="name">Node Name</label>
|
||||||
<input id="name" class="form-control" type="text" ng-model="currentNode.Name"></input>
|
<input placeholder="Home Server" id="name" class="form-control" type="text" ng-model="currentNode.Name"></input>
|
||||||
<p translate ng-if="currentNode.NodeID == myID" class="help-block">Shown instead of Node ID in the cluster status. Will be advertised to other nodes as an optional default name.</p>
|
<p translate ng-if="currentNode.NodeID == myID" class="help-block">Shown instead of Node ID in the cluster status. Will be advertised to other nodes as an optional default name.</p>
|
||||||
<p translate ng-if="currentNode.NodeID != myID" class="help-block">Shown instead of Node ID in the cluster status. Will be updated to the name the node advertises if left empty.</p>
|
<p translate ng-if="currentNode.NodeID != myID" class="help-block">Shown instead of Node ID in the cluster status. Will be updated to the name the node advertises if left empty.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label translate for="addresses">Addresses</label>
|
<label translate for="addresses">Addresses</label>
|
||||||
<input ng-disabled="currentNode.NodeID == myID" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
|
<input placeholder="dynamic" ng-disabled="currentNode.NodeID == myID" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
|
||||||
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p>
|
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="!editingSelf" class="form-group">
|
<div ng-if="!editingSelf" class="form-group">
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
|
<div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
|
||||||
<label for="repoID"><span translate>Repository ID</span></label>
|
<label for="repoID"><span translate>Repository ID</span></label>
|
||||||
<input name="repoID" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
|
<input name="repoID" placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
<span translate ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
|
<span translate ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
|
||||||
<span translate ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
|
<span translate ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
|
||||||
@@ -444,7 +444,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
|
<div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
|
||||||
<label translate for="repoPath">Repository Path</label>
|
<label translate for="repoPath">Repository Path</label>
|
||||||
<input name="repoPath" ng-disabled="editingExisting" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
|
<input name="repoPath" placeholder="~/Documents" ng-disabled="editingExisting" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
<span translate ng-if="repoEditor.repoPath.$valid || repoEditor.repoPath.$pristine">Path to the repository 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 translate ng-if="repoEditor.repoPath.$valid || repoEditor.repoPath.$pristine">Path to the repository 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 translate ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
|
<span translate ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
|
||||||
@@ -452,7 +452,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-class="{'has-error': repoEditor.rescanIntervalS.$invalid && repoEditor.rescanIntervalS.$dirty}">
|
<div class="form-group" ng-class="{'has-error': repoEditor.rescanIntervalS.$invalid && repoEditor.rescanIntervalS.$dirty}">
|
||||||
<label for="rescanIntervalS"><span translate>Rescan Interval</span> (s)</label>
|
<label for="rescanIntervalS"><span translate>Rescan Interval</span> (s)</label>
|
||||||
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentRepo.RescanIntervalS" required min="5"></input>
|
<input name="rescanIntervalS" placeholder="60" id="rescanIntervalS" class="form-control" type="number" ng-model="currentRepo.RescanIntervalS" required min="5"></input>
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
<span translate ng-if="!repoEditor.rescanIntervalS.$valid && repoEditor.rescanIntervalS.$dirty">The rescan interval must be at least 5 seconds.</span>
|
<span translate ng-if="!repoEditor.rescanIntervalS.$valid && repoEditor.rescanIntervalS.$dirty">The rescan interval must be at least 5 seconds.</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -528,7 +528,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-if="currentRepo.FileVersioningSelector == 'staggered'">
|
<div class="form-group" ng-if="currentRepo.FileVersioningSelector == 'staggered'">
|
||||||
<label translate for="staggeredVersionsPath">Versions Path</label>
|
<label translate for="staggeredVersionsPath">Versions Path</label>
|
||||||
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentRepo.staggeredVersionsPath"></input>
|
<input name="staggeredVersionsPath" placeholder="" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentRepo.staggeredVersionsPath"></input>
|
||||||
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the repository).</p>
|
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the repository).</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -606,7 +606,6 @@
|
|||||||
<label translate for="MaxSendKbps">Outgoing Rate Limit (KiB/s)</label>
|
<label translate for="MaxSendKbps">Outgoing Rate Limit (KiB/s)</label>
|
||||||
<input id="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.MaxSendKbps">
|
<input id="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.MaxSendKbps">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
@@ -614,6 +613,14 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<span translate>Local Discovery</span> <input id="LocalAnnEnabled" type="checkbox" ng-model="tmpOptions.LocalAnnEnabled">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
@@ -621,23 +628,6 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<span translate>Automatic upgrades</span> <input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.AutoUpgradeEnabled">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<span translate>Local Discovery</span> <input id="LocalAnnEnabled" type="checkbox" ng-model="tmpOptions.LocalAnnEnabled">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label translate for="GlobalAnnServer">Global Discovery Server</label>
|
<label translate for="GlobalAnnServer">Global Discovery Server</label>
|
||||||
<input ng-disabled="!tmpOptions.GlobalAnnEnabled" id="GlobalAnnServer" class="form-control" type="text" ng-model="tmpOptions.GlobalAnnServer">
|
<input ng-disabled="!tmpOptions.GlobalAnnEnabled" id="GlobalAnnServer" class="form-control" type="text" ng-model="tmpOptions.GlobalAnnServer">
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -118,7 +118,6 @@ type OptionsConfiguration struct {
|
|||||||
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true"`
|
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true"`
|
||||||
LocalAnnPort int `xml:"localAnnouncePort" default:"21025"`
|
LocalAnnPort int `xml:"localAnnouncePort" default:"21025"`
|
||||||
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
|
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
|
||||||
ParallelRequests int `xml:"parallelRequests" default:"16"`
|
|
||||||
MaxSendKbps int `xml:"maxSendKbps"`
|
MaxSendKbps int `xml:"maxSendKbps"`
|
||||||
MaxRecvKbps int `xml:"maxRecvKbps"`
|
MaxRecvKbps int `xml:"maxRecvKbps"`
|
||||||
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60"`
|
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60"`
|
||||||
@@ -128,7 +127,6 @@ type OptionsConfiguration struct {
|
|||||||
UPnPRenewal int `xml:"upnpRenewalMinutes" default:"30"`
|
UPnPRenewal int `xml:"upnpRenewalMinutes" default:"30"`
|
||||||
URAccepted int `xml:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
|
URAccepted int `xml:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
|
||||||
RestartOnWakeup bool `xml:"restartOnWakeup" default:"true"`
|
RestartOnWakeup bool `xml:"restartOnWakeup" default:"true"`
|
||||||
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" default:"12"` // 0 for off
|
|
||||||
|
|
||||||
Deprecated_RescanIntervalS int `xml:"rescanIntervalS,omitempty" json:"-"`
|
Deprecated_RescanIntervalS int `xml:"rescanIntervalS,omitempty" json:"-"`
|
||||||
Deprecated_UREnabled bool `xml:"urEnabled,omitempty" json:"-"`
|
Deprecated_UREnabled bool `xml:"urEnabled,omitempty" json:"-"`
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ func TestDefaultValues(t *testing.T) {
|
|||||||
LocalAnnEnabled: true,
|
LocalAnnEnabled: true,
|
||||||
LocalAnnPort: 21025,
|
LocalAnnPort: 21025,
|
||||||
LocalAnnMCAddr: "[ff32::5222]:21026",
|
LocalAnnMCAddr: "[ff32::5222]:21026",
|
||||||
ParallelRequests: 16,
|
|
||||||
MaxSendKbps: 0,
|
MaxSendKbps: 0,
|
||||||
MaxRecvKbps: 0,
|
MaxRecvKbps: 0,
|
||||||
ReconnectIntervalS: 60,
|
ReconnectIntervalS: 60,
|
||||||
@@ -38,7 +37,6 @@ func TestDefaultValues(t *testing.T) {
|
|||||||
UPnPLease: 0,
|
UPnPLease: 0,
|
||||||
UPnPRenewal: 30,
|
UPnPRenewal: 30,
|
||||||
RestartOnWakeup: true,
|
RestartOnWakeup: true,
|
||||||
AutoUpgradeIntervalH: 12,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := New("test", node1)
|
cfg := New("test", node1)
|
||||||
@@ -122,7 +120,6 @@ func TestOverriddenValues(t *testing.T) {
|
|||||||
LocalAnnEnabled: false,
|
LocalAnnEnabled: false,
|
||||||
LocalAnnPort: 42123,
|
LocalAnnPort: 42123,
|
||||||
LocalAnnMCAddr: "quux:3232",
|
LocalAnnMCAddr: "quux:3232",
|
||||||
ParallelRequests: 32,
|
|
||||||
MaxSendKbps: 1234,
|
MaxSendKbps: 1234,
|
||||||
MaxRecvKbps: 2341,
|
MaxRecvKbps: 2341,
|
||||||
ReconnectIntervalS: 6000,
|
ReconnectIntervalS: 6000,
|
||||||
@@ -131,7 +128,6 @@ func TestOverriddenValues(t *testing.T) {
|
|||||||
UPnPLease: 60,
|
UPnPLease: 60,
|
||||||
UPnPRenewal: 15,
|
UPnPRenewal: 15,
|
||||||
RestartOnWakeup: false,
|
RestartOnWakeup: false,
|
||||||
AutoUpgradeIntervalH: 24,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := Load("testdata/overridenvalues.xml", node1)
|
cfg, err := Load("testdata/overridenvalues.xml", node1)
|
||||||
|
|||||||
@@ -16,6 +16,5 @@
|
|||||||
<upnpLeaseMinutes>60</upnpLeaseMinutes>
|
<upnpLeaseMinutes>60</upnpLeaseMinutes>
|
||||||
<upnpRenewalMinutes>15</upnpRenewalMinutes>
|
<upnpRenewalMinutes>15</upnpRenewalMinutes>
|
||||||
<restartOnWakeup>false</restartOnWakeup>
|
<restartOnWakeup>false</restartOnWakeup>
|
||||||
<autoUpgradeIntervalH>24</autoUpgradeIntervalH>
|
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
+89
-27
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/syncthing/syncthing/internal/protocol"
|
"github.com/syncthing/syncthing/internal/protocol"
|
||||||
"github.com/syncthing/syncthing/internal/scanner"
|
"github.com/syncthing/syncthing/internal/scanner"
|
||||||
"github.com/syncthing/syncthing/internal/stats"
|
"github.com/syncthing/syncthing/internal/stats"
|
||||||
|
"github.com/syncthing/syncthing/internal/versioner"
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,22 +139,54 @@ func NewModel(indexDir string, cfg *config.Configuration, nodeName, clientName,
|
|||||||
// StartRW starts read/write processing on the current model. When in
|
// StartRW 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
|
// read/write mode the model will attempt to keep in sync with the cluster by
|
||||||
// pulling needed files from peer nodes.
|
// pulling needed files from peer nodes.
|
||||||
func (m *Model) StartRepoRW(repo string, threads int) {
|
func (m *Model) StartRepoRW(repo string) {
|
||||||
m.rmut.RLock()
|
m.rmut.Lock()
|
||||||
defer m.rmut.RUnlock()
|
cfg, ok := m.repoCfgs[repo]
|
||||||
|
m.rmut.Unlock()
|
||||||
|
|
||||||
if cfg, ok := m.repoCfgs[repo]; !ok {
|
if !ok {
|
||||||
panic("cannot start without repo")
|
panic("cannot start nonexistent repo " + repo)
|
||||||
} else {
|
|
||||||
newPuller(cfg, m, threads, m.cfg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p := Puller{
|
||||||
|
repo: repo,
|
||||||
|
dir: cfg.Directory,
|
||||||
|
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
|
||||||
|
model: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Versioning.Type) > 0 {
|
||||||
|
factory, ok := versioner.Factories[cfg.Versioning.Type]
|
||||||
|
if !ok {
|
||||||
|
l.Fatalf("Requested versioning type %q that does not exist", cfg.Versioning.Type)
|
||||||
|
}
|
||||||
|
p.versioner = factory(repo, cfg.Directory, cfg.Versioning.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.Serve()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartRO starts read only processing on the current model. When in
|
// StartRO starts read only processing on the current model. When in
|
||||||
// read only mode the model will announce files to the cluster but not
|
// read only mode the model will announce files to the cluster but not
|
||||||
// pull in any external changes.
|
// pull in any external changes.
|
||||||
func (m *Model) StartRepoRO(repo string) {
|
func (m *Model) StartRepoRO(repo string) {
|
||||||
m.StartRepoRW(repo, 0) // zero threads => read only
|
intv := time.Duration(m.repoCfgs[repo].RescanIntervalS) * time.Second
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(intv)
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
l.Debugln(m, "rescan", repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.setState(repo, RepoScanning)
|
||||||
|
if err := m.ScanRepo(repo); err != nil {
|
||||||
|
invalidateRepo(m.cfg, repo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.setState(repo, RepoIdle)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionInfo struct {
|
type ConnectionInfo struct {
|
||||||
@@ -240,7 +273,7 @@ func (m *Model) Completion(node protocol.NodeID, repo string) float64 {
|
|||||||
|
|
||||||
res := 100 * (1 - float64(need)/float64(tot))
|
res := 100 * (1 - float64(need)/float64(tot))
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("Completion(%s, %q): %f (%d / %d)", node, repo, res, need, tot)
|
l.Debugf("%v Completion(%s, %q): %f (%d / %d)", m, node, repo, res, need, tot)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -316,7 +349,7 @@ func (m *Model) NeedSize(repo string) (files int, bytes int64) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("NeedSize(%q): %d %d", repo, files, bytes)
|
l.Debugf("%v NeedSize(%q): %d %d", m, repo, files, bytes)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -389,7 +422,7 @@ func (m *Model) Index(nodeID protocol.NodeID, repo string, fs []protocol.FileInf
|
|||||||
// Implements the protocol.Model interface.
|
// Implements the protocol.Model interface.
|
||||||
func (m *Model) IndexUpdate(nodeID protocol.NodeID, repo string, fs []protocol.FileInfo) {
|
func (m *Model) IndexUpdate(nodeID protocol.NodeID, repo string, fs []protocol.FileInfo) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("IDXUP(in): %s / %q: %d files", nodeID, repo, len(fs))
|
l.Debugf("%v IDXUP(in): %s / %q: %d files", m, nodeID, repo, len(fs))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.repoSharedWith(repo, nodeID) {
|
if !m.repoSharedWith(repo, nodeID) {
|
||||||
@@ -574,20 +607,20 @@ func (m *Model) Request(nodeID protocol.NodeID, repo, name string, offset int64,
|
|||||||
lf := r.Get(protocol.LocalNodeID, name)
|
lf := r.Get(protocol.LocalNodeID, name)
|
||||||
if protocol.IsInvalid(lf.Flags) || protocol.IsDeleted(lf.Flags) {
|
if protocol.IsInvalid(lf.Flags) || protocol.IsDeleted(lf.Flags) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("REQ(in): %s: %q / %q o=%d s=%d; invalid: %v", nodeID, repo, name, offset, size, lf)
|
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d; invalid: %v", m, nodeID, repo, name, offset, size, lf)
|
||||||
}
|
}
|
||||||
return nil, ErrInvalid
|
return nil, ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset > lf.Size() {
|
if offset > lf.Size() {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("REQ(in; nonexistent): %s: %q o=%d s=%d", nodeID, name, offset, size)
|
l.Debugf("%v REQ(in; nonexistent): %s: %q o=%d s=%d", m, nodeID, name, offset, size)
|
||||||
}
|
}
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchFile
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug && nodeID != protocol.LocalNodeID {
|
if debug && nodeID != protocol.LocalNodeID {
|
||||||
l.Debugf("REQ(in): %s: %q / %q o=%d s=%d", nodeID, repo, name, offset, size)
|
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d", m, nodeID, repo, name, offset, size)
|
||||||
}
|
}
|
||||||
m.rmut.RLock()
|
m.rmut.RLock()
|
||||||
fn := filepath.Join(m.repoCfgs[repo].Directory, name)
|
fn := filepath.Join(m.repoCfgs[repo].Directory, name)
|
||||||
@@ -768,15 +801,9 @@ func sendIndexes(conn protocol.Connection, repo string, fs *files.Set, ignores i
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("sendIndexes for %s-%s@/%q starting", nodeID, name, repo)
|
l.Debugf("sendIndexes for %s-%s/%q starting", nodeID, name, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if debug {
|
|
||||||
l.Debugf("sendIndexes for %s-%s@/%q exiting: %v", nodeID, name, repo, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
minLocalVer, err := sendIndexTo(true, 0, conn, repo, fs, ignores)
|
minLocalVer, err := sendIndexTo(true, 0, conn, repo, fs, ignores)
|
||||||
|
|
||||||
for err == nil {
|
for err == nil {
|
||||||
@@ -787,6 +814,10 @@ func sendIndexes(conn protocol.Connection, repo string, fs *files.Set, ignores i
|
|||||||
|
|
||||||
minLocalVer, err = sendIndexTo(false, minLocalVer, conn, repo, fs, ignores)
|
minLocalVer, err = sendIndexTo(false, minLocalVer, conn, repo, fs, ignores)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
l.Debugf("sendIndexes for %s-%s/%q exiting: %v", nodeID, name, repo, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendIndexTo(initial bool, minLocalVer uint64, conn protocol.Connection, repo string, fs *files.Set, ignores ignore.Patterns) (uint64, error) {
|
func sendIndexTo(initial bool, minLocalVer uint64, conn protocol.Connection, repo string, fs *files.Set, ignores ignore.Patterns) (uint64, error) {
|
||||||
@@ -877,7 +908,7 @@ func (m *Model) requestGlobal(nodeID protocol.NodeID, repo, name string, offset
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("REQ(out): %s: %q / %q o=%d s=%d h=%x", nodeID, repo, name, offset, size, hash)
|
l.Debugf("%v REQ(out): %s: %q / %q o=%d s=%d h=%x", m, nodeID, repo, name, offset, size, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nc.Request(repo, name, offset, size)
|
return nc.Request(repo, name, offset, size)
|
||||||
@@ -1175,10 +1206,10 @@ func (m *Model) Override(repo string) {
|
|||||||
m.setState(repo, RepoIdle)
|
m.setState(repo, RepoIdle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version returns the change version for the given repository. This is
|
// CurrentLocalVersion returns the change version for the given repository.
|
||||||
// guaranteed to increment if the contents of the local or global repository
|
// This is guaranteed to increment if the contents of the local repository has
|
||||||
// has changed.
|
// changed.
|
||||||
func (m *Model) LocalVersion(repo string) uint64 {
|
func (m *Model) CurrentLocalVersion(repo string) uint64 {
|
||||||
m.rmut.Lock()
|
m.rmut.Lock()
|
||||||
defer m.rmut.Unlock()
|
defer m.rmut.Unlock()
|
||||||
|
|
||||||
@@ -1187,10 +1218,41 @@ func (m *Model) LocalVersion(repo string) uint64 {
|
|||||||
panic("bug: LocalVersion called for nonexistent repo " + repo)
|
panic("bug: LocalVersion called for nonexistent repo " + repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
ver := fs.LocalVersion(protocol.LocalNodeID)
|
return fs.LocalVersion(protocol.LocalNodeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteLocalVersion returns the change version for the given repository, as
|
||||||
|
// sent by remote peers. This is guaranteed to increment if the contents of
|
||||||
|
// the remote or global repository has changed.
|
||||||
|
func (m *Model) RemoteLocalVersion(repo string) uint64 {
|
||||||
|
m.rmut.Lock()
|
||||||
|
defer m.rmut.Unlock()
|
||||||
|
|
||||||
|
fs, ok := m.repoFiles[repo]
|
||||||
|
if !ok {
|
||||||
|
panic("bug: LocalVersion called for nonexistent repo " + repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ver uint64
|
||||||
for _, n := range m.repoNodes[repo] {
|
for _, n := range m.repoNodes[repo] {
|
||||||
ver += fs.LocalVersion(n)
|
ver += fs.LocalVersion(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ver
|
return ver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) availability(repo string, file string) []protocol.NodeID {
|
||||||
|
m.rmut.Lock()
|
||||||
|
defer m.rmut.Unlock()
|
||||||
|
|
||||||
|
fs, ok := m.repoFiles[repo]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.Availability(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) String() string {
|
||||||
|
return fmt.Sprintf("model@%p", m)
|
||||||
|
}
|
||||||
|
|||||||
@@ -241,25 +241,6 @@ func BenchmarkRequest(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActivityMap(t *testing.T) {
|
|
||||||
isValid := func(protocol.NodeID) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
m := make(activityMap)
|
|
||||||
if node := m.leastBusyNode([]protocol.NodeID{node1}, isValid); node != node1 {
|
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
|
||||||
}
|
|
||||||
if node := m.leastBusyNode([]protocol.NodeID{node2}, isValid); node != node2 {
|
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
|
||||||
}
|
|
||||||
if node := m.leastBusyNode([]protocol.NodeID{node1, node2}, isValid); node != node1 {
|
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
|
||||||
}
|
|
||||||
if node := m.leastBusyNode([]protocol.NodeID{node1, node2}, isValid); node != node2 {
|
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeRename(t *testing.T) {
|
func TestNodeRename(t *testing.T) {
|
||||||
ccm := protocol.ClusterConfigMessage{
|
ccm := protocol.ClusterConfigMessage{
|
||||||
ClientName: "syncthing",
|
ClientName: "syncthing",
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/internal/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodeActivity tracks the number of outstanding requests per node and can
|
||||||
|
// answer which node is least busy. It is safe for use from multiple
|
||||||
|
// goroutines.
|
||||||
|
type nodeActivity struct {
|
||||||
|
act map[protocol.NodeID]int
|
||||||
|
mut sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNodeActivity() *nodeActivity {
|
||||||
|
return &nodeActivity{
|
||||||
|
act: make(map[protocol.NodeID]int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nodeActivity) leastBusy(availability []protocol.NodeID) protocol.NodeID {
|
||||||
|
m.mut.Lock()
|
||||||
|
var low int = 2<<30 - 1
|
||||||
|
var selected protocol.NodeID
|
||||||
|
for _, node := range availability {
|
||||||
|
if usage := m.act[node]; usage < low {
|
||||||
|
low = usage
|
||||||
|
selected = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mut.Unlock()
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nodeActivity) using(node protocol.NodeID) {
|
||||||
|
m.mut.Lock()
|
||||||
|
defer m.mut.Unlock()
|
||||||
|
m.act[node]++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nodeActivity) done(node protocol.NodeID) {
|
||||||
|
m.mut.Lock()
|
||||||
|
defer m.mut.Unlock()
|
||||||
|
m.act[node]--
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/internal/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNodeActivity(t *testing.T) {
|
||||||
|
n0 := protocol.NodeID{1, 2, 3, 4}
|
||||||
|
n1 := protocol.NodeID{5, 6, 7, 8}
|
||||||
|
n2 := protocol.NodeID{9, 10, 11, 12}
|
||||||
|
nodes := []protocol.NodeID{n0, n1, n2}
|
||||||
|
na := newNodeActivity()
|
||||||
|
|
||||||
|
if lb := na.leastBusy(nodes); lb != n0 {
|
||||||
|
t.Errorf("Least busy node should be n0 (%v) not %v", n0, lb)
|
||||||
|
}
|
||||||
|
if lb := na.leastBusy(nodes); lb != n0 {
|
||||||
|
t.Errorf("Least busy node should still be n0 (%v) not %v", n0, lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
na.using(na.leastBusy(nodes))
|
||||||
|
if lb := na.leastBusy(nodes); lb != n1 {
|
||||||
|
t.Errorf("Least busy node should be n1 (%v) not %v", n1, lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
na.using(na.leastBusy(nodes))
|
||||||
|
if lb := na.leastBusy(nodes); lb != n2 {
|
||||||
|
t.Errorf("Least busy node should be n2 (%v) not %v", n2, lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
na.using(na.leastBusy(nodes))
|
||||||
|
if lb := na.leastBusy(nodes); lb != n0 {
|
||||||
|
t.Errorf("Least busy node should be n0 (%v) not %v", n0, lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
na.done(n1)
|
||||||
|
if lb := na.leastBusy(nodes); lb != n1 {
|
||||||
|
t.Errorf("Least busy node should be n1 (%v) not %v", n1, lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
na.done(n2)
|
||||||
|
if lb := na.leastBusy(nodes); lb != n1 {
|
||||||
|
t.Errorf("Least busy node should still be n1 (%v) not %v", n1, lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
na.done(n0)
|
||||||
|
if lb := na.leastBusy(nodes); lb != n0 {
|
||||||
|
t.Errorf("Least busy node should be n0 (%v) not %v", n0, lb)
|
||||||
|
}
|
||||||
|
}
|
||||||
+510
-749
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/internal/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A sharedPullerState is kept for each file that is being synced and is kept
|
||||||
|
// updated along the way.
|
||||||
|
type sharedPullerState struct {
|
||||||
|
// Immutable, does not require locking
|
||||||
|
file protocol.FileInfo
|
||||||
|
repo string
|
||||||
|
tempName string
|
||||||
|
realName string
|
||||||
|
|
||||||
|
// Mutable, must be locked for access
|
||||||
|
err error // The first error we hit
|
||||||
|
fd *os.File // The fd of the temp file
|
||||||
|
copyNeeded int // Number of copy actions we expect to happen
|
||||||
|
pullNeeded int // Number of block pulls we expect to happen
|
||||||
|
closed bool // Set when the file has been closed
|
||||||
|
mut sync.Mutex // Protects the above
|
||||||
|
}
|
||||||
|
|
||||||
|
// tempFile returns the fd for the temporary file, reusing an open fd
|
||||||
|
// or creating the file as necessary.
|
||||||
|
func (s *sharedPullerState) tempFile() (*os.File, error) {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
// If we've already hit an error, return early
|
||||||
|
if s.err != nil {
|
||||||
|
return nil, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the temp file is already open, return the file descriptor
|
||||||
|
if s.fd != nil {
|
||||||
|
return s.fd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the parent directory exists or can be created
|
||||||
|
dir := filepath.Dir(s.tempName)
|
||||||
|
if info, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(dir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
s.earlyCloseLocked("dst mkdir", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
s.earlyCloseLocked("dst stat dir", err)
|
||||||
|
return nil, err
|
||||||
|
} else if !info.IsDir() {
|
||||||
|
err = fmt.Errorf("%q: not a directory", dir)
|
||||||
|
s.earlyCloseLocked("dst mkdir", err)
|
||||||
|
return nil, err
|
||||||
|
} else if info.Mode()&04 == 0 {
|
||||||
|
err := os.Chmod(dir, 0755)
|
||||||
|
if err == nil {
|
||||||
|
defer func() {
|
||||||
|
err := os.Chmod(dir, info.Mode().Perm())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to create the temp file
|
||||||
|
fd, err := os.OpenFile(s.tempName, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
|
||||||
|
if err != nil {
|
||||||
|
s.earlyCloseLocked("dst create", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same fd will be used by all writers
|
||||||
|
s.fd = fd
|
||||||
|
|
||||||
|
return fd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceFile opens the existing source file for reading
|
||||||
|
func (s *sharedPullerState) sourceFile() (*os.File, error) {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
// If we've already hit an error, return early
|
||||||
|
if s.err != nil {
|
||||||
|
return nil, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to open the existing file
|
||||||
|
fd, err := os.Open(s.realName)
|
||||||
|
if err != nil {
|
||||||
|
s.earlyCloseLocked("src open", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// earlyClose prints a warning message composed of the context and
|
||||||
|
// error, and marks the sharedPullerState as failed. Is a no-op when called on
|
||||||
|
// an already failed state.
|
||||||
|
func (s *sharedPullerState) earlyClose(context string, err error) {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
s.earlyCloseLocked(context, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sharedPullerState) earlyCloseLocked(context string, err error) {
|
||||||
|
if s.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infof("Puller (repo %q, file %q): %s: %v", s.repo, s.file.Name, context, err)
|
||||||
|
s.err = err
|
||||||
|
if s.fd != nil {
|
||||||
|
s.fd.Close()
|
||||||
|
os.Remove(s.tempName)
|
||||||
|
}
|
||||||
|
s.closed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sharedPullerState) failed() error {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sharedPullerState) copyDone() {
|
||||||
|
s.mut.Lock()
|
||||||
|
s.copyNeeded--
|
||||||
|
if debug {
|
||||||
|
l.Debugln("sharedPullerState", s.repo, s.file.Name, "copyNeeded ->", s.pullNeeded)
|
||||||
|
}
|
||||||
|
s.mut.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sharedPullerState) pullDone() {
|
||||||
|
s.mut.Lock()
|
||||||
|
s.pullNeeded--
|
||||||
|
if debug {
|
||||||
|
l.Debugln("sharedPullerState", s.repo, s.file.Name, "pullNeeded ->", s.pullNeeded)
|
||||||
|
}
|
||||||
|
s.mut.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalClose atomically closes and returns closed status of a file. A true
|
||||||
|
// first return value means the file was closed and should be finished, with
|
||||||
|
// the error indicating the success or failure of the close. A false first
|
||||||
|
// return value indicates the file is not ready to be closed, or is already
|
||||||
|
// closed and should in either case not be finished off now.
|
||||||
|
func (s *sharedPullerState) finalClose() (bool, error) {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
if s.pullNeeded+s.copyNeeded != 0 {
|
||||||
|
// Not done yet.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if s.closed {
|
||||||
|
// Already handled.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.closed = true
|
||||||
|
if fd := s.fd; fd != nil {
|
||||||
|
s.fd = nil
|
||||||
|
return true, fd.Close()
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSourceFileOK(t *testing.T) {
|
||||||
|
s := sharedPullerState{
|
||||||
|
realName: "testdata/foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := s.sourceFile()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if fd == nil {
|
||||||
|
t.Fatal("Unexpected nil fd")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := make([]byte, 6)
|
||||||
|
n, err := fd.Read(bs)
|
||||||
|
|
||||||
|
if n != len(bs) {
|
||||||
|
t.Fatal("Wrong read length %d != %d", n, len(bs))
|
||||||
|
}
|
||||||
|
if string(bs) != "foobar" {
|
||||||
|
t.Fatal("Wrong contents %s != foobar", bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.failed(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSourceFileBad(t *testing.T) {
|
||||||
|
s := sharedPullerState{
|
||||||
|
realName: "nonexistent",
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := s.sourceFile()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Unexpected nil error")
|
||||||
|
}
|
||||||
|
if fd != nil {
|
||||||
|
t.Fatal("Unexpected non-nil fd")
|
||||||
|
}
|
||||||
|
if err := s.failed(); err == nil {
|
||||||
|
t.Fatal("Unexpected nil failed()")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ package scanner
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/protocol"
|
"github.com/syncthing/syncthing/internal/protocol"
|
||||||
@@ -88,3 +89,32 @@ func BlockDiff(src, tgt []protocol.BlockInfo) (have, need []protocol.BlockInfo)
|
|||||||
|
|
||||||
return have, need
|
return have, need
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify returns nil or an error describing the mismatch between the block
|
||||||
|
// list and actual reader contents
|
||||||
|
func Verify(r io.Reader, blocksize int, blocks []protocol.BlockInfo) error {
|
||||||
|
hf := sha256.New()
|
||||||
|
for i, block := range blocks {
|
||||||
|
lr := &io.LimitedReader{R: r, N: int64(blocksize)}
|
||||||
|
_, err := io.Copy(hf, lr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := hf.Sum(nil)
|
||||||
|
hf.Reset()
|
||||||
|
|
||||||
|
if bytes.Compare(hash, block.Hash) != 0 {
|
||||||
|
return fmt.Errorf("hash mismatch %x != %x for block %d", hash, block.Hash, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have reached the end now
|
||||||
|
bs := make([]byte, 1)
|
||||||
|
n, err := r.Read(bs)
|
||||||
|
if n != 0 || err != io.EOF {
|
||||||
|
return fmt.Errorf("file continues past end of blocks")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,6 +133,50 @@ func TestWalkError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerify(t *testing.T) {
|
||||||
|
blocksize := 16
|
||||||
|
// data should be an even multiple of blocksize long
|
||||||
|
data := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut e")
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
|
||||||
|
blocks, err := Blocks(buf, blocksize, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if exp := len(data) / blocksize; len(blocks) != exp {
|
||||||
|
t.Fatalf("Incorrect number of blocks %d != %d", len(blocks), exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = bytes.NewBuffer(data)
|
||||||
|
err = Verify(buf, blocksize, blocks)
|
||||||
|
t.Log(err)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unexpected verify failure", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = bytes.NewBuffer(append(data, '\n'))
|
||||||
|
err = Verify(buf, blocksize, blocks)
|
||||||
|
t.Log(err)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Unexpected verify success")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = bytes.NewBuffer(data[:len(data)-1])
|
||||||
|
err = Verify(buf, blocksize, blocks)
|
||||||
|
t.Log(err)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Unexpected verify success")
|
||||||
|
}
|
||||||
|
|
||||||
|
data[42] = 42
|
||||||
|
buf = bytes.NewBuffer(data)
|
||||||
|
err = Verify(buf, blocksize, blocks)
|
||||||
|
t.Log(err)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Unexpected verify success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fileList []protocol.FileInfo
|
type fileList []protocol.FileInfo
|
||||||
|
|
||||||
func (f fileList) Len() int {
|
func (f fileList) Len() int {
|
||||||
|
|||||||
@@ -26,29 +26,8 @@ var (
|
|||||||
ErrVersionUpToDate = errors.New("current version is up to date")
|
ErrVersionUpToDate = errors.New("current version is up to date")
|
||||||
ErrVersionUnknown = errors.New("couldn't fetch release information")
|
ErrVersionUnknown = errors.New("couldn't fetch release information")
|
||||||
ErrUpgradeUnsupported = errors.New("upgrade unsupported")
|
ErrUpgradeUnsupported = errors.New("upgrade unsupported")
|
||||||
ErrUpgradeInProgress = errors.New("upgrade already in progress")
|
|
||||||
upgradeUnlocked = make(chan bool, 1)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
upgradeUnlocked <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
// A wrapper around actual implementations
|
|
||||||
func UpgradeTo(rel Release, archExtra string) error {
|
|
||||||
select {
|
|
||||||
case <-upgradeUnlocked:
|
|
||||||
err := upgradeTo(rel, archExtra)
|
|
||||||
// If we've failed to upgrade, unlock so that another attempt could be made
|
|
||||||
if err != nil {
|
|
||||||
upgradeUnlocked <- true
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return ErrUpgradeInProgress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns 1 if a>b, -1 if a<b and 0 if they are equal
|
// Returns 1 if a>b, -1 if a<b and 0 if they are equal
|
||||||
func CompareVersions(a, b string) int {
|
func CompareVersions(a, b string) int {
|
||||||
arel, apre := versionParts(a)
|
arel, apre := versionParts(a)
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Upgrade to the given release, saving the previous binary with a ".old" extension.
|
// Upgrade to the given release, saving the previous binary with a ".old" extension.
|
||||||
func upgradeTo(rel Release, archExtra string) error {
|
func UpgradeTo(rel Release, archExtra string) error {
|
||||||
path, err := osext.Executable()
|
path, err := osext.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
osName := runtime.GOOS
|
osName := runtime.GOOS
|
||||||
if osName == "darwin" {
|
if osName == "darwin" {
|
||||||
// We call the darwin release bundles macosx because that makes more
|
// We call the darwin release bundles macosx because that makes more
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package upgrade
|
package upgrade
|
||||||
|
|
||||||
func upgradeTo(rel Release, extra string) error {
|
func UpgradeTo(rel Release, extra string) error {
|
||||||
return ErrUpgradeUnsupported
|
return ErrUpgradeUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Upgrade to the given release, saving the previous binary with a ".old" extension.
|
// Upgrade to the given release, saving the previous binary with a ".old" extension.
|
||||||
func upgradeTo(rel Release, archExtra string) error {
|
func UpgradeTo(rel Release, archExtra string) error {
|
||||||
path, err := osext.Executable()
|
path, err := osext.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedRelease := fmt.Sprintf("syncthing-%s-%s%s-%s.", runtime.GOOS, runtime.GOARCH, archExtra, rel.Tag)
|
expectedRelease := fmt.Sprintf("syncthing-%s-%s%s-%s.", runtime.GOOS, runtime.GOARCH, archExtra, rel.Tag)
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("expected release asset %q", expectedRelease)
|
l.Debugf("expected release asset %q", expectedRelease)
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
|
|
||||||
go test -tags integration -v -short
|
go test -tags integration -v
|
||||||
./test-http.sh
|
./test-http.sh
|
||||||
./test-merge.sh
|
./test-merge.sh
|
||||||
./test-delupd.sh
|
./test-delupd.sh
|
||||||
|
|||||||
+4
-8
@@ -17,6 +17,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(42)
|
||||||
|
}
|
||||||
|
|
||||||
func ReadRand(bs []byte) (int, error) {
|
func ReadRand(bs []byte) (int, error) {
|
||||||
var r uint32
|
var r uint32
|
||||||
for i := range bs {
|
for i := range bs {
|
||||||
@@ -38,20 +42,12 @@ func main() {
|
|||||||
var files int
|
var files int
|
||||||
var maxexp int
|
var maxexp int
|
||||||
var srcname string
|
var srcname string
|
||||||
var random bool
|
|
||||||
|
|
||||||
flag.IntVar(&files, "files", 1000, "Number of files")
|
flag.IntVar(&files, "files", 1000, "Number of files")
|
||||||
flag.IntVar(&maxexp, "maxexp", 20, "Maximum file size (max = 2^n + 128*1024 B)")
|
flag.IntVar(&maxexp, "maxexp", 20, "Maximum file size (max = 2^n + 128*1024 B)")
|
||||||
flag.StringVar(&srcname, "src", "/usr/share/dict/words", "Source material")
|
flag.StringVar(&srcname, "src", "/usr/share/dict/words", "Source material")
|
||||||
flag.BoolVar(&random, "random", true, "When false, always generate the same set of file")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if random {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
} else {
|
|
||||||
rand.Seed(42)
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := os.Open(srcname)
|
fd, err := os.Open(srcname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ var jsonEndpoints = []string{
|
|||||||
"/rest/errors",
|
"/rest/errors",
|
||||||
"/rest/events",
|
"/rest/events",
|
||||||
"/rest/lang",
|
"/rest/lang",
|
||||||
"/rest/model/version?repo=default",
|
|
||||||
"/rest/model?repo=default",
|
"/rest/model?repo=default",
|
||||||
"/rest/need",
|
"/rest/need",
|
||||||
"/rest/nodeid?id=I6KAH7666SLLLB5PFXSOAUFJCDZCYAOMLEKCP2GB32BV5RQST3PSROAU",
|
"/rest/nodeid?id=I6KAH7666SLLLB5PFXSOAUFJCDZCYAOMLEKCP2GB32BV5RQST3PSROAU",
|
||||||
|
|||||||
+4
-4
@@ -19,7 +19,7 @@ go build json.go
|
|||||||
start() {
|
start() {
|
||||||
echo "Starting..."
|
echo "Starting..."
|
||||||
for i in 1 2 3 ; do
|
for i in 1 2 3 ; do
|
||||||
STTRACE=files,model,puller,versioner,protocol STPROFILER=":909$i" syncthing -home "h$i" > "$i.out" 2>&1 &
|
STTRACE=model,scanner STPROFILER=":909$i" syncthing -home "h$i" > "$i.out" 2>&1 &
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ alterFiles() {
|
|||||||
echo " $i: deleting $todelete files..."
|
echo " $i: deleting $todelete files..."
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
find . -type f \
|
find . -type f \
|
||||||
| grep -v large \
|
| grep -v timechanged \
|
||||||
| sort -k 1.16 \
|
| sort -k 1.16 \
|
||||||
| head -n "$todelete" \
|
| head -n "$todelete" \
|
||||||
| xargs rm -f
|
| xargs rm -f
|
||||||
@@ -110,11 +110,10 @@ alterFiles() {
|
|||||||
# Create some new files and alter existing ones
|
# Create some new files and alter existing ones
|
||||||
echo " $i: random nonoverlapping"
|
echo " $i: random nonoverlapping"
|
||||||
../genfiles -maxexp 22 -files 200
|
../genfiles -maxexp 22 -files 200
|
||||||
echo " $i: append to large file"
|
|
||||||
dd if=large-$i bs=1024k count=4 >> large-$i 2>/dev/null
|
|
||||||
echo " $i: new files in ro directory"
|
echo " $i: new files in ro directory"
|
||||||
uuidgen > ro-test/$(uuidgen)
|
uuidgen > ro-test/$(uuidgen)
|
||||||
chmod 500 ro-test
|
chmod 500 ro-test
|
||||||
|
touch "timechanged-$i"
|
||||||
|
|
||||||
../md5r -l | sort | grep -v .stversions > ../md5-$i
|
../md5r -l | sort | grep -v .stversions > ../md5-$i
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
@@ -140,6 +139,7 @@ for i in 1 12-2 23-3; do
|
|||||||
mkdir ro-test
|
mkdir ro-test
|
||||||
uuidgen > ro-test/$(uuidgen)
|
uuidgen > ro-test/$(uuidgen)
|
||||||
chmod 500 ro-test
|
chmod 500 ro-test
|
||||||
|
dd if=/dev/urandom of="timechanged-$i" bs=1024k count=1
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestBenchmarkTransfer(t *testing.T) {
|
func TestBenchmarkTransfer(t *testing.T) {
|
||||||
nfiles := 10000
|
|
||||||
if testing.Short() {
|
|
||||||
nfiles = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Cleaning...")
|
log.Println("Cleaning...")
|
||||||
err := removeAll("s1", "s2", "h1/index", "h2/index")
|
err := removeAll("s1", "s2", "h1/index", "h2/index")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -26,7 +21,7 @@ func TestBenchmarkTransfer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Generating files...")
|
log.Println("Generating files...")
|
||||||
err = generateFiles("s1", nfiles, 22, "../bin/syncthing")
|
err = generateFiles("s1", 10000, 22, "../bin/syncthing")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user