mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-05 12:29:14 -05:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a203b8d83 | ||
|
|
feecdcc7a4 | ||
|
|
51ad533be6 | ||
|
|
29da0bc8f5 | ||
|
|
bccf7fc2a8 | ||
|
|
7b6b5981c4 | ||
|
|
9463192224 | ||
|
|
d1689f0012 | ||
|
|
8ed67fe349 | ||
|
|
90a1d99785 | ||
|
|
e215cf6fb8 | ||
|
|
e827c0bd94 | ||
|
|
8dd7e4e6b5 | ||
|
|
a2b94f4e06 | ||
|
|
8b0037ffab | ||
|
|
4ab03f3bef | ||
|
|
3c3db52f49 | ||
|
|
5b1e884659 | ||
|
|
7ec6740e26 | ||
|
|
f12b8c19be | ||
|
|
76174d31ce | ||
|
|
5042248260 | ||
|
|
1df6589533 | ||
|
|
12f76b448c | ||
|
|
f112ef34f6 |
@@ -100,8 +100,8 @@ International License. You retain the copyright to code you have
|
||||
written.
|
||||
|
||||
When accepting your first contribution, the maintainer of the project
|
||||
will ensure that you are added to the AUTHORS file. You are welcome to
|
||||
add yourself as a separate commit in your first pull request.
|
||||
will ensure that you are added to the AUTHORS file, the NICKS file and
|
||||
the list of authors in the about box.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
2
Godeps/Godeps.json
generated
2
Godeps/Godeps.json
generated
@@ -11,7 +11,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/logger",
|
||||
"Rev": "4d4e2801954c5581e4c2a80a3d3beb3b3645fd04"
|
||||
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/luhn",
|
||||
|
||||
8
Godeps/_workspace/src/github.com/calmh/logger/logger.go
generated
vendored
8
Godeps/_workspace/src/github.com/calmh/logger/logger.go
generated
vendored
@@ -6,6 +6,7 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -37,6 +38,13 @@ type Logger struct {
|
||||
var DefaultLogger = New()
|
||||
|
||||
func New() *Logger {
|
||||
if os.Getenv("LOGGER_DISCARD") != "" {
|
||||
// Hack to completely disable logging, for example when running benchmarks.
|
||||
return &Logger{
|
||||
logger: log.New(ioutil.Discard, "", 0),
|
||||
}
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
logger: log.New(os.Stdout, "", log.Ltime),
|
||||
}
|
||||
|
||||
50
benchfilter.go
Normal file
50
benchfilter.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2015 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 http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Neatly format benchmarking output which otherwise looks like crap.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
var (
|
||||
benchRe = regexp.MustCompile(`^Bench`)
|
||||
spacesRe = regexp.MustCompile(`\s+`)
|
||||
numbersRe = regexp.MustCompile(`\b[\d\.]+\b`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
|
||||
br := bufio.NewScanner(os.Stdin)
|
||||
n := 0
|
||||
|
||||
for br.Scan() {
|
||||
line := br.Bytes()
|
||||
|
||||
if benchRe.Match(line) {
|
||||
n++
|
||||
line = spacesRe.ReplaceAllLiteral(line, []byte("\t"))
|
||||
line = numbersRe.ReplaceAllFunc(line, func(n []byte) []byte {
|
||||
return []byte(fmt.Sprintf("%12s", n))
|
||||
})
|
||||
tw.Write(line)
|
||||
tw.Write([]byte("\n"))
|
||||
} else if n > 0 && bytes.HasPrefix(line, []byte("ok")) {
|
||||
n = 0
|
||||
tw.Flush()
|
||||
fmt.Printf("%s\n\n", line)
|
||||
}
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
24
build.go
24
build.go
@@ -110,6 +110,9 @@ func main() {
|
||||
case "test":
|
||||
test("./...")
|
||||
|
||||
case "bench":
|
||||
bench("./...")
|
||||
|
||||
case "assets":
|
||||
assets()
|
||||
|
||||
@@ -182,6 +185,11 @@ func test(pkg string) {
|
||||
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
|
||||
}
|
||||
|
||||
func bench(pkg string) {
|
||||
setBuildEnv()
|
||||
runPrint("go", "test", "-run", "NONE", "-bench", ".", pkg)
|
||||
}
|
||||
|
||||
func install(pkg string, tags []string) {
|
||||
os.Setenv("GOBIN", "./bin")
|
||||
args := []string{"install", "-v", "-ldflags", ldflags()}
|
||||
@@ -278,6 +286,17 @@ func buildZip() {
|
||||
func buildDeb() {
|
||||
os.RemoveAll("deb")
|
||||
|
||||
// "goarch" here is set to whatever the Debian packages expect. We correct
|
||||
// "it to what we actually know how to build and keep the Debian variant
|
||||
// "name in "debarch".
|
||||
debarch := goarch
|
||||
switch goarch {
|
||||
case "i386":
|
||||
goarch = "386"
|
||||
case "armel", "armhf":
|
||||
goarch = "arm"
|
||||
}
|
||||
|
||||
build("./cmd/syncthing", []string{"noupgrade"})
|
||||
|
||||
files := []archiveFile{
|
||||
@@ -297,11 +316,6 @@ func buildDeb() {
|
||||
}
|
||||
}
|
||||
|
||||
debarch := goarch
|
||||
if debarch == "386" {
|
||||
debarch = "i386"
|
||||
}
|
||||
|
||||
control := `Package: syncthing
|
||||
Architecture: {{arch}}
|
||||
Depends: libc6
|
||||
|
||||
10
build.sh
10
build.sh
@@ -17,8 +17,11 @@ case "${1:-default}" in
|
||||
ulimit -t 60 &>/dev/null || true
|
||||
ulimit -d 512000 &>/dev/null || true
|
||||
ulimit -m 512000 &>/dev/null || true
|
||||
go run build.go test
|
||||
;;
|
||||
|
||||
go run build.go "$1"
|
||||
bench)
|
||||
LOGGER_DISCARD=1 go run build.go bench | go run benchfilter.go
|
||||
;;
|
||||
|
||||
tar)
|
||||
@@ -118,8 +121,9 @@ case "${1:-default}" in
|
||||
-e "STTRACE=$STTRACE" \
|
||||
syncthing/build:latest \
|
||||
sh -c './build.sh clean \
|
||||
&& ./build.sh all \
|
||||
&& STTRACE=all ./build.sh test-cov'
|
||||
&& ./build.sh test-cov \
|
||||
&& ./build.sh bench \
|
||||
&& ./build.sh all'
|
||||
;;
|
||||
|
||||
docker-test)
|
||||
|
||||
@@ -29,7 +29,7 @@ print-missing-authors() {
|
||||
}
|
||||
|
||||
print-missing-copyright() {
|
||||
find . -name \*.go | xargs egrep -L 'Copyright \(C\)|automatically generated' | grep -v Godeps | grep -v internal/auto/
|
||||
find . -name \*.go | xargs egrep -L 'Copyright|automatically generated' | grep -v Godeps | grep -v internal/auto/
|
||||
}
|
||||
|
||||
authors=$(print-missing-authors)
|
||||
|
||||
@@ -279,7 +279,7 @@ func main() {
|
||||
l.Fatalln(dir, "is not a directory")
|
||||
}
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
err = osutil.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
l.Fatalln("generate:", err)
|
||||
}
|
||||
@@ -714,9 +714,7 @@ func syncthingMain() {
|
||||
func dbOpts() *opt.Options {
|
||||
// Calculate a suitable database block cache capacity.
|
||||
|
||||
// Default is 8 MiB. In reality, the database will use twice the amount we
|
||||
// calculate here, as it also has two write buffers each sized at half the
|
||||
// block cache.
|
||||
// Default is 8 MiB.
|
||||
blockCacheCapacity := 8 << 20
|
||||
// Increase block cache up to this maximum:
|
||||
const maxCapacity = 64 << 20
|
||||
@@ -743,7 +741,7 @@ func dbOpts() *opt.Options {
|
||||
return &opt.Options{
|
||||
OpenFilesCacheCapacity: 100,
|
||||
BlockCacheCapacity: blockCacheCapacity,
|
||||
WriteBuffer: blockCacheCapacity / 2,
|
||||
WriteBuffer: 4 << 20,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +880,7 @@ func discovery(extPort int) *discover.Discoverer {
|
||||
func ensureDir(dir string, mode int) {
|
||||
fi, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
err := os.MkdirAll(dir, 0700)
|
||||
err := osutil.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<small>{{ event.time | date:"H:mm:ss" }}:</small>
|
||||
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
|
||||
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
|
||||
Device {%device%} ({%address%}) wants to connect. Add new device?
|
||||
<span>
|
||||
@@ -122,7 +122,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<small>{{ event.time | date:"H:mm:ss" }}:</small>
|
||||
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
|
||||
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
|
||||
{%device%} wants to share folder "{%folder%}".
|
||||
</span>
|
||||
@@ -154,7 +154,7 @@
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
|
||||
<div class="panel-body">
|
||||
<p ng-repeat="err in errorList()"><small>{{err.time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
|
||||
<p ng-repeat="err in errorList()"><small>{{err.time | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span> <span translate>OK</span></button>
|
||||
@@ -265,7 +265,7 @@
|
||||
<tr ng-if="!folder.readOnly && folderStats[folder.id].lastFile">
|
||||
<th><span class="glyphicon glyphicon-transfer"></span> <span translate>Last File Received</span></th>
|
||||
<td class="text-right">
|
||||
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm'}}">
|
||||
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
|
||||
{{folderStats[folder.id].lastFile.filename | basename}}
|
||||
</span>
|
||||
</td>
|
||||
@@ -403,7 +403,7 @@
|
||||
<tr ng-if="!connections[deviceCfg.deviceID]">
|
||||
<th><span class="glyphicon glyphicon-eye-open"></span> <span translate>Last seen</span></th>
|
||||
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
|
||||
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
|
||||
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="glyphicon glyphicon-hdd"></span> <span translate>Folders</span></th>
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -110,7 +111,8 @@ func (d *Discoverer) startLocalIPv6Multicasts(localMCAddr string) {
|
||||
|
||||
v6Intfs := 0
|
||||
for _, intf := range intfs {
|
||||
if intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0 {
|
||||
// Interface flags seem to always be 0 on Windows
|
||||
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -100,9 +100,10 @@ type Event struct {
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
mask EventType
|
||||
id int
|
||||
events chan Event
|
||||
mask EventType
|
||||
id int
|
||||
events chan Event
|
||||
timeout *time.Timer
|
||||
}
|
||||
|
||||
var Default = NewLogger()
|
||||
@@ -149,9 +150,10 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
dl.Debugln("subscribe", mask)
|
||||
}
|
||||
s := &Subscription{
|
||||
mask: mask,
|
||||
id: l.nextID,
|
||||
events: make(chan Event, BufferSize),
|
||||
mask: mask,
|
||||
id: l.nextID,
|
||||
events: make(chan Event, BufferSize),
|
||||
timeout: time.NewTimer(0),
|
||||
}
|
||||
l.nextID++
|
||||
l.subs[s.id] = s
|
||||
@@ -169,19 +171,22 @@ func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
l.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Poll returns an event from the subscription or an error if the poll times
|
||||
// out of the event channel is closed. Poll should not be called concurrently
|
||||
// from multiple goroutines for a single subscription.
|
||||
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
if debug {
|
||||
dl.Debugln("poll", timeout)
|
||||
}
|
||||
|
||||
to := time.After(timeout)
|
||||
s.timeout.Reset(timeout)
|
||||
select {
|
||||
case e, ok := <-s.events:
|
||||
if !ok {
|
||||
return e, ErrClosed
|
||||
}
|
||||
return e, nil
|
||||
case <-to:
|
||||
case <-s.timeout.C:
|
||||
return Event{}, ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1611,7 +1611,7 @@ func (m *Model) CheckFolderHealth(id string) error {
|
||||
} else if os.IsNotExist(err) {
|
||||
// If we don't have any files in the index, and the directory
|
||||
// doesn't exist, try creating it.
|
||||
err = os.MkdirAll(folder.Path(), 0700)
|
||||
err = osutil.MkdirAll(folder.Path(), 0700)
|
||||
if err == nil {
|
||||
err = folder.CreateMarker()
|
||||
}
|
||||
|
||||
@@ -176,74 +176,58 @@ func genFiles(n int) []protocol.FileInfo {
|
||||
return files
|
||||
}
|
||||
|
||||
func BenchmarkIndex10000(b *testing.B) {
|
||||
func BenchmarkIndex_10000(b *testing.B) {
|
||||
benchmarkIndex(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkIndex_100(b *testing.B) {
|
||||
benchmarkIndex(b, 100)
|
||||
}
|
||||
|
||||
func benchmarkIndex(b *testing.B, nfiles int) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.StartFolderRO("default")
|
||||
|
||||
files := genFiles(nfiles)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
func BenchmarkIndex00100(b *testing.B) {
|
||||
func BenchmarkIndexUpdate_10000_10000(b *testing.B) {
|
||||
benchmarkIndexUpdate(b, 10000, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate_10000_100(b *testing.B) {
|
||||
benchmarkIndexUpdate(b, 10000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate_10000_1(b *testing.B) {
|
||||
benchmarkIndexUpdate(b, 10000, 1)
|
||||
}
|
||||
|
||||
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(100)
|
||||
m.StartFolderRO("default")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
}
|
||||
}
|
||||
files := genFiles(nfiles)
|
||||
ufiles := genFiles(nufiles)
|
||||
|
||||
func BenchmarkIndexUpdate10000f10000(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.IndexUpdate(device1, "default", files, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate10000f00100(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
ufiles := genFiles(100)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.IndexUpdate(device1, "default", ufiles, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate10000f00001(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
ufiles := genFiles(1)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.IndexUpdate(device1, "default", ufiles, 0, nil)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
type FakeConnection struct {
|
||||
@@ -1166,56 +1150,23 @@ func genDeepFiles(n, d int) []protocol.FileInfo {
|
||||
}
|
||||
|
||||
func BenchmarkTree_10000_50(b *testing.B) {
|
||||
benchmarkTree(b, 10000, 50)
|
||||
}
|
||||
|
||||
func BenchmarkTree_100_50(b *testing.B) {
|
||||
benchmarkTree(b, 100, 50)
|
||||
}
|
||||
|
||||
func BenchmarkTree_100_10(b *testing.B) {
|
||||
benchmarkTree(b, 100, 10)
|
||||
}
|
||||
|
||||
func benchmarkTree(b *testing.B, n1, n2 int) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(10000, 50)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTree_10000_10(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(10000, 10)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTree_00100_50(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(100, 50)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTree_00100_10(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(100, 10)
|
||||
files := genDeepFiles(n1, n2)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
@@ -1223,4 +1174,5 @@ func BenchmarkTree_00100_10(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
@@ -631,8 +631,16 @@ func (p *rwFolder) deleteDir(file protocol.FileInfo) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = osutil.InWritableDir(osutil.Remove, realName)
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
// It was removed or it doesn't exist to start with
|
||||
p.dbUpdates <- file
|
||||
} else if _, err = os.Lstat(realName); err != nil && !os.IsPermission(err) {
|
||||
// We get an error just looking at the directory, and it's not a
|
||||
// permission problem. Lets assume the error is in fact some variant
|
||||
// of "file does not exist" (possibly expressed as some parent being a
|
||||
// file and not a directory etc) and that the delete is handled.
|
||||
p.dbUpdates <- file
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): delete: %v", p.folder, file.Name, err)
|
||||
@@ -673,10 +681,17 @@ func (p *rwFolder) deleteFile(file protocol.FileInfo) {
|
||||
err = osutil.InWritableDir(osutil.Remove, realName)
|
||||
}
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
l.Infof("Puller (folder %q, file %q): delete: %v", p.folder, file.Name, err)
|
||||
} else {
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
// It was removed or it doesn't exist to start with
|
||||
p.dbUpdates <- file
|
||||
} else if _, err := os.Lstat(realName); err != nil && !os.IsPermission(err) {
|
||||
// We get an error just looking at the file, and it's not a permission
|
||||
// problem. Lets assume the error is in fact some variant of "file
|
||||
// does not exist" (possibly expressed as some parent being a file and
|
||||
// not a directory etc) and that the delete is handled.
|
||||
p.dbUpdates <- file
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, file %q): delete: %v", p.folder, file.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
internal/osutil/mkdirall.go
Normal file
17
internal/osutil/mkdirall.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2015 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 http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
65
internal/osutil/mkdirall_windows.go
Normal file
65
internal/osutil/mkdirall_windows.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Modified by Zillode to fix https://github.com/syncthing/syncthing/issues/1822
|
||||
// Sync with https://github.com/golang/go/blob/master/src/os/path.go
|
||||
// See https://github.com/golang/go/issues/10900
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// MkdirAll creates a directory named path, along with any necessary parents,
|
||||
// and returns nil, or else returns an error.
|
||||
// The permission bits perm are used for all directories that MkdirAll creates.
|
||||
// If path is already a directory, MkdirAll does nothing and returns nil.
|
||||
func MkdirAll(path string, perm os.FileMode) error {
|
||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||
dir, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{"mkdir", path, syscall.ENOTDIR}
|
||||
}
|
||||
|
||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||
i := len(path)
|
||||
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
|
||||
i--
|
||||
}
|
||||
|
||||
j := i
|
||||
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
|
||||
j--
|
||||
}
|
||||
|
||||
if j > 1 {
|
||||
// Create parent
|
||||
parent := path[0 : j-1]
|
||||
if parent != filepath.VolumeName(parent) {
|
||||
err = MkdirAll(parent, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent now exists; invoke Mkdir and use its result.
|
||||
err = os.Mkdir(path, perm)
|
||||
if err != nil {
|
||||
// Handle arguments like "foo/." by
|
||||
// double-checking that directory doesn't exist.
|
||||
dir, err1 := os.Lstat(path)
|
||||
if err1 == nil && dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/ignore"
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
@@ -218,7 +219,7 @@ func TestNormalization(t *testing.T) {
|
||||
|
||||
for _, s1 := range tests {
|
||||
// Create a directory for each of the interesting strings above
|
||||
if err := os.MkdirAll(filepath.Join("testdata/normalization", s1), 0755); err != nil {
|
||||
if err := osutil.MkdirAll(filepath.Join("testdata/normalization", s1), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -102,37 +103,11 @@ func Discover(timeout time.Duration) []IGD {
|
||||
|
||||
resultChan := make(chan IGD)
|
||||
|
||||
// Aggregator
|
||||
go func() {
|
||||
next:
|
||||
for result := range resultChan {
|
||||
for _, existingResult := range results {
|
||||
if existingResult.uuid == result.uuid {
|
||||
if debug {
|
||||
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
goto next
|
||||
}
|
||||
}
|
||||
results = append(results, result)
|
||||
if debug {
|
||||
l.Debugf("UPnP discovery result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg := sync.NewWaitGroup()
|
||||
|
||||
for _, intf := range interfaces {
|
||||
if intf.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
if intf.Flags&net.FlagMulticast == 0 {
|
||||
// Interface flags seem to always be 0 on Windows
|
||||
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -145,8 +120,33 @@ func Discover(timeout time.Duration) []IGD {
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
}()
|
||||
|
||||
nextResult:
|
||||
for result := range resultChan {
|
||||
for _, existingResult := range results {
|
||||
if existingResult.uuid == result.uuid {
|
||||
if debug {
|
||||
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
continue nextResult
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
if debug {
|
||||
l.Debugf("UPnP discovery result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -472,7 +472,7 @@ func soapRequest(url, service, function, message string) ([]byte, error) {
|
||||
|
||||
resp, _ = ioutil.ReadAll(r.Body)
|
||||
if debug {
|
||||
l.Debugln("SOAP Response:\n\n" + string(resp) + "\n")
|
||||
l.Debugf("SOAP Response: %v\n\n%v\n\n", r.StatusCode, string(resp))
|
||||
}
|
||||
|
||||
r.Body.Close()
|
||||
@@ -528,6 +528,11 @@ type getExternalIPAddressResponse struct {
|
||||
NewExternalIPAddress string `xml:"NewExternalIPAddress"`
|
||||
}
|
||||
|
||||
type soapErrorResponse struct {
|
||||
ErrorCode int `xml:"Body>Fault>detail>UPnPError>errorCode"`
|
||||
ErrorDescription string `xml:"Body>Fault>detail>UPnPError>errorDescription"`
|
||||
}
|
||||
|
||||
// AddPortMapping adds a port mapping to the specified IGD service.
|
||||
func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
|
||||
tpl := `<u:AddPortMapping xmlns:u="%s">
|
||||
@@ -542,12 +547,20 @@ func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, ex
|
||||
</u:AddPortMapping>`
|
||||
body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol, internalPort, localIPAddress, description, timeout)
|
||||
|
||||
_, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
|
||||
if err != nil {
|
||||
return err
|
||||
response, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
|
||||
if err != nil && timeout > 0 {
|
||||
// Try to repair error code 725 - OnlyPermanentLeasesSupported
|
||||
envelope := &soapErrorResponse{}
|
||||
err = xml.Unmarshal(response, envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if envelope.ErrorCode == 725 {
|
||||
return s.AddPortMapping(localIPAddress, protocol, externalPort, internalPort, description, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePortMapping deletes a port mapping from the specified IGD service.
|
||||
|
||||
@@ -33,6 +33,33 @@ func TestExternalIPParsing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoapFaultParsing(t *testing.T) {
|
||||
soapResponse :=
|
||||
[]byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<s:Fault>
|
||||
<faultcode>s:Client</faultcode>
|
||||
<faultstring>UPnPError</faultstring>
|
||||
<detail>
|
||||
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
|
||||
<errorCode>725</errorCode>
|
||||
<errorDescription>OnlyPermanentLeasesSupported</errorDescription></UPnPError>
|
||||
</detail>
|
||||
</s:Fault>
|
||||
</s:Body>
|
||||
</s:Envelope>`)
|
||||
|
||||
envelope := &soapErrorResponse{}
|
||||
err := xml.Unmarshal(soapResponse, envelope)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if envelope.ErrorCode != 725 {
|
||||
t.Error("Parse of SOAP request failed.", envelope)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlURLParsing(t *testing.T) {
|
||||
rootURL := "http://192.168.243.1:80/igd.xml"
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ func (v Simple) Archive(filePath string) error {
|
||||
if debug {
|
||||
l.Debugln("creating versions dir", versionsDir)
|
||||
}
|
||||
os.MkdirAll(versionsDir, 0755)
|
||||
osutil.MkdirAll(versionsDir, 0755)
|
||||
osutil.HideFile(versionsDir)
|
||||
} else {
|
||||
return err
|
||||
@@ -79,7 +79,7 @@ func (v Simple) Archive(filePath string) error {
|
||||
}
|
||||
|
||||
dir := filepath.Join(versionsDir, inFolderPath)
|
||||
err = os.MkdirAll(dir, 0755)
|
||||
err = osutil.MkdirAll(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ func (v Staggered) Archive(filePath string) error {
|
||||
if debug {
|
||||
l.Debugln("creating versions dir", v.versionsPath)
|
||||
}
|
||||
os.MkdirAll(v.versionsPath, 0755)
|
||||
osutil.MkdirAll(v.versionsPath, 0755)
|
||||
osutil.HideFile(v.versionsPath)
|
||||
} else {
|
||||
return err
|
||||
@@ -275,7 +275,7 @@ func (v Staggered) Archive(filePath string) error {
|
||||
}
|
||||
|
||||
dir := filepath.Join(v.versionsPath, inFolderPath)
|
||||
err = os.MkdirAll(dir, 0755)
|
||||
err = osutil.MkdirAll(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestConflict(t *testing.T) {
|
||||
defer sender.stop()
|
||||
defer receiver.stop()
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestConflict(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestConflict(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestInitialMergeConflicts(t *testing.T) {
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ func TestResetConflicts(t *testing.T) {
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ func TestResetConflicts(t *testing.T) {
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ func TestResetConflicts(t *testing.T) {
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
if err = coCompletion(sender, receiver); err != nil {
|
||||
if err = awaitCompletion("default", sender, receiver); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -467,31 +467,3 @@ func coSenderReceiver(t *testing.T) (syncthingProcess, syncthingProcess) {
|
||||
|
||||
return sender, receiver
|
||||
}
|
||||
|
||||
func coCompletion(p ...syncthingProcess) error {
|
||||
mainLoop:
|
||||
for {
|
||||
time.Sleep(2500 * time.Millisecond)
|
||||
|
||||
tot := 0
|
||||
for i := range p {
|
||||
comp, err := p[i].peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
continue mainLoop
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pct := range comp {
|
||||
tot += pct
|
||||
}
|
||||
}
|
||||
|
||||
if tot == 100*(len(p)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("%d / %d...", tot, 100*(len(p)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
@@ -113,6 +112,7 @@ func testFileTypeChange(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sender.stop()
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
instance: "2",
|
||||
@@ -125,28 +125,11 @@ func testFileTypeChange(t *testing.T) {
|
||||
sender.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer receiver.stop()
|
||||
|
||||
for {
|
||||
comp, err := sender.peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
sender.stop()
|
||||
receiver.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curComp := comp[id2]
|
||||
|
||||
if curComp == 100 {
|
||||
sender.stop()
|
||||
receiver.stop()
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
err = awaitCompletion("default", sender, receiver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = sender.stop()
|
||||
@@ -212,36 +195,10 @@ func testFileTypeChange(t *testing.T) {
|
||||
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
sender.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
comp, err := sender.peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
sender.stop()
|
||||
receiver.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curComp := comp[id2]
|
||||
|
||||
if curComp == 100 {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
_, err = sender.stop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = receiver.stop()
|
||||
err = awaitCompletion("default", sender, receiver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
)
|
||||
|
||||
var jsonEndpoints = []string{
|
||||
@@ -172,11 +176,13 @@ func TestGetJSON(t *testing.T) {
|
||||
for _, path := range jsonEndpoints {
|
||||
res, err := st.get(path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ct := res.Header.Get("Content-Type"); ct != "application/json; charset=utf-8" {
|
||||
t.Errorf("Incorrect Content-Type %q for %q", ct, path)
|
||||
continue
|
||||
}
|
||||
|
||||
var intf interface{}
|
||||
@@ -184,7 +190,7 @@ func TestGetJSON(t *testing.T) {
|
||||
res.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,3 +270,92 @@ func TestPOSTWithoutCSRF(t *testing.T) {
|
||||
t.Fatalf("Status %d != 403 for POST", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
initOnce sync.Once
|
||||
proc syncthingProcess
|
||||
)
|
||||
|
||||
func setupAPIBench() {
|
||||
err := removeAll("s1", "s2", "h1/index*", "h2/index*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = generateFiles("s1", 25000, 20, "../LICENSE")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("s1/knownfile", []byte("somedatahere"), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
proc = syncthingProcess{ // id1
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = proc.start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Wait for one scan to succeed, or up to 20 seconds... This is to let
|
||||
// startup, UPnP etc complete and make sure the sender has the full index
|
||||
// before they connect.
|
||||
for i := 0; i < 20; i++ {
|
||||
resp, err := proc.post("/rest/scan?folder=default", nil)
|
||||
if err != nil {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkURL(b *testing.B, url string) {
|
||||
initOnce.Do(setupAPIBench)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := proc.get(url)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
b.Fatal(resp.Status)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAPI_db_completion(b *testing.B) {
|
||||
benchmarkURL(b, "/rest/db/completion?folder=default&device="+protocol.LocalDeviceID.String())
|
||||
}
|
||||
|
||||
func BenchmarkAPI_db_file(b *testing.B) {
|
||||
benchmarkURL(b, "/rest/db/file?folder=default&file=knownfile")
|
||||
}
|
||||
|
||||
func BenchmarkAPI_db_ignores(b *testing.B) {
|
||||
benchmarkURL(b, "/rest/db/ignores?folder=default")
|
||||
}
|
||||
|
||||
func BenchmarkAPI_db_need(b *testing.B) {
|
||||
benchmarkURL(b, "/rest/db/need?folder=default")
|
||||
}
|
||||
|
||||
func BenchmarkAPI_db_status(b *testing.B) {
|
||||
benchmarkURL(b, "/rest/db/status?folder=default")
|
||||
}
|
||||
|
||||
func BenchmarkAPI_db_browse_dirsonly(b *testing.B) {
|
||||
benchmarkURL(b, "/rest/db/browse?folder=default&dirsonly=true")
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
@@ -92,19 +91,9 @@ func TestManyPeers(t *testing.T) {
|
||||
}
|
||||
defer sender.stop()
|
||||
|
||||
for {
|
||||
comp, err := sender.peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
if comp[id2] == 100 {
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
err = awaitCompletion("default", sender, receiver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Comparing directories...")
|
||||
|
||||
@@ -102,7 +102,8 @@ func TestOverride(t *testing.T) {
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
if err = ovCompletion(100, master, slave); err != nil {
|
||||
err = awaitCompletion("default", master, slave)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -137,14 +138,9 @@ func TestOverride(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Syncing...")
|
||||
log.Println("Waiting for index to send...")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Expect ~99% completion since the change will be rejected by the master side
|
||||
if err = ovCompletion(99, master, slave); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
log.Println("Hitting Override on master...")
|
||||
|
||||
@@ -158,7 +154,8 @@ func TestOverride(t *testing.T) {
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
if err = ovCompletion(100, master, slave); err != nil {
|
||||
err = awaitCompletion("default", master, slave)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -193,6 +190,9 @@ func TestOverride(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This doesn't currently work with detection completion, as we don't actually
|
||||
get to completion when in master/slave mode. Needs fixing.
|
||||
|
||||
func TestOverrideIgnores(t *testing.T) {
|
||||
// Enable "Master" on s1/default
|
||||
id, _ := protocol.DeviceIDFromString(id1)
|
||||
@@ -247,18 +247,6 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
}
|
||||
defer master.stop()
|
||||
|
||||
// Wait for one scan to succeed, or up to 20 seconds... This is to let
|
||||
// startup, UPnP etc complete and make sure the master has the full index
|
||||
// before they connect.
|
||||
for i := 0; i < 20; i++ {
|
||||
err := master.rescan("default")
|
||||
if err != nil {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
log.Println("Starting slave...")
|
||||
slave := syncthingProcess{ // id2
|
||||
instance: "2",
|
||||
@@ -275,7 +263,8 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
if err = ovCompletion(100, master, slave); err != nil {
|
||||
err = awaitCompletion("default", master, slave)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -335,12 +324,8 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
|
||||
err = master.rescan("default")
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
// Expect 100% completion since the change should be invisible to the master side
|
||||
if err = ovCompletion(100, master, slave); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Println("Waiting for sync...")
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Verify that sync worked
|
||||
|
||||
@@ -381,12 +366,8 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
// Expect 100% completion since the change should be invisible to the master side
|
||||
if err = ovCompletion(100, master, slave); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Println("Waiting for sync...")
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Verify that nothing changed
|
||||
|
||||
@@ -434,12 +415,8 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
t.Fatal(resp.Status)
|
||||
}
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
// Expect ~99% completion since the change will be rejected by the master side
|
||||
if err = ovCompletion(99, master, slave); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Println("Waiting for sync...")
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
fd, err = os.Open("s2/testfile.txt")
|
||||
if err == nil {
|
||||
@@ -473,12 +450,8 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
t.Fatal(resp.Status)
|
||||
}
|
||||
|
||||
log.Println("Syncing...")
|
||||
|
||||
// Expect ~99% completion since the change will be rejected by the master side
|
||||
if err = ovCompletion(99, master, slave); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Println("Waiting for sync...")
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
fd, err = os.Open("s2/testfile.txt")
|
||||
if err == nil {
|
||||
@@ -503,31 +476,4 @@ func TestOverrideIgnores(t *testing.T) {
|
||||
fd.Close()
|
||||
|
||||
}
|
||||
|
||||
func ovCompletion(expected int, p ...syncthingProcess) error {
|
||||
mainLoop:
|
||||
for {
|
||||
time.Sleep(2500 * time.Millisecond)
|
||||
|
||||
tot := 0
|
||||
for i := range p {
|
||||
comp, err := p[i].peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
continue mainLoop
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pct := range comp {
|
||||
tot += pct
|
||||
}
|
||||
}
|
||||
|
||||
if tot >= expected*len(p) {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("%d / %d...", tot, expected*len(p))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,9 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sender.stop()
|
||||
|
||||
waitForScan(sender)
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
instance: "2",
|
||||
@@ -63,38 +66,24 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
|
||||
}
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
sender.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer receiver.stop()
|
||||
|
||||
var prevComp int
|
||||
var prevBytes int
|
||||
for {
|
||||
comp, err := sender.peerCompletion()
|
||||
recv, err := receiver.dbStatus("default")
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
sender.stop()
|
||||
receiver.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curComp := comp[id2]
|
||||
|
||||
if curComp == 100 {
|
||||
_, err = sender.stop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = receiver.stop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if recv.InSyncBytes > 0 && recv.InSyncBytes == recv.GlobalBytes {
|
||||
// Receiver is done
|
||||
break
|
||||
}
|
||||
} else if recv.InSyncBytes > prevBytes+recv.GlobalBytes/10 {
|
||||
// Receiver has made progress
|
||||
prevBytes = recv.InSyncBytes
|
||||
|
||||
if curComp > prevComp {
|
||||
if restartReceiver {
|
||||
log.Printf("Stopping receiver...")
|
||||
_, err = receiver.stop()
|
||||
@@ -134,8 +123,6 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
prevComp = curComp
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
@@ -41,13 +40,13 @@ func TestReset(t *testing.T) {
|
||||
// startup, UPnP etc complete and make sure that we've performed folder
|
||||
// error checking which creates the folder path if it's missing.
|
||||
log.Println("Starting...")
|
||||
waitForScan(t, &p)
|
||||
waitForScan(p)
|
||||
|
||||
log.Println("Creating files...")
|
||||
size := createFiles(t)
|
||||
|
||||
log.Println("Scanning files...")
|
||||
waitForScan(t, &p)
|
||||
waitForScan(p)
|
||||
|
||||
m, err := p.model("default")
|
||||
if err != nil {
|
||||
@@ -90,7 +89,7 @@ func TestReset(t *testing.T) {
|
||||
|
||||
// Wait for ST and scan
|
||||
p.start()
|
||||
waitForScan(t, &p)
|
||||
waitForScan(p)
|
||||
|
||||
// Verify that we see them
|
||||
m, err = p.model("default")
|
||||
@@ -105,7 +104,7 @@ func TestReset(t *testing.T) {
|
||||
// Recreate the files and scan
|
||||
log.Println("Creating files...")
|
||||
size = createFiles(t)
|
||||
waitForScan(t, &p)
|
||||
waitForScan(p)
|
||||
|
||||
// Verify that we see them
|
||||
m, err = p.model("default")
|
||||
@@ -126,7 +125,7 @@ func TestReset(t *testing.T) {
|
||||
|
||||
// Wait for ST and scan
|
||||
p.start()
|
||||
waitForScan(t, &p)
|
||||
waitForScan(p)
|
||||
|
||||
m, err = p.model("default")
|
||||
if err != nil {
|
||||
@@ -138,18 +137,6 @@ func TestReset(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func waitForScan(t *testing.T, p *syncthingProcess) {
|
||||
// Wait for one scan to succeed, or up to 20 seconds...
|
||||
for i := 0; i < 20; i++ {
|
||||
err := p.rescan("default")
|
||||
if err != nil {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func createFiles(t *testing.T) int {
|
||||
// Create eight empty files and directories
|
||||
files := []string{"f1", "f2", "f3", "f4", "f11", "f12", "f13", "f14"}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
@@ -177,6 +176,7 @@ func testSymlinks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sender.stop()
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
instance: "2",
|
||||
@@ -186,29 +186,13 @@ func testSymlinks(t *testing.T) {
|
||||
}
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
sender.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer receiver.stop()
|
||||
|
||||
for {
|
||||
comp, err := sender.peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
sender.stop()
|
||||
receiver.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curComp := comp[id2]
|
||||
|
||||
if curComp == 100 {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
err = awaitCompletion("default", sender, receiver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = sender.stop()
|
||||
@@ -311,29 +295,12 @@ func testSymlinks(t *testing.T) {
|
||||
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
sender.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
comp, err := sender.peerCompletion()
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
sender.stop()
|
||||
receiver.stop()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curComp := comp[id2]
|
||||
|
||||
if curComp == 100 {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
err = awaitCompletion("default", sender, receiver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = sender.stop()
|
||||
|
||||
@@ -163,17 +163,8 @@ func testSyncCluster(t *testing.T) {
|
||||
}()
|
||||
|
||||
log.Println("Waiting for startup...")
|
||||
// Wait for one scan to succeed, or up to 20 seconds...
|
||||
// This is to let startup, UPnP etc complete.
|
||||
for _, device := range p {
|
||||
for i := 0; i < 20; i++ {
|
||||
err := device.rescan("default")
|
||||
if err != nil {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
for _, dev := range p {
|
||||
waitForScan(dev)
|
||||
}
|
||||
|
||||
for count := 0; count < iterations; count++ {
|
||||
@@ -310,15 +301,15 @@ func scStartProcesses() ([]syncthingProcess, error) {
|
||||
func scSyncAndCompare(p []syncthingProcess, expected [][]fileInfo) error {
|
||||
log.Println("Syncing...")
|
||||
|
||||
for {
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if err := allDevicesInSync(p); err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
// Special handling because we know which devices share which folders...
|
||||
if err := awaitCompletion("default", p...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := awaitCompletion("s12", p[0], p[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := awaitCompletion("s23", p[1], p[2]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This is necessary, or all files won't be in place even when everything
|
||||
|
||||
@@ -66,7 +66,7 @@ func (p *syncthingProcess) start() error {
|
||||
binary = binary + "-" + p.instance + ".exe"
|
||||
}
|
||||
|
||||
argv := append(p.argv, "-no-browser")
|
||||
argv := append(p.argv, "-no-browser", "-verbose")
|
||||
cmd := exec.Command(binary, argv...)
|
||||
cmd.Stdout = p.logfd
|
||||
cmd.Stderr = p.logfd
|
||||
@@ -203,40 +203,6 @@ func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, er
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
|
||||
resp, err := p.get("/rest/debug/peerCompletion")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
comp := map[string]int{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&comp)
|
||||
|
||||
// Remove ourselves from the set. In the remaining map, all peers should
|
||||
// be att 100% if we're in sync.
|
||||
for id := range comp {
|
||||
if id == p.id.String() {
|
||||
delete(comp, id)
|
||||
}
|
||||
}
|
||||
|
||||
return comp, err
|
||||
}
|
||||
|
||||
func (p *syncthingProcess) allPeersInSync() error {
|
||||
comp, err := p.peerCompletion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for id, val := range comp {
|
||||
if val != 100 {
|
||||
return fmt.Errorf("%.7s at %d%%", id, val)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type model struct {
|
||||
GlobalBytes int
|
||||
GlobalDeleted int
|
||||
@@ -311,6 +277,35 @@ func (p *syncthingProcess) version() (string, error) {
|
||||
return v.Version, nil
|
||||
}
|
||||
|
||||
type statusResp struct {
|
||||
GlobalBytes int
|
||||
InSyncBytes int
|
||||
Version int
|
||||
}
|
||||
|
||||
func (p *syncthingProcess) dbStatus(folder string) (statusResp, error) {
|
||||
resp, err := p.get("/rest/db/status?folder=" + folder)
|
||||
if err != nil {
|
||||
return statusResp{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var s statusResp
|
||||
err = json.NewDecoder(resp.Body).Decode(&s)
|
||||
if err != nil {
|
||||
return statusResp{}, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (p *syncthingProcess) insync(folder string) (bool, int, error) {
|
||||
s, err := p.dbStatus(folder)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
return s.GlobalBytes == s.InSyncBytes, s.Version, nil
|
||||
}
|
||||
|
||||
func (p *syncthingProcess) rescan(folder string) error {
|
||||
resp, err := p.post("/rest/db/scan?folder="+folder, nil)
|
||||
if err != nil {
|
||||
@@ -350,11 +345,46 @@ func (p *syncthingProcess) reset(folder string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func allDevicesInSync(p []syncthingProcess) error {
|
||||
for _, device := range p {
|
||||
if err := device.allPeersInSync(); err != nil {
|
||||
return fmt.Errorf("%.7s: %v", device.id.String(), err)
|
||||
func awaitCompletion(folder string, ps ...syncthingProcess) error {
|
||||
mainLoop:
|
||||
for {
|
||||
time.Sleep(2500 * time.Millisecond)
|
||||
|
||||
expectedVersion := 0
|
||||
for _, p := range ps {
|
||||
insync, version, err := p.insync(folder)
|
||||
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
continue mainLoop
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !insync {
|
||||
continue mainLoop
|
||||
}
|
||||
|
||||
if expectedVersion == 0 {
|
||||
expectedVersion = version
|
||||
} else if version != expectedVersion {
|
||||
// Version number mismatch between devices, so not in sync.
|
||||
continue mainLoop
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitForScan(p syncthingProcess) {
|
||||
// Wait for one scan to succeed, or up to 20 seconds...
|
||||
for i := 0; i < 20; i++ {
|
||||
err := p.rescan("default")
|
||||
if err != nil {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user