mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-02 10:59:03 -05:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c88e473cb | ||
|
|
875377981d | ||
|
|
c0b5a70ce3 | ||
|
|
7bcdc5b08e | ||
|
|
c0b3de2680 | ||
|
|
9a9bcff3e9 | ||
|
|
88482b29ee | ||
|
|
22dff7207c | ||
|
|
0104e78589 | ||
|
|
80894948f6 | ||
|
|
e945e65b13 | ||
|
|
ebd2e5bf30 | ||
|
|
60c07b259e | ||
|
|
fe50f1a158 | ||
|
|
5851aabe02 | ||
|
|
0832285d79 | ||
|
|
c2ea9d119d | ||
|
|
534f07d9ca | ||
|
|
24d4290d03 | ||
|
|
09b872cef4 | ||
|
|
2d124e053c | ||
|
|
90b70c7a16 | ||
|
|
e910acdc17 | ||
|
|
96350d7600 | ||
|
|
77a5980747 | ||
|
|
b677464dfa | ||
|
|
b1c74860e8 | ||
|
|
f6f696c6c5 | ||
|
|
cf40ed6cec | ||
|
|
6fa02d5081 | ||
|
|
86e35f1879 | ||
|
|
720a6bf62e | ||
|
|
4a619e74f2 | ||
|
|
58ef5368f8 | ||
|
|
7b37d453f9 | ||
|
|
edf2399ce6 | ||
|
|
d43b0a4395 | ||
|
|
f2efd08e0f | ||
|
|
61b9f7bd55 | ||
|
|
1475c0344a | ||
|
|
77cc87dfca | ||
|
|
bc7dd02e2b | ||
|
|
8a06cf0973 | ||
|
|
05835ed81f | ||
|
|
df522576ac | ||
|
|
d681ac11fe | ||
|
|
7d5f7d508d | ||
|
|
1d182e4631 | ||
|
|
710f5c199f | ||
|
|
fd847d4efe | ||
|
|
15e51fc045 | ||
|
|
c1c976aa2b | ||
|
|
159d1a68e1 | ||
|
|
dd850f66bb | ||
|
|
669bcb748f | ||
|
|
4e22a96602 | ||
|
|
a992559abc | ||
|
|
46e72d76b5 | ||
|
|
7a4c88d4e4 |
2
AUTHORS
2
AUTHORS
@@ -46,7 +46,7 @@ Brandon Philips (philips) <brandon@ifup.org>
|
||||
Brendan Long (brendanlong) <self@brendanlong.com>
|
||||
Brian R. Becker (brbecker) <brbecker@gmail.com>
|
||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
||||
Carsten Hagemann (Moter8) <moter8@gmail.com>
|
||||
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.12 AS builder
|
||||
FROM golang:1.13 AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
@@ -24,4 +24,5 @@ ENV PUID=1000 PGID=1000
|
||||
HEALTHCHECK --interval=1m --timeout=10s \
|
||||
CMD nc -z localhost 8384 || exit 1
|
||||
|
||||
ENTRYPOINT ["/bin/entrypoint.sh", "-home", "/var/syncthing/config", "-gui-address", "0.0.0.0:8384"]
|
||||
ENV STGUIADDRESS=0.0.0.0:8384
|
||||
ENTRYPOINT ["/bin/entrypoint.sh", "-home", "/var/syncthing/config"]
|
||||
|
||||
@@ -18,7 +18,11 @@ $ docker run -p 8384:8384 -p 22000:22000 \
|
||||
syncthing/syncthing:latest
|
||||
```
|
||||
|
||||
Note that local device discovery will not work with the above command, resulting in poor local transfer rates if local device addresses are not manually configured.
|
||||
## Discovery
|
||||
|
||||
Note that local device discovery will not work with the above command,
|
||||
resulting in poor local transfer rates if local device addresses are not
|
||||
manually configured.
|
||||
|
||||
To allow local discovery, the docker host network can be used instead:
|
||||
|
||||
@@ -32,3 +36,24 @@ $ docker run --network=host \
|
||||
Be aware that syncthing alone is now in control of what interfaces and ports it
|
||||
listens on. You can edit the syncthing configuration to change the defaults if
|
||||
there are conflicts.
|
||||
|
||||
## GUI Security
|
||||
|
||||
By default Syncthing inside the Docker image listens on 0.0.0.0:8384 to
|
||||
allow GUI connections via the Docker proxy. This is set by the
|
||||
`STGUIADDRESS` environment variable in the Dockerfile, as it differs from
|
||||
what Syncthing would otherwise use by default. This means you should set up
|
||||
authentication in the GUI, like for any other externally reachable Syncthing
|
||||
instance. If you do not require the GUI, or you use host networking, you can
|
||||
unset the `STGUIADDRESS` variable to have Syncthing fall back to listening
|
||||
on 127.0.0.1:
|
||||
|
||||
```
|
||||
$ docker pull syncthing/syncthing
|
||||
$ docker run -e STGUIADDRESS= \
|
||||
-v /wherever/st-sync:/var/syncthing \
|
||||
syncthing/syncthing:latest
|
||||
```
|
||||
|
||||
With the environment variable unset Syncthing will follow what is set in the
|
||||
configuration file / GUI settings dialog.
|
||||
|
||||
@@ -62,6 +62,10 @@ There are a few examples for keeping Syncthing running in the background
|
||||
on your system in [the etc directory][3]. There are also several [GUI
|
||||
implementations][11] for Windows, Mac and Linux.
|
||||
|
||||
## Docker
|
||||
|
||||
To run Syncthing in Docker, see [the Docker README][16].
|
||||
|
||||
## Vote on features/bugs
|
||||
|
||||
We'd like to encourage you to [vote][12] on issues that matter to you.
|
||||
@@ -110,4 +114,5 @@ All code is licensed under the [MPLv2 License][7].
|
||||
[13]: https://github.com/syncthing/syncthing/blob/master/GOALS.md
|
||||
[14]: assets/logo-text-128.png
|
||||
[15]: https://syncthing.net/
|
||||
[16]: https://github.com/syncthing/syncthing/blob/master/README-Docker.md
|
||||
|
||||
|
||||
76
build.go
76
build.go
@@ -34,34 +34,35 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
noupgrade bool
|
||||
version string
|
||||
goCmd string
|
||||
goVersion float64
|
||||
race bool
|
||||
debug = os.Getenv("BUILDDEBUG") != ""
|
||||
extraTags string
|
||||
installSuffix string
|
||||
pkgdir string
|
||||
cc string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
timeout = "120s"
|
||||
gogoProtoVersion = "v1.2.0"
|
||||
versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
noupgrade bool
|
||||
version string
|
||||
goCmd string
|
||||
goVersion float64
|
||||
race bool
|
||||
debug = os.Getenv("BUILDDEBUG") != ""
|
||||
extraTags string
|
||||
installSuffix string
|
||||
pkgdir string
|
||||
cc string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
timeout = "120s"
|
||||
)
|
||||
|
||||
type target struct {
|
||||
name string
|
||||
debname string
|
||||
debdeps []string
|
||||
debpre string
|
||||
debpost string
|
||||
description string
|
||||
buildPkg string
|
||||
binaryName string
|
||||
archiveFiles []archiveFile
|
||||
systemdServices []string
|
||||
installationFiles []archiveFile
|
||||
tags []string
|
||||
}
|
||||
@@ -128,6 +129,7 @@ var targets = map[string]target{
|
||||
name: "stdiscosrv",
|
||||
debname: "syncthing-discosrv",
|
||||
debdeps: []string{"libc6"},
|
||||
debpre: "cmd/stdiscosrv/scripts/preinst",
|
||||
description: "Syncthing Discovery Server",
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/stdiscosrv",
|
||||
binaryName: "stdiscosrv", // .exe will be added automatically for Windows builds
|
||||
@@ -137,12 +139,17 @@ var targets = map[string]target{
|
||||
{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
|
||||
},
|
||||
systemdServices: []string{
|
||||
"cmd/stdiscosrv/etc/linux-systemd/stdiscosrv.service",
|
||||
},
|
||||
installationFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
|
||||
{src: "cmd/stdiscosrv/README.md", dst: "deb/usr/share/doc/syncthing-discosrv/README.txt", perm: 0644},
|
||||
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing-discosrv/LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing-discosrv/AUTHORS.txt", perm: 0644},
|
||||
{src: "man/stdiscosrv.1", dst: "deb/usr/share/man/man1/stdiscosrv.1", perm: 0644},
|
||||
{src: "cmd/stdiscosrv/etc/linux-systemd/default", dst: "deb/etc/default/syncthing-discosrv", perm: 0644},
|
||||
{src: "cmd/stdiscosrv/etc/firewall-ufw/stdiscosrv", dst: "deb/etc/ufw/applications.d/stdiscosrv", perm: 0644},
|
||||
},
|
||||
tags: []string{"purego"},
|
||||
},
|
||||
@@ -150,6 +157,7 @@ var targets = map[string]target{
|
||||
name: "strelaysrv",
|
||||
debname: "syncthing-relaysrv",
|
||||
debdeps: []string{"libc6"},
|
||||
debpre: "cmd/strelaysrv/scripts/preinst",
|
||||
description: "Syncthing Relay Server",
|
||||
buildPkg: "github.com/syncthing/syncthing/cmd/strelaysrv",
|
||||
binaryName: "strelaysrv", // .exe will be added automatically for Windows builds
|
||||
@@ -160,6 +168,9 @@ var targets = map[string]target{
|
||||
{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
|
||||
},
|
||||
systemdServices: []string{
|
||||
"cmd/strelaysrv/etc/linux-systemd/strelaysrv.service",
|
||||
},
|
||||
installationFiles: []archiveFile{
|
||||
{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
|
||||
{src: "cmd/strelaysrv/README.md", dst: "deb/usr/share/doc/syncthing-relaysrv/README.txt", perm: 0644},
|
||||
@@ -167,6 +178,8 @@ var targets = map[string]target{
|
||||
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing-relaysrv/LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing-relaysrv/AUTHORS.txt", perm: 0644},
|
||||
{src: "man/strelaysrv.1", dst: "deb/usr/share/man/man1/strelaysrv.1", perm: 0644},
|
||||
{src: "cmd/strelaysrv/etc/linux-systemd/default", dst: "deb/etc/default/syncthing-relaysrv", perm: 0644},
|
||||
{src: "cmd/strelaysrv/etc/firewall-ufw/strelaysrv", dst: "deb/etc/ufw/applications.d/strelaysrv", perm: 0644},
|
||||
},
|
||||
},
|
||||
"strelaypoolsrv": {
|
||||
@@ -200,7 +213,6 @@ type dependencyRepo struct {
|
||||
}
|
||||
|
||||
var dependencyRepos = []dependencyRepo{
|
||||
{path: "protobuf", repo: "https://github.com/gogo/protobuf.git", commit: gogoProtoVersion},
|
||||
{path: "xdr", repo: "https://github.com/calmh/xdr.git", commit: "08e072f9cb16"},
|
||||
}
|
||||
|
||||
@@ -555,9 +567,15 @@ func buildDeb(target target) {
|
||||
for _, dep := range target.debdeps {
|
||||
args = append(args, "-d", dep)
|
||||
}
|
||||
for _, service := range target.systemdServices {
|
||||
args = append(args, "--deb-systemd", service)
|
||||
}
|
||||
if target.debpost != "" {
|
||||
args = append(args, "--after-upgrade", target.debpost)
|
||||
}
|
||||
if target.debpre != "" {
|
||||
args = append(args, "--before-install", target.debpre)
|
||||
}
|
||||
runPrint("fpm", args...)
|
||||
}
|
||||
|
||||
@@ -736,14 +754,21 @@ func shouldRebuildAssets(target, srcdir string) bool {
|
||||
}
|
||||
|
||||
func proto() {
|
||||
runPrint(goCmd, "get", fmt.Sprintf("github.com/gogo/protobuf/protoc-gen-gogofast@%v", gogoProtoVersion))
|
||||
pv := protobufVersion()
|
||||
dependencyRepos = append(dependencyRepos,
|
||||
dependencyRepo{path: "protobuf", repo: "https://github.com/gogo/protobuf.git", commit: pv},
|
||||
)
|
||||
|
||||
runPrint(goCmd, "get", fmt.Sprintf("github.com/gogo/protobuf/protoc-gen-gogofast@%v", pv))
|
||||
os.MkdirAll("repos", 0755)
|
||||
for _, dep := range dependencyRepos {
|
||||
path := filepath.Join("repos", dep.path)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
runPrintInDir("repos", "git", "clone", dep.repo, dep.path)
|
||||
runPrintInDir(path, "git", "checkout", dep.commit)
|
||||
} else {
|
||||
runPrintInDir(path, "git", "fetch")
|
||||
}
|
||||
runPrintInDir(path, "git", "checkout", dep.commit)
|
||||
}
|
||||
runPrint(goCmd, "generate", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/stdiscosrv")
|
||||
}
|
||||
@@ -776,6 +801,9 @@ func ldflags() string {
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp%c%d", sep, buildStamp())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User%c%s", sep, buildUser())
|
||||
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Host%c%s", sep, buildHost())
|
||||
if v := os.Getenv("EXTRA_LDFLAGS"); v != "" {
|
||||
fmt.Fprintf(b, " %s", v);
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -1232,3 +1260,11 @@ func (t target) BinaryName() string {
|
||||
}
|
||||
return t.binaryName
|
||||
}
|
||||
|
||||
func protobufVersion() string {
|
||||
bs, err := runError(goCmd, "list", "-f", "{{.Version}}", "-m", "github.com/gogo/protobuf")
|
||||
if err != nil {
|
||||
log.Fatal("Getting protobuf version:", err)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/urfave/cli"
|
||||
@@ -85,7 +86,7 @@ func main() {
|
||||
myID := protocol.NewDeviceID(cert.Certificate[0])
|
||||
|
||||
// Load the config
|
||||
cfg, err := config.Load(locations.Get(locations.ConfigFile), myID)
|
||||
cfg, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
|
||||
if err != nil {
|
||||
log.Fatalln(errors.Wrap(err, "loading config"))
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -52,12 +54,12 @@ func (r *crashReceiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// The final path component should be a SHA256 hash in hex, so 64 hex
|
||||
// characters. We don't care about case on the request but use lower
|
||||
// case internally.
|
||||
base := strings.ToLower(path.Base(req.URL.Path))
|
||||
if len(base) != 64 {
|
||||
reportID := strings.ToLower(path.Base(req.URL.Path))
|
||||
if len(reportID) != 64 {
|
||||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
for _, c := range base {
|
||||
for _, c := range reportID {
|
||||
if c >= 'a' && c <= 'f' {
|
||||
continue
|
||||
}
|
||||
@@ -68,40 +70,57 @@ func (r *crashReceiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// The location of the report on disk, compressed
|
||||
fullPath := filepath.Join(r.dir, r.dirFor(reportID), reportID) + ".gz"
|
||||
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
r.serveGet(fullPath, w, req)
|
||||
case http.MethodHead:
|
||||
r.serveHead(base, w, req)
|
||||
r.serveHead(fullPath, w, req)
|
||||
case http.MethodPut:
|
||||
r.servePut(base, w, req)
|
||||
r.servePut(reportID, fullPath, w, req)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// serveGet responds to GET requests by serving the uncompressed report.
|
||||
func (r *crashReceiver) serveGet(fullPath string, w http.ResponseWriter, _ *http.Request) {
|
||||
fd, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
gr, err := gzip.NewReader(fd)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, _ = io.Copy(w, gr) // best effort
|
||||
}
|
||||
|
||||
// serveHead responds to HEAD requests by checking if the named report
|
||||
// already exists in the system.
|
||||
func (r *crashReceiver) serveHead(base string, w http.ResponseWriter, _ *http.Request) {
|
||||
path := filepath.Join(r.dirFor(base), base)
|
||||
if _, err := os.Lstat(path); err != nil {
|
||||
func (r *crashReceiver) serveHead(fullPath string, w http.ResponseWriter, _ *http.Request) {
|
||||
if _, err := os.Lstat(fullPath); err != nil {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
}
|
||||
// 200 OK
|
||||
}
|
||||
|
||||
// servePut accepts and stores the given report.
|
||||
func (r *crashReceiver) servePut(base string, w http.ResponseWriter, req *http.Request) {
|
||||
path := filepath.Join(r.dirFor(base), base)
|
||||
fullPath := filepath.Join(r.dir, path)
|
||||
|
||||
func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWriter, req *http.Request) {
|
||||
// Ensure the destination directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
|
||||
log.Printf("Creating directory for report %s: %v", base, err)
|
||||
log.Println("Creating directory:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Read at most maxRequestSize of report data.
|
||||
log.Println("Receiving report", base)
|
||||
log.Println("Receiving report", reportID)
|
||||
lr := io.LimitReader(req.Body, maxRequestSize)
|
||||
bs, err := ioutil.ReadAll(lr)
|
||||
if err != nil {
|
||||
@@ -110,10 +129,16 @@ func (r *crashReceiver) servePut(base string, w http.ResponseWriter, req *http.R
|
||||
return
|
||||
}
|
||||
|
||||
// Create an output file
|
||||
err = ioutil.WriteFile(fullPath, bs, 0644)
|
||||
// Compress the report for storage
|
||||
buf := new(bytes.Buffer)
|
||||
gw := gzip.NewWriter(buf)
|
||||
_, _ = gw.Write(bs) // can't fail
|
||||
gw.Close()
|
||||
|
||||
// Create an output file with the compressed report
|
||||
err = ioutil.WriteFile(fullPath, buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Printf("Creating file for report %s: %v", base, err)
|
||||
log.Println("Saving report:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -122,7 +147,7 @@ func (r *crashReceiver) servePut(base string, w http.ResponseWriter, req *http.R
|
||||
if r.dsn != "" {
|
||||
go func() {
|
||||
// There's no need for the client to have to wait for this part.
|
||||
if err := sendReport(r.dsn, path, bs); err != nil {
|
||||
if err := sendReport(r.dsn, reportID, bs); err != nil {
|
||||
log.Println("Failed to send report:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
package main
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
import io "io"
|
||||
import (
|
||||
fmt "fmt"
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
@@ -19,7 +21,7 @@ var _ = math.Inf
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type DatabaseRecord struct {
|
||||
Addresses []DatabaseAddress `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses"`
|
||||
@@ -32,7 +34,7 @@ func (m *DatabaseRecord) Reset() { *m = DatabaseRecord{} }
|
||||
func (m *DatabaseRecord) String() string { return proto.CompactTextString(m) }
|
||||
func (*DatabaseRecord) ProtoMessage() {}
|
||||
func (*DatabaseRecord) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_database_0f49e029703a04f5, []int{0}
|
||||
return fileDescriptor_b90fe3356ea5df07, []int{0}
|
||||
}
|
||||
func (m *DatabaseRecord) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -42,15 +44,15 @@ func (m *DatabaseRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, erro
|
||||
return xxx_messageInfo_DatabaseRecord.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *DatabaseRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseRecord.Merge(dst, src)
|
||||
func (m *DatabaseRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseRecord.Merge(m, src)
|
||||
}
|
||||
func (m *DatabaseRecord) XXX_Size() int {
|
||||
return m.Size()
|
||||
@@ -71,7 +73,7 @@ func (m *ReplicationRecord) Reset() { *m = ReplicationRecord{} }
|
||||
func (m *ReplicationRecord) String() string { return proto.CompactTextString(m) }
|
||||
func (*ReplicationRecord) ProtoMessage() {}
|
||||
func (*ReplicationRecord) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_database_0f49e029703a04f5, []int{1}
|
||||
return fileDescriptor_b90fe3356ea5df07, []int{1}
|
||||
}
|
||||
func (m *ReplicationRecord) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -81,15 +83,15 @@ func (m *ReplicationRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, e
|
||||
return xxx_messageInfo_ReplicationRecord.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *ReplicationRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ReplicationRecord.Merge(dst, src)
|
||||
func (m *ReplicationRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ReplicationRecord.Merge(m, src)
|
||||
}
|
||||
func (m *ReplicationRecord) XXX_Size() int {
|
||||
return m.Size()
|
||||
@@ -109,7 +111,7 @@ func (m *DatabaseAddress) Reset() { *m = DatabaseAddress{} }
|
||||
func (m *DatabaseAddress) String() string { return proto.CompactTextString(m) }
|
||||
func (*DatabaseAddress) ProtoMessage() {}
|
||||
func (*DatabaseAddress) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_database_0f49e029703a04f5, []int{2}
|
||||
return fileDescriptor_b90fe3356ea5df07, []int{2}
|
||||
}
|
||||
func (m *DatabaseAddress) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -119,15 +121,15 @@ func (m *DatabaseAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, err
|
||||
return xxx_messageInfo_DatabaseAddress.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *DatabaseAddress) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseAddress.Merge(dst, src)
|
||||
func (m *DatabaseAddress) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DatabaseAddress.Merge(m, src)
|
||||
}
|
||||
func (m *DatabaseAddress) XXX_Size() int {
|
||||
return m.Size()
|
||||
@@ -143,10 +145,34 @@ func init() {
|
||||
proto.RegisterType((*ReplicationRecord)(nil), "main.ReplicationRecord")
|
||||
proto.RegisterType((*DatabaseAddress)(nil), "main.DatabaseAddress")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("database.proto", fileDescriptor_b90fe3356ea5df07) }
|
||||
|
||||
var fileDescriptor_b90fe3356ea5df07 = []byte{
|
||||
// 270 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x41, 0x4a, 0xc4, 0x30,
|
||||
0x18, 0x85, 0x9b, 0x49, 0x1d, 0x99, 0x08, 0xa3, 0x06, 0x94, 0x20, 0x12, 0x4b, 0xdd, 0x74, 0xd5,
|
||||
0x01, 0x5d, 0xb9, 0x74, 0xd0, 0x0b, 0xe4, 0x06, 0xe9, 0xe4, 0x77, 0x08, 0x3a, 0x4d, 0x49, 0x2a,
|
||||
0xe8, 0x29, 0xf4, 0x58, 0x5d, 0xce, 0xd2, 0x95, 0x68, 0x7b, 0x11, 0x69, 0x26, 0x55, 0x14, 0x37,
|
||||
0xb3, 0x7b, 0xdf, 0xff, 0xbf, 0x97, 0xbc, 0x84, 0x4c, 0x95, 0xac, 0x65, 0x21, 0x1d, 0xe4, 0x95,
|
||||
0x35, 0xb5, 0xa1, 0xf1, 0x4a, 0xea, 0xf2, 0xe4, 0xdc, 0x42, 0x65, 0xdc, 0xcc, 0x8f, 0x8a, 0xc7,
|
||||
0xbb, 0xd9, 0xd2, 0x2c, 0x8d, 0x07, 0xaf, 0x36, 0xd6, 0xf4, 0x05, 0x91, 0xe9, 0x4d, 0x48, 0x0b,
|
||||
0x58, 0x18, 0xab, 0xe8, 0x15, 0x99, 0x48, 0xa5, 0x2c, 0x38, 0x07, 0x8e, 0xa1, 0x04, 0x67, 0x7b,
|
||||
0x17, 0x47, 0x79, 0x7f, 0x62, 0x3e, 0x18, 0xaf, 0x37, 0xeb, 0x79, 0xdc, 0xbc, 0x9f, 0x45, 0xe2,
|
||||
0xc7, 0x4d, 0x8f, 0xc9, 0x78, 0xa5, 0x7d, 0x6e, 0x94, 0xa0, 0x6c, 0x47, 0x04, 0xa2, 0x94, 0xc4,
|
||||
0x0e, 0xa0, 0x64, 0x38, 0x41, 0x19, 0x16, 0x5e, 0x7f, 0x7b, 0x15, 0x8b, 0xfd, 0x34, 0x50, 0x5a,
|
||||
0x93, 0x43, 0x01, 0xd5, 0x83, 0x5e, 0xc8, 0x5a, 0x9b, 0x32, 0x74, 0x3a, 0x20, 0xf8, 0x1e, 0x9e,
|
||||
0x19, 0x4a, 0x50, 0x36, 0x11, 0xbd, 0xfc, 0xdd, 0x72, 0xb4, 0x55, 0xcb, 0x7f, 0xda, 0xa4, 0xb7,
|
||||
0x64, 0xff, 0x4f, 0x8e, 0x32, 0xb2, 0x1b, 0x32, 0xe1, 0xde, 0x01, 0xfb, 0x0d, 0x3c, 0x55, 0xda,
|
||||
0x86, 0x77, 0x62, 0x31, 0xe0, 0xfc, 0xb4, 0xf9, 0xe4, 0x51, 0xd3, 0x72, 0xb4, 0x6e, 0x39, 0xfa,
|
||||
0x68, 0x39, 0x7a, 0xed, 0x78, 0xb4, 0xee, 0x78, 0xf4, 0xd6, 0xf1, 0xa8, 0x18, 0xfb, 0x3f, 0xbf,
|
||||
0xfc, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xa2, 0xf6, 0x1e, 0xb0, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *DatabaseRecord) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,44 +180,51 @@ func (m *DatabaseRecord) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *DatabaseRecord) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *DatabaseRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Addresses) > 0 {
|
||||
for _, msg := range m.Addresses {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if m.Misses != 0 {
|
||||
dAtA[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Misses))
|
||||
if m.Missed != 0 {
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Missed))
|
||||
i--
|
||||
dAtA[i] = 0x20
|
||||
}
|
||||
if m.Seen != 0 {
|
||||
dAtA[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if m.Missed != 0 {
|
||||
dAtA[i] = 0x20
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Missed))
|
||||
if m.Misses != 0 {
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Misses))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
return i, nil
|
||||
if len(m.Addresses) > 0 {
|
||||
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.Addresses[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ReplicationRecord) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,40 +232,48 @@ func (m *ReplicationRecord) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *ReplicationRecord) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ReplicationRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Key) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Key)))
|
||||
i += copy(dAtA[i:], m.Key)
|
||||
if m.Seen != 0 {
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if len(m.Addresses) > 0 {
|
||||
for _, msg := range m.Addresses {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.Addresses[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(size))
|
||||
}
|
||||
i += n
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
}
|
||||
if m.Seen != 0 {
|
||||
dAtA[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
|
||||
if len(m.Key) > 0 {
|
||||
i -= len(m.Key)
|
||||
copy(dAtA[i:], m.Key)
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Key)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return i, nil
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *DatabaseAddress) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -240,32 +281,40 @@ func (m *DatabaseAddress) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *DatabaseAddress) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *DatabaseAddress) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Address) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Address)))
|
||||
i += copy(dAtA[i:], m.Address)
|
||||
}
|
||||
if m.Expires != 0 {
|
||||
dAtA[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(m.Expires))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
return i, nil
|
||||
if len(m.Address) > 0 {
|
||||
i -= len(m.Address)
|
||||
copy(dAtA[i:], m.Address)
|
||||
i = encodeVarintDatabase(dAtA, i, uint64(len(m.Address)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintDatabase(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovDatabase(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
return base
|
||||
}
|
||||
func (m *DatabaseRecord) Size() (n int) {
|
||||
if m == nil {
|
||||
@@ -330,14 +379,7 @@ func (m *DatabaseAddress) Size() (n int) {
|
||||
}
|
||||
|
||||
func sovDatabase(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozDatabase(x uint64) (n int) {
|
||||
return sovDatabase(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
@@ -357,7 +399,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -385,7 +427,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -394,6 +436,9 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -416,7 +461,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Misses |= (int32(b) & 0x7F) << shift
|
||||
m.Misses |= int32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -435,7 +480,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Seen |= (int64(b) & 0x7F) << shift
|
||||
m.Seen |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -454,7 +499,7 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Missed |= (int64(b) & 0x7F) << shift
|
||||
m.Missed |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -468,6 +513,9 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -495,7 +543,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -523,7 +571,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -533,6 +581,9 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -552,7 +603,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -561,6 +612,9 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -583,7 +637,7 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Seen |= (int64(b) & 0x7F) << shift
|
||||
m.Seen |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -597,6 +651,9 @@ func (m *ReplicationRecord) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -624,7 +681,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -652,7 +709,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -662,6 +719,9 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -681,7 +741,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Expires |= (int64(b) & 0x7F) << shift
|
||||
m.Expires |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -695,6 +755,9 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDatabase
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -761,10 +824,13 @@ func skipDatabase(dAtA []byte) (n int, err error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthDatabase
|
||||
}
|
||||
iNdEx += length
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthDatabase
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
@@ -793,6 +859,9 @@ func skipDatabase(dAtA []byte) (n int, err error) {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthDatabase
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
@@ -811,26 +880,3 @@ var (
|
||||
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("database.proto", fileDescriptor_database_0f49e029703a04f5) }
|
||||
|
||||
var fileDescriptor_database_0f49e029703a04f5 = []byte{
|
||||
// 270 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x41, 0x4a, 0xc4, 0x30,
|
||||
0x18, 0x85, 0x9b, 0x49, 0x1d, 0x99, 0x08, 0xa3, 0x06, 0x94, 0x20, 0x12, 0x4b, 0xdd, 0x74, 0xd5,
|
||||
0x01, 0x5d, 0xb9, 0x74, 0xd0, 0x0b, 0xe4, 0x06, 0xe9, 0xe4, 0x77, 0x08, 0x3a, 0x4d, 0x49, 0x2a,
|
||||
0xe8, 0x29, 0xf4, 0x58, 0x5d, 0xce, 0xd2, 0x95, 0x68, 0x7b, 0x11, 0x69, 0x26, 0x55, 0x14, 0x37,
|
||||
0xb3, 0x7b, 0xdf, 0xff, 0xbf, 0x97, 0xbc, 0x84, 0x4c, 0x95, 0xac, 0x65, 0x21, 0x1d, 0xe4, 0x95,
|
||||
0x35, 0xb5, 0xa1, 0xf1, 0x4a, 0xea, 0xf2, 0xe4, 0xdc, 0x42, 0x65, 0xdc, 0xcc, 0x8f, 0x8a, 0xc7,
|
||||
0xbb, 0xd9, 0xd2, 0x2c, 0x8d, 0x07, 0xaf, 0x36, 0xd6, 0xf4, 0x05, 0x91, 0xe9, 0x4d, 0x48, 0x0b,
|
||||
0x58, 0x18, 0xab, 0xe8, 0x15, 0x99, 0x48, 0xa5, 0x2c, 0x38, 0x07, 0x8e, 0xa1, 0x04, 0x67, 0x7b,
|
||||
0x17, 0x47, 0x79, 0x7f, 0x62, 0x3e, 0x18, 0xaf, 0x37, 0xeb, 0x79, 0xdc, 0xbc, 0x9f, 0x45, 0xe2,
|
||||
0xc7, 0x4d, 0x8f, 0xc9, 0x78, 0xa5, 0x7d, 0x6e, 0x94, 0xa0, 0x6c, 0x47, 0x04, 0xa2, 0x94, 0xc4,
|
||||
0x0e, 0xa0, 0x64, 0x38, 0x41, 0x19, 0x16, 0x5e, 0x7f, 0x7b, 0x15, 0x8b, 0xfd, 0x34, 0x50, 0x5a,
|
||||
0x93, 0x43, 0x01, 0xd5, 0x83, 0x5e, 0xc8, 0x5a, 0x9b, 0x32, 0x74, 0x3a, 0x20, 0xf8, 0x1e, 0x9e,
|
||||
0x19, 0x4a, 0x50, 0x36, 0x11, 0xbd, 0xfc, 0xdd, 0x72, 0xb4, 0x55, 0xcb, 0x7f, 0xda, 0xa4, 0xb7,
|
||||
0x64, 0xff, 0x4f, 0x8e, 0x32, 0xb2, 0x1b, 0x32, 0xe1, 0xde, 0x01, 0xfb, 0x0d, 0x3c, 0x55, 0xda,
|
||||
0x86, 0x77, 0x62, 0x31, 0xe0, 0xfc, 0xb4, 0xf9, 0xe4, 0x51, 0xd3, 0x72, 0xb4, 0x6e, 0x39, 0xfa,
|
||||
0x68, 0x39, 0x7a, 0xed, 0x78, 0xb4, 0xee, 0x78, 0xf4, 0xd6, 0xf1, 0xa8, 0x18, 0xfb, 0x3f, 0xbf,
|
||||
0xfc, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xa2, 0xf6, 0x1e, 0xb0, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
4
cmd/stdiscosrv/etc/firewall-ufw/stdiscosrv
Normal file
4
cmd/stdiscosrv/etc/firewall-ufw/stdiscosrv
Normal file
@@ -0,0 +1,4 @@
|
||||
[stdiscosrv]
|
||||
title=Syncthing discovery server
|
||||
description=Lets syncthing clients discover each other
|
||||
ports=8443/tcp
|
||||
3
cmd/stdiscosrv/etc/linux-systemd/default
Normal file
3
cmd/stdiscosrv/etc/linux-systemd/default
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default settings for syncthing-relaysrv (strelaysrv).
|
||||
## Add Options here:
|
||||
DISCOSRV_OPTS=
|
||||
25
cmd/stdiscosrv/etc/linux-systemd/stdiscosrv.service
Normal file
25
cmd/stdiscosrv/etc/linux-systemd/stdiscosrv.service
Normal file
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=Syncthing Discovery Server
|
||||
After=network.target
|
||||
Documentation=man:stdiscosrv(1)
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/lib/syncthing-discosrv
|
||||
EnvironmentFile=/etc/default/syncthing-discosrv
|
||||
ExecStart=/usr/bin/stdiscosrv $DISCOSRV_OPTS
|
||||
|
||||
# Hardening
|
||||
User=syncthing-discosrv
|
||||
Group=syncthing
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/lib/syncthing-discosrv
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=syncthing-discosrv.service
|
||||
4
cmd/stdiscosrv/scripts/preinst
Normal file
4
cmd/stdiscosrv/scripts/preinst
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
addgroup --system syncthing
|
||||
adduser --system --home /var/lib/syncthing-discosrv --ingroup syncthing syncthing-discosrv
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/discover"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
@@ -82,7 +83,7 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
|
||||
}
|
||||
|
||||
func checkServer(deviceID protocol.DeviceID, server string) checkResult {
|
||||
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil)
|
||||
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, events.NoopLogger)
|
||||
if err != nil {
|
||||
return checkResult{error: err}
|
||||
}
|
||||
|
||||
9
cmd/strelaysrv/etc/firewall-ufw/strelaysrv
Normal file
9
cmd/strelaysrv/etc/firewall-ufw/strelaysrv
Normal file
@@ -0,0 +1,9 @@
|
||||
[strelaysrv]
|
||||
title=Syncthing relay server
|
||||
description=Proxies traffic of syncthing client behind firewalls
|
||||
ports=22067/tcp
|
||||
|
||||
[strelaysrv-metrics]
|
||||
title=Syncthing relay metrics
|
||||
description=Provides metrics about the syncthing relay server
|
||||
ports=22070/tcp
|
||||
5
cmd/strelaysrv/etc/linux-systemd/default
Normal file
5
cmd/strelaysrv/etc/linux-systemd/default
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default settings for syncthing-relaysrv (strelaysrv).
|
||||
NAT=true
|
||||
|
||||
## Add Options here:
|
||||
RELAYSRV_OPTS=
|
||||
@@ -1,17 +1,25 @@
|
||||
[Unit]
|
||||
Description=Syncthing relay server
|
||||
Description=Syncthing Relay Server
|
||||
After=network.target
|
||||
Documentation=man:strelaysrv(1)
|
||||
|
||||
[Service]
|
||||
User=strelaysrv
|
||||
Group=strelaysrv
|
||||
ExecStart=/usr/bin/strelaysrv
|
||||
WorkingDirectory=/var/lib/strelaysrv
|
||||
WorkingDirectory=/var/lib/syncthing-relaysrv
|
||||
EnvironmentFile=/etc/default/syncthing-relaysrv
|
||||
ExecStart=/usr/bin/strelaysrv -nat=${NAT} $RELAYSRV_OPTS
|
||||
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
# Hardening
|
||||
User=syncthing-relaysrv
|
||||
Group=syncthing
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/lib/syncthing-relaysrv
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=syncthing-relaysrv.service
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/relay/protocol"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -194,7 +195,7 @@ func main() {
|
||||
log.Println("ID:", id)
|
||||
}
|
||||
|
||||
wrapper := config.Wrap("config", config.New(id))
|
||||
wrapper := config.Wrap("config", config.New(id), events.NoopLogger)
|
||||
wrapper.SetOptions(config.OptionsConfiguration{
|
||||
NATLeaseM: natLease,
|
||||
NATRenewalM: natRenewal,
|
||||
|
||||
4
cmd/strelaysrv/scripts/preinst
Normal file
4
cmd/strelaysrv/scripts/preinst
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
addgroup --system syncthing
|
||||
adduser --system --home /var/lib/syncthing-relaysrv --ingroup syncthing syncthing-relaysrv
|
||||
57
cmd/stupgrades/main.go
Normal file
57
cmd/stupgrades/main.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2019 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
)
|
||||
|
||||
const defaultURL = "https://api.github.com/repos/syncthing/syncthing/releases?per_page=25"
|
||||
|
||||
func main() {
|
||||
url := flag.String("u", defaultURL, "GitHub releases url")
|
||||
flag.Parse()
|
||||
|
||||
rels := upgrade.FetchLatestReleases(*url, "")
|
||||
if rels == nil {
|
||||
// An error was already logged
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sort.Sort(upgrade.SortByRelease(rels))
|
||||
rels = filterForLatest(rels)
|
||||
|
||||
if err := json.NewEncoder(os.Stdout).Encode(rels); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// filterForLatest returns the latest stable and prerelease only. If the
|
||||
// stable version is newer (comes first in the list) there is no need to go
|
||||
// looking for a prerelease at all.
|
||||
func filterForLatest(rels []upgrade.Release) []upgrade.Release {
|
||||
var filtered []upgrade.Release
|
||||
var havePre bool
|
||||
for _, rel := range rels {
|
||||
if !rel.Prerelease {
|
||||
// We found a stable version, we're good now.
|
||||
filtered = append(filtered, rel)
|
||||
break
|
||||
}
|
||||
if rel.Prerelease && !havePre {
|
||||
// We remember the first prerelease we find.
|
||||
filtered = append(filtered, rel)
|
||||
havePre = true
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
@@ -22,11 +22,15 @@ func init() {
|
||||
panic("Couldn't find block profiler")
|
||||
}
|
||||
l.Debugln("Starting block profiling")
|
||||
go saveBlockingProfiles(profiler)
|
||||
go func() {
|
||||
err := saveBlockingProfiles(profiler) // Only returns on error
|
||||
l.Warnln("Block profiler failed:", err)
|
||||
panic("Block profiler failed")
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func saveBlockingProfiles(profiler *pprof.Profile) {
|
||||
func saveBlockingProfiles(profiler *pprof.Profile) error {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
|
||||
t0 := time.Now()
|
||||
@@ -35,16 +39,16 @@ func saveBlockingProfiles(profiler *pprof.Profile) {
|
||||
|
||||
fd, err := os.Create(fmt.Sprintf("block-%05d-%07d.pprof", syscall.Getpid(), startms))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = profiler.WriteTo(fd, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = fd.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,11 +23,15 @@ func init() {
|
||||
rate = i
|
||||
}
|
||||
l.Debugln("Starting heap profiling")
|
||||
go saveHeapProfiles(rate)
|
||||
go func() {
|
||||
err := saveHeapProfiles(rate) // Only returns on error
|
||||
l.Warnln("Heap profiler failed:", err)
|
||||
panic("Heap profiler failed")
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func saveHeapProfiles(rate int) {
|
||||
func saveHeapProfiles(rate int) error {
|
||||
runtime.MemProfileRate = rate
|
||||
var memstats, prevMemstats runtime.MemStats
|
||||
|
||||
@@ -38,21 +42,21 @@ func saveHeapProfiles(rate int) {
|
||||
if memstats.HeapInuse > prevMemstats.HeapInuse {
|
||||
fd, err := os.Create(name + ".tmp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = pprof.WriteHeapProfile(fd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
err = fd.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
os.Remove(name) // Error deliberately ignored
|
||||
err = os.Rename(name+".tmp", name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
prevMemstats = memstats
|
||||
|
||||
@@ -394,7 +394,7 @@ func main() {
|
||||
}
|
||||
|
||||
func openGUI(myID protocol.DeviceID) error {
|
||||
cfg, err := loadOrDefaultConfig(myID)
|
||||
cfg, err := loadOrDefaultConfig(myID, events.NoopLogger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -437,7 +437,7 @@ func generate(generateDir string) error {
|
||||
l.Warnln("Config exists; will not overwrite.")
|
||||
return nil
|
||||
}
|
||||
cfg, err := defaultConfig(cfgFile, myID)
|
||||
cfg, err := syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -471,7 +471,7 @@ func debugFacilities() string {
|
||||
}
|
||||
|
||||
func checkUpgrade() upgrade.Release {
|
||||
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID)
|
||||
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
|
||||
opts := cfg.Options()
|
||||
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
|
||||
if err != nil {
|
||||
@@ -491,7 +491,7 @@ func checkUpgrade() upgrade.Release {
|
||||
|
||||
func performUpgrade(release upgrade.Release) {
|
||||
// Use leveldb database locks to protect against concurrent upgrades
|
||||
_, err := syncthing.OpenGoleveldb(locations.Get(locations.Database))
|
||||
_, err := syncthing.OpenGoleveldb(locations.Get(locations.Database), config.TuningAuto)
|
||||
if err == nil {
|
||||
err = upgrade.To(release)
|
||||
if err != nil {
|
||||
@@ -512,7 +512,7 @@ func performUpgrade(release upgrade.Release) {
|
||||
}
|
||||
|
||||
func upgradeViaRest() error {
|
||||
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID)
|
||||
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
|
||||
u, err := url.Parse(cfg.GUI().URL())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -557,25 +557,20 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
l.Infoln(build.LongVersion)
|
||||
|
||||
// Ensure that we have a certificate and key.
|
||||
cert, err := tls.LoadX509KeyPair(
|
||||
cert, err := syncthing.LoadOrGenerateCertificate(
|
||||
locations.Get(locations.CertFile),
|
||||
locations.Get(locations.KeyFile),
|
||||
)
|
||||
if err != nil {
|
||||
l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName)
|
||||
cert, err = tlsutil.NewCertificate(
|
||||
locations.Get(locations.CertFile),
|
||||
locations.Get(locations.KeyFile),
|
||||
tlsDefaultCommonName,
|
||||
)
|
||||
if err != nil {
|
||||
l.Warnln("Failed to generate certificate:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
l.Warnln("Failed to load/generate certificate:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
myID := protocol.NewDeviceID(cert.Certificate[0])
|
||||
|
||||
cfg, err := loadConfigAtStartup(runtimeOptions.allowNewerConfig, myID)
|
||||
evLogger := events.NewLogger()
|
||||
go evLogger.Serve()
|
||||
defer evLogger.Stop()
|
||||
|
||||
cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
|
||||
if err != nil {
|
||||
l.Warnln("Failed to initialize config:", err)
|
||||
os.Exit(exitError)
|
||||
@@ -588,7 +583,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
}
|
||||
|
||||
dbFile := locations.Get(locations.Database)
|
||||
ldb, err := syncthing.OpenGoleveldb(dbFile)
|
||||
ldb, err := syncthing.OpenGoleveldb(dbFile, cfg.Options().DatabaseTuning)
|
||||
if err != nil {
|
||||
l.Warnln("Error opening database:", err)
|
||||
os.Exit(1)
|
||||
@@ -603,7 +598,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
appOpts.DeadlockTimeoutS = secs
|
||||
}
|
||||
|
||||
app := syncthing.New(cfg, ldb, cert, appOpts)
|
||||
app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
|
||||
|
||||
setupSignalHandling(app)
|
||||
|
||||
@@ -648,7 +643,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
if runtimeOptions.NoUpgrade {
|
||||
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
|
||||
} else {
|
||||
go autoUpgrade(cfg, app)
|
||||
go autoUpgrade(cfg, app, evLogger)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,79 +688,17 @@ func setupSignalHandling(app *syncthing.App) {
|
||||
}()
|
||||
}
|
||||
|
||||
func loadOrDefaultConfig(myID protocol.DeviceID) (config.Wrapper, error) {
|
||||
func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger) (config.Wrapper, error) {
|
||||
cfgFile := locations.Get(locations.ConfigFile)
|
||||
cfg, err := config.Load(cfgFile, myID)
|
||||
cfg, err := config.Load(cfgFile, myID, evLogger)
|
||||
|
||||
if err != nil {
|
||||
cfg, err = defaultConfig(cfgFile, myID)
|
||||
cfg, err = syncthing.DefaultConfig(cfgFile, myID, evLogger, noDefaultFolder)
|
||||
}
|
||||
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func loadConfigAtStartup(allowNewerConfig bool, myID protocol.DeviceID) (config.Wrapper, error) {
|
||||
cfgFile := locations.Get(locations.ConfigFile)
|
||||
cfg, err := config.Load(cfgFile, myID)
|
||||
if os.IsNotExist(err) {
|
||||
cfg, err = defaultConfig(cfgFile, myID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate default config")
|
||||
}
|
||||
err = cfg.Save()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to save default config")
|
||||
}
|
||||
l.Infof("Default config saved. Edit %s to taste (with Syncthing stopped) or use the GUI", cfg.ConfigPath())
|
||||
} else if err == io.EOF {
|
||||
return nil, errors.New("Failed to load config: unexpected end of file. Truncated or empty configuration?")
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load config")
|
||||
}
|
||||
|
||||
if cfg.RawCopy().OriginalVersion != config.CurrentVersion {
|
||||
if cfg.RawCopy().OriginalVersion == config.CurrentVersion+1101 {
|
||||
l.Infof("Now, THAT's what we call a config from the future! Don't worry. As long as you hit that wire with the connecting hook at precisely eighty-eight miles per hour the instant the lightning strikes the tower... everything will be fine.")
|
||||
}
|
||||
if cfg.RawCopy().OriginalVersion > config.CurrentVersion && !allowNewerConfig {
|
||||
return nil, fmt.Errorf("Config file version (%d) is newer than supported version (%d). If this is expected, use -allow-newer-config to override.", cfg.RawCopy().OriginalVersion, config.CurrentVersion)
|
||||
}
|
||||
err = archiveAndSaveConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "config archive")
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func archiveAndSaveConfig(cfg config.Wrapper) error {
|
||||
// Copy the existing config to an archive copy
|
||||
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion)
|
||||
l.Infoln("Archiving a copy of old config file format at:", archivePath)
|
||||
if err := copyFile(cfg.ConfigPath(), archivePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do a regular atomic config sve
|
||||
return cfg.Save()
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
bs, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(dst, bs, 0600); err != nil {
|
||||
// Attempt to clean up
|
||||
os.Remove(dst)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func auditWriter(auditFile string) io.Writer {
|
||||
var fd io.Writer
|
||||
var err error
|
||||
@@ -798,22 +731,6 @@ func auditWriter(auditFile string) io.Writer {
|
||||
return fd
|
||||
}
|
||||
|
||||
func defaultConfig(cfgFile string, myID protocol.DeviceID) (config.Wrapper, error) {
|
||||
newCfg, err := config.NewWithFreePorts(myID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if noDefaultFolder {
|
||||
l.Infoln("We will skip creation of a default folder on first start since the proper envvar is set")
|
||||
return config.Wrap(cfgFile, newCfg), nil
|
||||
}
|
||||
|
||||
newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
|
||||
l.Infoln("Default folder created and/or linked to new config")
|
||||
return config.Wrap(cfgFile, newCfg), nil
|
||||
}
|
||||
|
||||
func resetDB() error {
|
||||
return os.RemoveAll(locations.Get(locations.Database))
|
||||
}
|
||||
@@ -861,9 +778,9 @@ func standbyMonitor(app *syncthing.App) {
|
||||
}
|
||||
}
|
||||
|
||||
func autoUpgrade(cfg config.Wrapper, app *syncthing.App) {
|
||||
func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
|
||||
timer := time.NewTimer(0)
|
||||
sub := events.Default.Subscribe(events.DeviceConnected)
|
||||
sub := evLogger.Subscribe(events.DeviceConnected)
|
||||
for {
|
||||
select {
|
||||
case event := <-sub.C():
|
||||
@@ -885,7 +802,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App) {
|
||||
|
||||
rel, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
|
||||
if err == upgrade.ErrUpgradeUnsupported {
|
||||
events.Default.Unsubscribe(sub)
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
@@ -909,7 +826,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App) {
|
||||
timer.Reset(checkInterval)
|
||||
continue
|
||||
}
|
||||
events.Default.Unsubscribe(sub)
|
||||
sub.Unsubscribe()
|
||||
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
|
||||
time.Sleep(time.Minute)
|
||||
app.Stop(syncthing.ExitUpgrade)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
@@ -102,7 +103,8 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
l.Infoln("Starting syncthing")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
l.Warnln("Error starting the main Syncthing process:", err)
|
||||
panic("Error starting the main Syncthing process")
|
||||
}
|
||||
|
||||
stdoutMut.Lock()
|
||||
@@ -449,7 +451,7 @@ func childEnv() []string {
|
||||
// panicUploadMaxWait uploading panics...
|
||||
func maybeReportPanics() {
|
||||
// Try to get a config to see if/where panics should be reported.
|
||||
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID)
|
||||
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
|
||||
if err != nil {
|
||||
l.Warnln("Couldn't load config; not reporting crash")
|
||||
return
|
||||
|
||||
@@ -755,6 +755,8 @@ func main() {
|
||||
http.HandleFunc("/blockstats.json", withDB(db, blockStatsHandler))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
|
||||
go cacheRefresher(db)
|
||||
|
||||
err = srv.Serve(listener)
|
||||
if err != nil {
|
||||
log.Fatalln("https:", err)
|
||||
@@ -767,7 +769,31 @@ var (
|
||||
cacheMut sync.Mutex
|
||||
)
|
||||
|
||||
const maxCacheTime = 5 * 60 * time.Second
|
||||
const maxCacheTime = 15 * time.Minute
|
||||
|
||||
func cacheRefresher(db *sql.DB) {
|
||||
ticker := time.NewTicker(maxCacheTime - time.Minute)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
cacheMut.Lock()
|
||||
if err := refreshCacheLocked(db); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
cacheMut.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func refreshCacheLocked(db *sql.DB) error {
|
||||
rep := getReport(db)
|
||||
buf := new(bytes.Buffer)
|
||||
err := tpl.Execute(buf, rep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheData = buf.Bytes()
|
||||
cacheTime = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
@@ -775,16 +801,11 @@ func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
defer cacheMut.Unlock()
|
||||
|
||||
if time.Since(cacheTime) > maxCacheTime {
|
||||
rep := getReport(db)
|
||||
buf := new(bytes.Buffer)
|
||||
err := tpl.Execute(buf, rep)
|
||||
if err != nil {
|
||||
if err := refreshCacheLocked(db); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Template Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cacheData = buf.Bytes()
|
||||
cacheTime = time.Now()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
50
go.mod
50
go.mod
@@ -4,49 +4,53 @@ require (
|
||||
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362
|
||||
github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6
|
||||
github.com/AudriusButkevicius/recli v0.0.5
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e
|
||||
github.com/calmh/du v1.0.1
|
||||
github.com/calmh/xdr v1.1.0
|
||||
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
|
||||
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 // indirect
|
||||
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d
|
||||
github.com/gogo/protobuf v1.2.1
|
||||
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4
|
||||
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e
|
||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogo/protobuf v1.3.0
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
|
||||
github.com/golang/mock v1.3.1 // indirect
|
||||
github.com/jackpal/gateway v1.0.5
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/lucas-clemente/quic-go v0.11.2
|
||||
github.com/lucas-clemente/quic-go v0.12.0
|
||||
github.com/maruel/panicparse v1.3.0
|
||||
github.com/mattn/go-isatty v0.0.7
|
||||
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/minio/sha256-simd v0.1.0
|
||||
github.com/onsi/ginkgo v1.9.0 // indirect
|
||||
github.com/onsi/gomega v1.6.0 // indirect
|
||||
github.com/oschwald/geoip2-golang v1.3.0
|
||||
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.4.0 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v0.9.4
|
||||
github.com/rcrowley/go-metrics v0.0.0-20171128170426-e181e095bae9
|
||||
github.com/prometheus/client_golang v1.1.0
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
|
||||
github.com/prometheus/procfs v0.0.4 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563
|
||||
github.com/sasha-s/go-deadlock v0.2.0
|
||||
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf
|
||||
github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965
|
||||
github.com/thejerf/suture v3.0.2+incompatible
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/urfave/cli v1.21.0
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/ldap.v2 v2.5.1
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
||||
137
go.sum
137
go.sum
@@ -4,22 +4,27 @@ github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6 h1:Apvc
|
||||
github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6/go.mod h1:1N0EEx/irz4B1qV17wW82TFbjQrE7oX316Cki6eDY0Q=
|
||||
github.com/AudriusButkevicius/recli v0.0.5 h1:xUa55PvWTHBm17T6RvjElRO3y5tALpdceH86vhzQ5wg=
|
||||
github.com/AudriusButkevicius/recli v0.0.5/go.mod h1:Q2E26yc6RvWWEz/TJ/goUp6yXvipYdJI096hpoaqsNs=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e h1:2augTYh6E+XoNrrivZJBadpThP/dsvYKj0nzqfQ8tM4=
|
||||
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/calmh/du v1.0.1 h1:uDCrDbXVVPrzxSNRkpj6nqSfwrl5uRWH3zvrJgl7RRo=
|
||||
github.com/calmh/du v1.0.1/go.mod h1:pHNccp4cXQeyDaiV3S7t5GN+eGOgynF0VSLxJjk9tLU=
|
||||
github.com/calmh/xdr v1.1.0 h1:U/Dd4CXNLoo8EiQ4ulJUXkgO1/EyQLgDKLgpY1SOoJE=
|
||||
github.com/calmh/xdr v1.1.0/go.mod h1:E8sz2ByAdXC8MbANf1LCRYzedSnnc+/sXXJs/PVqoeg=
|
||||
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=
|
||||
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
|
||||
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 h1:UNOqI3EKhvbqV8f1Vm3NIwkrhq388sGCeAH2Op7w0rc=
|
||||
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 h1:8k9FLYBLKT+9v2HQJ/a95ZemmTx+/ltJcAiRhVushG8=
|
||||
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 h1:Wg96Dh0MLTanEaPO0OkGtUIaa2jOnShAIOVUIzRHUxo=
|
||||
@@ -37,31 +42,45 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d h1:IngNQgbqr5ZOU0exk395Szrvkzes9Ilk1fmJfkw7d+M=
|
||||
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4 h1:6o8aP0LGMKzo3NzwhhX6EJsiJ3ejmj+9yA/3p8Fjjlw=
|
||||
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e h1:lS8IitpqG4RkZbEDlZg5Z7FvBdWLVjSVfsPGOKafEkI=
|
||||
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
|
||||
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 h1:vE7J1m7cCpiRVEIr1B5ccDxRpbPsWT5JU3if2Di5nE4=
|
||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@@ -70,16 +89,13 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lucas-clemente/quic-go v0.11.2 h1:Mop0ac3zALaBR3wGs6j8OYe/tcFvFsxTUFMkE/7yUOI=
|
||||
github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/maruel/panicparse v1.2.1 h1:mNlHGiakrixj+AwF/qRpTwnj+zsWYPRLQ7wRqnJsfO0=
|
||||
github.com/maruel/panicparse v1.2.1/go.mod h1:vszMjr5QQ4F5FSRfraldcIA/BCw5xrdLL+zEcU2nRBs=
|
||||
github.com/lucas-clemente/quic-go v0.12.0 h1:TRbvZ6F++sofeGbh+Z2IIyIOhl8KyGnYuA06g2yrHdI=
|
||||
github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI=
|
||||
github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/maruel/panicparse v1.3.0 h1:1Ep/RaYoSL1r5rTILHQQbyzHG8T4UP5ZbQTYTo4bdDc=
|
||||
github.com/maruel/panicparse v1.3.0/go.mod h1:vszMjr5QQ4F5FSRfraldcIA/BCw5xrdLL+zEcU2nRBs=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
@@ -87,51 +103,67 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338 h1:USW1+zAUkUSvk097CAX/i8KR3r6f+DHNhk6Xe025Oyw=
|
||||
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.1.0 h1:U41/2erhAKcmSI14xh/ZTUdBPOzDOIfS93ibzUSl8KM=
|
||||
github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.9.0 h1:SZjF721BByVj8QH636/8S2DnX4n0Re3SteMmw3N+tzc=
|
||||
github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.6.0 h1:8XTW0fcJZEq9q+Upcyws4JSGua2MFysCL5xkaSgHc+M=
|
||||
github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8=
|
||||
github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
|
||||
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70 h1:XGLYUmodtNzThosQ8GkMvj9TiIB/uWsP8NfxKSa3aDc=
|
||||
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
||||
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59 h1:2pHcLyJYXivxVvpoCc29uo3GDU1qFfJ1ggXKGYMrM0E=
|
||||
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/oschwald/maxminddb-golang v1.4.0 h1:5/rpmW41qrgSed4wK32rdznbkTSXHcraY2LOMJX4DMc=
|
||||
github.com/oschwald/maxminddb-golang v1.4.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.4 h1:Y8E/JaaPbmFSW2V81Ab/d8yZFYQQGbni1b1jPcG9Y6A=
|
||||
github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20171128170426-e181e095bae9 h1:jmLW6izPBVlIbk4d+XgK9+sChGbVKxxOPmd9eqRHCjw=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20171128170426-e181e095bae9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78=
|
||||
github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
|
||||
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
|
||||
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf h1:c9SV5NzG4KOk448TUE7iqCmb4E4y79CZF4zDdc1Jx3Q=
|
||||
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -147,23 +179,29 @@ github.com/thejerf/suture v3.0.2+incompatible h1:GtMydYcnK4zBJ0KL6Lx9vLzl6Oozb65
|
||||
github.com/thejerf/suture v3.0.2+incompatible/go.mod h1:ibKwrVj+Uzf3XZdAiNWUouPaAbSoemxOHLmJmwheEMc=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
|
||||
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -175,19 +213,24 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b h1:3X+R0qq1+64izd8es+EttB6qcY+JDlVmAhpRXl7gpzU=
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 h1:JBwmEvLfCqgPcIq8MjVMQxsF3LVL4XG/HH0qiG0+IFY=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -386,15 +386,7 @@ angular.module('syncthing.core')
|
||||
});
|
||||
});
|
||||
|
||||
// If we're not listening on localhost, and there is no
|
||||
// authentication configured, and the magic setting to silence the
|
||||
// warning isn't set, then yell at the user.
|
||||
var guiCfg = $scope.config.gui;
|
||||
$scope.openNoAuth = guiCfg.address.substr(0, 4) !== "127."
|
||||
&& guiCfg.address.substr(0, 6) !== "[::1]:"
|
||||
&& (!guiCfg.user || !guiCfg.password)
|
||||
&& guiCfg.authMode !== 'ldap'
|
||||
&& !guiCfg.insecureAdminAccess;
|
||||
refreshNoAuthWarning();
|
||||
|
||||
if (!hasConfig) {
|
||||
$scope.$emit('ConfigLoaded');
|
||||
@@ -427,10 +419,32 @@ angular.module('syncthing.core')
|
||||
}
|
||||
}
|
||||
$scope.discoveryFailed = discoveryFailed;
|
||||
|
||||
refreshNoAuthWarning();
|
||||
|
||||
console.log("refreshSystem", data);
|
||||
}).error($scope.emitHTTPError);
|
||||
}
|
||||
|
||||
function refreshNoAuthWarning() {
|
||||
if (!$scope.system || !$scope.config) {
|
||||
// We need both to be able to determine the state.
|
||||
return
|
||||
}
|
||||
|
||||
// If we're not listening on localhost, and there is no
|
||||
// authentication configured, and the magic setting to silence the
|
||||
// warning isn't set, then yell at the user.
|
||||
var addr = $scope.system.guiAddressUsed;
|
||||
var guiCfg = $scope.config.gui;
|
||||
$scope.openNoAuth = addr.substr(0, 4) !== "127."
|
||||
&& addr.substr(0, 6) !== "[::1]:"
|
||||
&& (!guiCfg.user || !guiCfg.password)
|
||||
&& guiCfg.authMode !== 'ldap'
|
||||
&& !guiCfg.insecureAdminAccess;
|
||||
}
|
||||
|
||||
|
||||
function refreshDiscoveryCache() {
|
||||
$http.get(urlbase + '/system/discovery').success(function (data) {
|
||||
for (var device in data) {
|
||||
@@ -1279,6 +1293,13 @@ angular.module('syncthing.core')
|
||||
$scope.protocolChanged = true;
|
||||
}
|
||||
|
||||
// Parse strings to arrays before copying over
|
||||
['listenAddresses', 'globalAnnounceServers'].forEach(function (key) {
|
||||
$scope.tmpOptions[key] = $scope.tmpOptions["_" + key + "Str"].split(/[ ,]+/).map(function (x) {
|
||||
return x.trim();
|
||||
});
|
||||
});
|
||||
|
||||
// Apply new settings locally
|
||||
$scope.thisDeviceIn($scope.tmpDevices).name = $scope.tmpOptions.deviceName;
|
||||
$scope.config.options = angular.copy($scope.tmpOptions);
|
||||
@@ -1292,12 +1313,6 @@ angular.module('syncthing.core')
|
||||
// here as well...
|
||||
$scope.devices = $scope.config.devices;
|
||||
|
||||
['listenAddresses', 'globalAnnounceServers'].forEach(function (key) {
|
||||
$scope.config.options[key] = $scope.config.options["_" + key + "Str"].split(/[ ,]+/).map(function (x) {
|
||||
return x.trim();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.saveConfig(function () {
|
||||
if (themeChanged) {
|
||||
document.location.reload(true);
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -70,6 +71,7 @@ type service struct {
|
||||
model model.Model
|
||||
eventSubs map[events.EventType]events.BufferedSubscription
|
||||
eventSubsMut sync.Mutex
|
||||
evLogger events.Logger
|
||||
discoverer discover.CachingMux
|
||||
connectionsService connections.Service
|
||||
fss model.FolderSummaryService
|
||||
@@ -79,7 +81,6 @@ type service struct {
|
||||
contr Controller
|
||||
noUpgrade bool
|
||||
tlsDefaultCommonName string
|
||||
stop chan struct{} // signals intentional stop
|
||||
configChanged chan struct{} // signals intentional listener close due to config change
|
||||
started chan string // signals startup complete by sending the listener address, for testing only
|
||||
startedOnce chan struct{} // the service has started successfully at least once
|
||||
@@ -105,7 +106,7 @@ type Service interface {
|
||||
WaitForStart() error
|
||||
}
|
||||
|
||||
func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, cpu Rater, contr Controller, noUpgrade bool) Service {
|
||||
func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, evLogger events.Logger, discoverer discover.CachingMux, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, cpu Rater, contr Controller, noUpgrade bool) Service {
|
||||
s := &service{
|
||||
id: id,
|
||||
cfg: cfg,
|
||||
@@ -116,6 +117,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
|
||||
DiskEventMask: diskSub,
|
||||
},
|
||||
eventSubsMut: sync.NewMutex(),
|
||||
evLogger: evLogger,
|
||||
discoverer: discoverer,
|
||||
connectionsService: connectionsService,
|
||||
fss: fss,
|
||||
@@ -308,14 +310,14 @@ func (s *service) serve(stop chan struct{}) {
|
||||
|
||||
// Wrap everything in CSRF protection. The /rest prefix should be
|
||||
// protected, other requests will grant cookies.
|
||||
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
|
||||
var handler http.Handler = newCsrfManager(s.id.String()[:5], "/rest", guiCfg, mux, locations.Get(locations.CsrfTokens))
|
||||
|
||||
// Add our version and ID as a header to responses
|
||||
handler = withDetailsMiddleware(s.id, handler)
|
||||
|
||||
// Wrap everything in basic auth, if user/password is set.
|
||||
if guiCfg.IsAuthEnabled() {
|
||||
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, s.cfg.LDAP(), handler)
|
||||
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, s.cfg.LDAP(), handler, s.evLogger)
|
||||
}
|
||||
|
||||
// Redirect to HTTPS if we are supposed to
|
||||
@@ -338,6 +340,9 @@ func (s *service) serve(stop chan struct{}) {
|
||||
// ReadTimeout must be longer than SyncthingController $scope.refresh
|
||||
// interval to avoid HTTP keepalive/GUI refresh race.
|
||||
ReadTimeout: 15 * time.Second,
|
||||
// Prevent the HTTP server from logging stuff on its own. The things we
|
||||
// care about we log ourselves from the handlers.
|
||||
ErrorLog: log.New(ioutil.Discard, "", 0),
|
||||
}
|
||||
|
||||
l.Infoln("GUI and API listening on", listener.Addr())
|
||||
@@ -375,6 +380,7 @@ func (s *service) serve(stop chan struct{}) {
|
||||
// Restart due to listen/serve failure
|
||||
l.Warnln("GUI/API:", err, "(restarting)")
|
||||
}
|
||||
srv.Close()
|
||||
}
|
||||
|
||||
// Complete implements suture.IsCompletable, which signifies to the supervisor
|
||||
@@ -907,6 +913,7 @@ func (s *service) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
res["uptime"] = s.urService.UptimeS()
|
||||
res["startTime"] = ur.StartTime
|
||||
res["guiAddressOverridden"] = s.cfg.GUI().IsOverridden()
|
||||
res["guiAddressUsed"] = s.cfg.GUI().Address()
|
||||
|
||||
sendJSON(w, res)
|
||||
}
|
||||
@@ -1211,7 +1218,7 @@ func (s *service) getEventSub(mask events.EventType) events.BufferedSubscription
|
||||
s.eventSubsMut.Lock()
|
||||
bufsub, ok := s.eventSubs[mask]
|
||||
if !ok {
|
||||
evsub := events.Default.Subscribe(mask)
|
||||
evsub := s.evLogger.Subscribe(mask)
|
||||
bufsub = events.NewBufferedSubscription(evsub, EventSubBufferSize)
|
||||
s.eventSubs[mask] = bufsub
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ var (
|
||||
sessionsMut = sync.NewMutex()
|
||||
)
|
||||
|
||||
func emitLoginAttempt(success bool, username string) {
|
||||
events.Default.Log(events.LoginAttempt, map[string]interface{}{
|
||||
func emitLoginAttempt(success bool, username string, evLogger events.Logger) {
|
||||
evLogger.Log(events.LoginAttempt, map[string]interface{}{
|
||||
"success": success,
|
||||
"username": username,
|
||||
})
|
||||
}
|
||||
|
||||
func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler) http.Handler {
|
||||
func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler, evLogger events.Logger) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if guiCfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
|
||||
next.ServeHTTP(w, r)
|
||||
@@ -94,7 +94,7 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
|
||||
}
|
||||
|
||||
if !authOk {
|
||||
emitLoginAttempt(false, username)
|
||||
emitLoginAttempt(false, username, evLogger)
|
||||
error()
|
||||
return
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
|
||||
MaxAge: 0,
|
||||
})
|
||||
|
||||
emitLoginAttempt(true, username)
|
||||
emitLoginAttempt(true, username, evLogger)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ func init() {
|
||||
}
|
||||
|
||||
func TestStaticAuthOK(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := authStatic("user", "pass", "user", string(passwordHashBytes))
|
||||
if !ok {
|
||||
t.Fatalf("should pass auth")
|
||||
@@ -26,6 +28,8 @@ func TestStaticAuthOK(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSimpleAuthUsernameFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := authStatic("userWRONG", "pass", "user", string(passwordHashBytes))
|
||||
if ok {
|
||||
t.Fatalf("should fail auth")
|
||||
@@ -33,6 +37,8 @@ func TestSimpleAuthUsernameFail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStaticAuthPasswordFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := authStatic("user", "passWRONG", "user", string(passwordHashBytes))
|
||||
if ok {
|
||||
t.Fatalf("should fail auth")
|
||||
|
||||
@@ -13,83 +13,103 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
// csrfTokens is a list of valid tokens. It is sorted so that the most
|
||||
// recently used token is first in the list. New tokens are added to the front
|
||||
// of the list (as it is the most recently used at that time). The list is
|
||||
// pruned to a maximum of maxCsrfTokens, throwing away the least recently used
|
||||
// tokens.
|
||||
var csrfTokens []string
|
||||
var csrfMut = sync.NewMutex()
|
||||
|
||||
const maxCsrfTokens = 25
|
||||
|
||||
type csrfManager struct {
|
||||
// tokens is a list of valid tokens. It is sorted so that the most
|
||||
// recently used token is first in the list. New tokens are added to the front
|
||||
// of the list (as it is the most recently used at that time). The list is
|
||||
// pruned to a maximum of maxCsrfTokens, throwing away the least recently used
|
||||
// tokens.
|
||||
tokens []string
|
||||
tokensMut sync.Mutex
|
||||
|
||||
unique string
|
||||
prefix string
|
||||
apiKeyValidator apiKeyValidator
|
||||
next http.Handler
|
||||
saveLocation string
|
||||
}
|
||||
|
||||
type apiKeyValidator interface {
|
||||
IsValidAPIKey(key string) bool
|
||||
}
|
||||
|
||||
// Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
|
||||
// the request with 403. For / and /index.html, set a new CSRF cookie if none
|
||||
// is currently set.
|
||||
func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||
loadCsrfTokens()
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Allow requests carrying a valid API key
|
||||
if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
|
||||
// Set the access-control-allow-origin header for CORS requests
|
||||
// since a valid API key has been provided
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/rest/debug") {
|
||||
// Debugging functions are only available when explicitly
|
||||
// enabled, and can be accessed without a CSRF token
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Allow requests for anything not under the protected path prefix,
|
||||
// and set a CSRF cookie if there isn't already a valid one.
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
cookie, err := r.Cookie("CSRF-Token-" + unique)
|
||||
if err != nil || !validCsrfToken(cookie.Value) {
|
||||
l.Debugln("new CSRF cookie in response to request for", r.URL)
|
||||
cookie = &http.Cookie{
|
||||
Name: "CSRF-Token-" + unique,
|
||||
Value: newCsrfToken(),
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the CSRF token
|
||||
token := r.Header.Get("X-CSRF-Token-" + unique)
|
||||
if !validCsrfToken(token) {
|
||||
http.Error(w, "CSRF Error", 403)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
func newCsrfManager(unique string, prefix string, apiKeyValidator apiKeyValidator, next http.Handler, saveLocation string) *csrfManager {
|
||||
m := &csrfManager{
|
||||
tokensMut: sync.NewMutex(),
|
||||
unique: unique,
|
||||
prefix: prefix,
|
||||
apiKeyValidator: apiKeyValidator,
|
||||
next: next,
|
||||
saveLocation: saveLocation,
|
||||
}
|
||||
m.load()
|
||||
return m
|
||||
}
|
||||
|
||||
func validCsrfToken(token string) bool {
|
||||
csrfMut.Lock()
|
||||
defer csrfMut.Unlock()
|
||||
for i, t := range csrfTokens {
|
||||
func (m *csrfManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Allow requests carrying a valid API key
|
||||
if m.apiKeyValidator.IsValidAPIKey(r.Header.Get("X-API-Key")) {
|
||||
// Set the access-control-allow-origin header for CORS requests
|
||||
// since a valid API key has been provided
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
m.next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/rest/debug") {
|
||||
// Debugging functions are only available when explicitly
|
||||
// enabled, and can be accessed without a CSRF token
|
||||
m.next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Allow requests for anything not under the protected path prefix,
|
||||
// and set a CSRF cookie if there isn't already a valid one.
|
||||
if !strings.HasPrefix(r.URL.Path, m.prefix) {
|
||||
cookie, err := r.Cookie("CSRF-Token-" + m.unique)
|
||||
if err != nil || !m.validToken(cookie.Value) {
|
||||
l.Debugln("new CSRF cookie in response to request for", r.URL)
|
||||
cookie = &http.Cookie{
|
||||
Name: "CSRF-Token-" + m.unique,
|
||||
Value: m.newToken(),
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
m.next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the CSRF token
|
||||
token := r.Header.Get("X-CSRF-Token-" + m.unique)
|
||||
if !m.validToken(token) {
|
||||
http.Error(w, "CSRF Error", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
m.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (m *csrfManager) validToken(token string) bool {
|
||||
m.tokensMut.Lock()
|
||||
defer m.tokensMut.Unlock()
|
||||
for i, t := range m.tokens {
|
||||
if t == token {
|
||||
if i > 0 {
|
||||
// Move this token to the head of the list. Copy the tokens at
|
||||
// the front one step to the right and then replace the token
|
||||
// at the head.
|
||||
copy(csrfTokens[1:], csrfTokens[:i+1])
|
||||
csrfTokens[0] = token
|
||||
copy(m.tokens[1:], m.tokens[:i+1])
|
||||
m.tokens[0] = token
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -97,40 +117,47 @@ func validCsrfToken(token string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func newCsrfToken() string {
|
||||
func (m *csrfManager) newToken() string {
|
||||
token := rand.String(32)
|
||||
|
||||
csrfMut.Lock()
|
||||
csrfTokens = append([]string{token}, csrfTokens...)
|
||||
if len(csrfTokens) > maxCsrfTokens {
|
||||
csrfTokens = csrfTokens[:maxCsrfTokens]
|
||||
m.tokensMut.Lock()
|
||||
m.tokens = append([]string{token}, m.tokens...)
|
||||
if len(m.tokens) > maxCsrfTokens {
|
||||
m.tokens = m.tokens[:maxCsrfTokens]
|
||||
}
|
||||
defer csrfMut.Unlock()
|
||||
defer m.tokensMut.Unlock()
|
||||
|
||||
saveCsrfTokens()
|
||||
m.save()
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
func saveCsrfTokens() {
|
||||
func (m *csrfManager) save() {
|
||||
// We're ignoring errors in here. It's not super critical and there's
|
||||
// nothing relevant we can do about them anyway...
|
||||
|
||||
name := locations.Get(locations.CsrfTokens)
|
||||
f, err := osutil.CreateAtomic(name)
|
||||
if m.saveLocation == "" {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := osutil.CreateAtomic(m.saveLocation)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range csrfTokens {
|
||||
for _, t := range m.tokens {
|
||||
fmt.Fprintln(f, t)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func loadCsrfTokens() {
|
||||
f, err := os.Open(locations.Get(locations.CsrfTokens))
|
||||
func (m *csrfManager) load() {
|
||||
if m.saveLocation == "" {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(m.saveLocation)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -138,6 +165,6 @@ func loadCsrfTokens() {
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
csrfTokens = append(csrfTokens, s.Text())
|
||||
m.tokens = append(m.tokens, s.Text())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -52,7 +53,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestCSRFToken(t *testing.T) {
|
||||
defer os.Remove(token)
|
||||
t.Parallel()
|
||||
|
||||
max := 250
|
||||
int := 5
|
||||
@@ -61,11 +62,13 @@ func TestCSRFToken(t *testing.T) {
|
||||
int = 2
|
||||
}
|
||||
|
||||
t1 := newCsrfToken()
|
||||
t2 := newCsrfToken()
|
||||
m := newCsrfManager("unique", "prefix", config.GUIConfiguration{}, nil, "")
|
||||
|
||||
t3 := newCsrfToken()
|
||||
if !validCsrfToken(t3) {
|
||||
t1 := m.newToken()
|
||||
t2 := m.newToken()
|
||||
|
||||
t3 := m.newToken()
|
||||
if !m.validToken(t3) {
|
||||
t.Fatal("t3 should be valid")
|
||||
}
|
||||
|
||||
@@ -73,36 +76,38 @@ func TestCSRFToken(t *testing.T) {
|
||||
if i%int == 0 {
|
||||
// t1 and t2 should remain valid by virtue of us checking them now
|
||||
// and then.
|
||||
if !validCsrfToken(t1) {
|
||||
if !m.validToken(t1) {
|
||||
t.Fatal("t1 should be valid at iteration", i)
|
||||
}
|
||||
if !validCsrfToken(t2) {
|
||||
if !m.validToken(t2) {
|
||||
t.Fatal("t2 should be valid at iteration", i)
|
||||
}
|
||||
}
|
||||
|
||||
// The newly generated token is always valid
|
||||
t4 := newCsrfToken()
|
||||
if !validCsrfToken(t4) {
|
||||
t4 := m.newToken()
|
||||
if !m.validToken(t4) {
|
||||
t.Fatal("t4 should be valid at iteration", i)
|
||||
}
|
||||
}
|
||||
|
||||
if validCsrfToken(t3) {
|
||||
if m.validToken(t3) {
|
||||
t.Fatal("t3 should have expired by now")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopAfterBrokenConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := config.Configuration{
|
||||
GUI: config.GUIConfiguration{
|
||||
RawAddress: "127.0.0.1:0",
|
||||
RawUseTLS: false,
|
||||
},
|
||||
}
|
||||
w := config.Wrap("/dev/null", cfg)
|
||||
w := config.Wrap("/dev/null", cfg, events.NoopLogger)
|
||||
|
||||
srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
|
||||
srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
|
||||
defer os.Remove(token)
|
||||
srv.started = make(chan string)
|
||||
|
||||
@@ -134,6 +139,8 @@ func TestStopAfterBrokenConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAssetsDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// For any given request to $FILE, we should return the first found of
|
||||
// - assetsdir/$THEME/$FILE
|
||||
// - compiled in asset $THEME/$FILE
|
||||
@@ -208,6 +215,8 @@ func expectURLToContain(t *testing.T, url, exp string) {
|
||||
}
|
||||
|
||||
func TestDirNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
names := dirNames("testdata")
|
||||
expected := []string{"config", "default", "foo", "testfolder"}
|
||||
if diff, equal := messagediff.PrettyDiff(expected, names); !equal {
|
||||
@@ -224,6 +233,8 @@ type httpTestCase struct {
|
||||
}
|
||||
|
||||
func TestAPIServiceRequests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
@@ -434,6 +445,8 @@ func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase, apikey strin
|
||||
}
|
||||
|
||||
func TestHTTPLogin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.User = "üser"
|
||||
cfg.gui.Password = "$2a$10$IdIZTxTg/dCNuNEGlmLynOjqg4B1FvDKuIV5e0BB3pnWVHNb8.GSq" // bcrypt of "räksmörgås" in UTF-8
|
||||
@@ -512,8 +525,8 @@ func startHTTP(cfg *mockedConfig) (string, error) {
|
||||
|
||||
// Instantiate the API service
|
||||
urService := ur.New(cfg, m, connections, false)
|
||||
summaryService := model.NewFolderSummaryService(cfg, m, protocol.LocalDeviceID)
|
||||
svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, discoverer, connections, urService, summaryService, errorLog, systemLog, cpu, nil, false).(*service)
|
||||
summaryService := model.NewFolderSummaryService(cfg, m, protocol.LocalDeviceID, events.NoopLogger)
|
||||
svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, summaryService, errorLog, systemLog, cpu, nil, false).(*service)
|
||||
defer os.Remove(token)
|
||||
svc.started = addrChan
|
||||
|
||||
@@ -541,6 +554,8 @@ func startHTTP(cfg *mockedConfig) (string, error) {
|
||||
}
|
||||
|
||||
func TestCSRFRequired(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
@@ -614,6 +629,8 @@ func TestCSRFRequired(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRandomString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
@@ -663,6 +680,8 @@ func TestRandomString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigPostOK(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := bytes.NewBuffer([]byte(`{
|
||||
"version": 15,
|
||||
"folders": [
|
||||
@@ -684,6 +703,8 @@ func TestConfigPostOK(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigPostDupFolder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := bytes.NewBuffer([]byte(`{
|
||||
"version": 15,
|
||||
"folders": [
|
||||
@@ -719,6 +740,8 @@ func testConfigPost(data io.Reader) (*http.Response, error) {
|
||||
}
|
||||
|
||||
func TestHostCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// An API service bound to localhost should reject non-localhost host Headers
|
||||
|
||||
cfg := new(mockedConfig)
|
||||
@@ -826,6 +849,11 @@ func TestHostCheck(t *testing.T) {
|
||||
|
||||
// This should all work over IPv6 as well
|
||||
|
||||
if runningInContainer() {
|
||||
// Working IPv6 in Docker can't be taken for granted.
|
||||
return
|
||||
}
|
||||
|
||||
cfg = new(mockedConfig)
|
||||
cfg.gui.RawAddress = "[::1]:0"
|
||||
baseURL, err = startHTTP(cfg)
|
||||
@@ -872,6 +900,8 @@ func TestHostCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddressIsLocalhost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testcases := []struct {
|
||||
address string
|
||||
result bool
|
||||
@@ -915,6 +945,8 @@ func TestAddressIsLocalhost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAccessControlAllowOriginHeader(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
@@ -943,6 +975,8 @@ func TestAccessControlAllowOriginHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOptionsRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
@@ -976,10 +1010,12 @@ func TestOptionsRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEventMasks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := new(mockedConfig)
|
||||
defSub := new(mockedEventSub)
|
||||
diskSub := new(mockedEventSub)
|
||||
svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
|
||||
svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, events.NoopLogger, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
|
||||
defer os.Remove(token)
|
||||
|
||||
if mask := svc.getEventMask(""); mask != DefaultEventMask {
|
||||
@@ -1008,6 +1044,8 @@ func TestEventMasks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBrowse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pathSep := string(os.PathSeparator)
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "syncthing")
|
||||
@@ -1060,6 +1098,8 @@ func TestBrowse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrefixMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
s string
|
||||
prefix string
|
||||
@@ -1090,3 +1130,24 @@ func equalStrings(a, b []string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// runningInContainer returns true if we are inside Docker or LXC. It might
|
||||
// be prone to false negatives if things change in the future, but likely
|
||||
// not false positives.
|
||||
func runningInContainer() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return false
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile("/proc/1/cgroup")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if bytes.Contains(bs, []byte("/docker/")) {
|
||||
return true
|
||||
}
|
||||
if bytes.Contains(bs, []byte("/lxc/")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -27,3 +27,7 @@ func (m *mockedConnections) NATType() string {
|
||||
func (m *mockedConnections) Serve() {}
|
||||
|
||||
func (m *mockedConnections) Stop() {}
|
||||
|
||||
func (m *mockedConnections) ExternalAddresses() []string { return nil }
|
||||
|
||||
func (m *mockedConnections) AllAddresses() []string { return nil }
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
type recv struct {
|
||||
@@ -19,7 +23,93 @@ type recv struct {
|
||||
|
||||
type Interface interface {
|
||||
suture.Service
|
||||
fmt.Stringer
|
||||
Send(data []byte)
|
||||
Recv() ([]byte, net.Addr)
|
||||
Error() error
|
||||
}
|
||||
|
||||
type cast struct {
|
||||
*suture.Supervisor
|
||||
name string
|
||||
reader util.ServiceWithError
|
||||
writer util.ServiceWithError
|
||||
outbox chan recv
|
||||
inbox chan []byte
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
// newCast creates a base object for multi- or broadcasting. Afterwards the
|
||||
// caller needs to set reader and writer with the addReader and addWriter
|
||||
// methods to get a functional implementation of Interface.
|
||||
func newCast(name string) *cast {
|
||||
return &cast{
|
||||
Supervisor: suture.New(name, suture.Spec{
|
||||
// Don't retry too frenetically: an error to open a socket or
|
||||
// whatever is usually something that is either permanent or takes
|
||||
// a while to get solved...
|
||||
FailureThreshold: 2,
|
||||
FailureBackoff: 60 * time.Second,
|
||||
// Only log restarts in debug mode.
|
||||
Log: func(line string) {
|
||||
l.Debugln(line)
|
||||
},
|
||||
PassThroughPanics: true,
|
||||
}),
|
||||
name: name,
|
||||
inbox: make(chan []byte),
|
||||
outbox: make(chan recv, 16),
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cast) addReader(svc func(chan struct{}) error) {
|
||||
c.reader = c.createService(svc, "reader")
|
||||
c.Add(c.reader)
|
||||
}
|
||||
|
||||
func (c *cast) addWriter(svc func(stop chan struct{}) error) {
|
||||
c.writer = c.createService(svc, "writer")
|
||||
c.Add(c.writer)
|
||||
}
|
||||
|
||||
func (c *cast) createService(svc func(chan struct{}) error, suffix string) util.ServiceWithError {
|
||||
return util.AsServiceWithError(func(stop chan struct{}) error {
|
||||
l.Debugln("Starting", c.name, suffix)
|
||||
err := svc(stop)
|
||||
l.Debugf("Stopped %v %v: %v", c.name, suffix, err)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *cast) Stop() {
|
||||
c.Supervisor.Stop()
|
||||
close(c.stopped)
|
||||
}
|
||||
|
||||
func (c *cast) String() string {
|
||||
return fmt.Sprintf("%s@%p", c.name, c)
|
||||
}
|
||||
|
||||
func (c *cast) Send(data []byte) {
|
||||
select {
|
||||
case c.inbox <- data:
|
||||
case <-c.stopped:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cast) Recv() ([]byte, net.Addr) {
|
||||
select {
|
||||
case recv := <-c.outbox:
|
||||
return recv.data, recv.src
|
||||
case <-c.stopped:
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *cast) Error() error {
|
||||
if err := c.reader.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.writer.Error()
|
||||
}
|
||||
|
||||
@@ -7,85 +7,22 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
type Broadcast struct {
|
||||
*suture.Supervisor
|
||||
port int
|
||||
inbox chan []byte
|
||||
outbox chan recv
|
||||
br *broadcastReader
|
||||
bw *broadcastWriter
|
||||
func NewBroadcast(port int) Interface {
|
||||
c := newCast("broadcastBeacon")
|
||||
c.addReader(func(stop chan struct{}) error {
|
||||
return readBroadcasts(c.outbox, port, stop)
|
||||
})
|
||||
c.addWriter(func(stop chan struct{}) error {
|
||||
return writeBroadcasts(c.inbox, port, stop)
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
func NewBroadcast(port int) *Broadcast {
|
||||
b := &Broadcast{
|
||||
Supervisor: suture.New("broadcastBeacon", suture.Spec{
|
||||
// Don't retry too frenetically: an error to open a socket or
|
||||
// whatever is usually something that is either permanent or takes
|
||||
// a while to get solved...
|
||||
FailureThreshold: 2,
|
||||
FailureBackoff: 60 * time.Second,
|
||||
// Only log restarts in debug mode.
|
||||
Log: func(line string) {
|
||||
l.Debugln(line)
|
||||
},
|
||||
PassThroughPanics: true,
|
||||
}),
|
||||
port: port,
|
||||
inbox: make(chan []byte),
|
||||
outbox: make(chan recv, 16),
|
||||
}
|
||||
|
||||
b.br = &broadcastReader{
|
||||
port: port,
|
||||
outbox: b.outbox,
|
||||
}
|
||||
b.br.ServiceWithError = util.AsServiceWithError(b.br.serve)
|
||||
b.Add(b.br)
|
||||
b.bw = &broadcastWriter{
|
||||
port: port,
|
||||
inbox: b.inbox,
|
||||
}
|
||||
b.bw.ServiceWithError = util.AsServiceWithError(b.bw.serve)
|
||||
b.Add(b.bw)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Broadcast) Send(data []byte) {
|
||||
b.inbox <- data
|
||||
}
|
||||
|
||||
func (b *Broadcast) Recv() ([]byte, net.Addr) {
|
||||
recv := <-b.outbox
|
||||
return recv.data, recv.src
|
||||
}
|
||||
|
||||
func (b *Broadcast) Error() error {
|
||||
if err := b.br.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.bw.Error()
|
||||
}
|
||||
|
||||
type broadcastWriter struct {
|
||||
util.ServiceWithError
|
||||
port int
|
||||
inbox chan []byte
|
||||
}
|
||||
|
||||
func (w *broadcastWriter) serve(stop chan struct{}) error {
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
|
||||
func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error {
|
||||
conn, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
@@ -104,7 +41,7 @@ func (w *broadcastWriter) serve(stop chan struct{}) error {
|
||||
for {
|
||||
var bs []byte
|
||||
select {
|
||||
case bs = <-w.inbox:
|
||||
case bs = <-inbox:
|
||||
case <-stop:
|
||||
return nil
|
||||
}
|
||||
@@ -112,8 +49,7 @@ func (w *broadcastWriter) serve(stop chan struct{}) error {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
w.SetError(err)
|
||||
continue
|
||||
return err
|
||||
}
|
||||
|
||||
var dsts []net.IP
|
||||
@@ -133,13 +69,13 @@ func (w *broadcastWriter) serve(stop chan struct{}) error {
|
||||
|
||||
success := 0
|
||||
for _, ip := range dsts {
|
||||
dst := &net.UDPAddr{IP: ip, Port: w.port}
|
||||
dst := &net.UDPAddr{IP: ip, Port: port}
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(time.Second))
|
||||
_, err := conn.WriteTo(bs, dst)
|
||||
_, err = conn.WriteTo(bs, dst)
|
||||
conn.SetWriteDeadline(time.Time{})
|
||||
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
// Write timeouts should not happen. We treat it as a fatal
|
||||
// error on the socket.
|
||||
l.Debugln(err)
|
||||
@@ -149,7 +85,6 @@ func (w *broadcastWriter) serve(stop chan struct{}) error {
|
||||
if err != nil {
|
||||
// Some other error that we don't expect. Debug and continue.
|
||||
l.Debugln(err)
|
||||
w.SetError(err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -157,27 +92,15 @@ func (w *broadcastWriter) serve(stop chan struct{}) error {
|
||||
success++
|
||||
}
|
||||
|
||||
if success > 0 {
|
||||
w.SetError(nil)
|
||||
if success == 0 {
|
||||
l.Debugln("couldn't send any braodcasts")
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *broadcastWriter) String() string {
|
||||
return fmt.Sprintf("broadcastWriter@%p", w)
|
||||
}
|
||||
|
||||
type broadcastReader struct {
|
||||
util.ServiceWithError
|
||||
port int
|
||||
outbox chan recv
|
||||
}
|
||||
|
||||
func (r *broadcastReader) serve(stop chan struct{}) error {
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
|
||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
|
||||
func readBroadcasts(outbox chan<- recv, port int, stop chan struct{}) error {
|
||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
return err
|
||||
@@ -200,14 +123,12 @@ func (r *broadcastReader) serve(stop chan struct{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SetError(nil)
|
||||
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
|
||||
c := make([]byte, n)
|
||||
copy(c, bs)
|
||||
select {
|
||||
case r.outbox <- recv{c, addr}:
|
||||
case outbox <- recv{c, addr}:
|
||||
case <-stop:
|
||||
return nil
|
||||
default:
|
||||
@@ -216,10 +137,6 @@ func (r *broadcastReader) serve(stop chan struct{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *broadcastReader) String() string {
|
||||
return fmt.Sprintf("broadcastReader@%p", r)
|
||||
}
|
||||
|
||||
func bcast(ip *net.IPNet) *net.IPNet {
|
||||
var bc = &net.IPNet{}
|
||||
bc.IP = make([]byte, len(ip.IP))
|
||||
|
||||
@@ -8,86 +8,25 @@ package beacon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
type Multicast struct {
|
||||
*suture.Supervisor
|
||||
inbox chan []byte
|
||||
outbox chan recv
|
||||
mr *multicastReader
|
||||
mw *multicastWriter
|
||||
func NewMulticast(addr string) Interface {
|
||||
c := newCast("multicastBeacon")
|
||||
c.addReader(func(stop chan struct{}) error {
|
||||
return readMulticasts(c.outbox, addr, stop)
|
||||
})
|
||||
c.addWriter(func(stop chan struct{}) error {
|
||||
return writeMulticasts(c.inbox, addr, stop)
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
func NewMulticast(addr string) *Multicast {
|
||||
m := &Multicast{
|
||||
Supervisor: suture.New("multicastBeacon", suture.Spec{
|
||||
// Don't retry too frenetically: an error to open a socket or
|
||||
// whatever is usually something that is either permanent or takes
|
||||
// a while to get solved...
|
||||
FailureThreshold: 2,
|
||||
FailureBackoff: 60 * time.Second,
|
||||
// Only log restarts in debug mode.
|
||||
Log: func(line string) {
|
||||
l.Debugln(line)
|
||||
},
|
||||
PassThroughPanics: true,
|
||||
}),
|
||||
inbox: make(chan []byte),
|
||||
outbox: make(chan recv, 16),
|
||||
}
|
||||
|
||||
m.mr = &multicastReader{
|
||||
addr: addr,
|
||||
outbox: m.outbox,
|
||||
}
|
||||
m.mr.ServiceWithError = util.AsServiceWithError(m.mr.serve)
|
||||
m.Add(m.mr)
|
||||
|
||||
m.mw = &multicastWriter{
|
||||
addr: addr,
|
||||
inbox: m.inbox,
|
||||
}
|
||||
m.mw.ServiceWithError = util.AsServiceWithError(m.mw.serve)
|
||||
m.Add(m.mw)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Multicast) Send(data []byte) {
|
||||
m.inbox <- data
|
||||
}
|
||||
|
||||
func (m *Multicast) Recv() ([]byte, net.Addr) {
|
||||
recv := <-m.outbox
|
||||
return recv.data, recv.src
|
||||
}
|
||||
|
||||
func (m *Multicast) Error() error {
|
||||
if err := m.mr.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.mw.Error()
|
||||
}
|
||||
|
||||
type multicastWriter struct {
|
||||
util.ServiceWithError
|
||||
addr string
|
||||
inbox <-chan []byte
|
||||
}
|
||||
|
||||
func (w *multicastWriter) serve(stop chan struct{}) error {
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
|
||||
gaddr, err := net.ResolveUDPAddr("udp6", w.addr)
|
||||
func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error {
|
||||
gaddr, err := net.ResolveUDPAddr("udp6", addr)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
return err
|
||||
@@ -117,7 +56,7 @@ func (w *multicastWriter) serve(stop chan struct{}) error {
|
||||
for {
|
||||
var bs []byte
|
||||
select {
|
||||
case bs = <-w.inbox:
|
||||
case bs = <-inbox:
|
||||
case <-stop:
|
||||
return nil
|
||||
}
|
||||
@@ -137,7 +76,6 @@ func (w *multicastWriter) serve(stop chan struct{}) error {
|
||||
|
||||
if err != nil {
|
||||
l.Debugln(err, "on write to", gaddr, intf.Name)
|
||||
w.SetError(err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -152,33 +90,20 @@ func (w *multicastWriter) serve(stop chan struct{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if success > 0 {
|
||||
w.SetError(nil)
|
||||
if success == 0 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *multicastWriter) String() string {
|
||||
return fmt.Sprintf("multicastWriter@%p", w)
|
||||
}
|
||||
|
||||
type multicastReader struct {
|
||||
util.ServiceWithError
|
||||
addr string
|
||||
outbox chan<- recv
|
||||
}
|
||||
|
||||
func (r *multicastReader) serve(stop chan struct{}) error {
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
|
||||
gaddr, err := net.ResolveUDPAddr("udp6", r.addr)
|
||||
func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error {
|
||||
gaddr, err := net.ResolveUDPAddr("udp6", addr)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := net.ListenPacket("udp6", r.addr)
|
||||
conn, err := net.ListenPacket("udp6", addr)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
return err
|
||||
@@ -226,21 +151,16 @@ func (r *multicastReader) serve(stop chan struct{}) error {
|
||||
n, _, addr, err := pconn.ReadFrom(bs)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
r.SetError(err)
|
||||
continue
|
||||
return err
|
||||
}
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
|
||||
c := make([]byte, n)
|
||||
copy(c, bs)
|
||||
select {
|
||||
case r.outbox <- recv{c, addr}:
|
||||
case outbox <- recv{c, addr}:
|
||||
default:
|
||||
l.Debugln("dropping message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *multicastReader) String() string {
|
||||
return fmt.Sprintf("multicastReader@%p", r)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (validationError) String() string {
|
||||
func TestReplaceCommit(t *testing.T) {
|
||||
t.Skip("broken, fails randomly, #3834")
|
||||
|
||||
w := Wrap("/dev/null", Configuration{Version: 0})
|
||||
w := wrap("/dev/null", Configuration{Version: 0})
|
||||
if w.RawCopy().Version != 0 {
|
||||
t.Fatal("Config incorrect")
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ func New(myID protocol.DeviceID) Configuration {
|
||||
|
||||
// Can't happen.
|
||||
if err := cfg.prepare(myID); err != nil {
|
||||
panic("bug: error in preparing new folder: " + err.Error())
|
||||
l.Warnln("bug: error in preparing new folder:", err)
|
||||
panic("error in preparing new folder")
|
||||
}
|
||||
|
||||
return cfg
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/d4l3k/messagediff"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
@@ -86,7 +87,7 @@ func TestDefaultValues(t *testing.T) {
|
||||
func TestDeviceConfig(t *testing.T) {
|
||||
for i := OldestHandledVersion; i <= CurrentVersion; i++ {
|
||||
os.RemoveAll(filepath.Join("testdata", DefaultMarkerName))
|
||||
wr, err := Load(fmt.Sprintf("testdata/v%d.xml", i), device1)
|
||||
wr, err := load(fmt.Sprintf("testdata/v%d.xml", i), device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -168,7 +169,7 @@ func TestDeviceConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoListenAddresses(t *testing.T) {
|
||||
cfg, err := Load("testdata/nolistenaddress.xml", device1)
|
||||
cfg, err := load("testdata/nolistenaddress.xml", device1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -225,7 +226,7 @@ func TestOverriddenValues(t *testing.T) {
|
||||
}
|
||||
|
||||
os.Unsetenv("STNOUPGRADE")
|
||||
cfg, err := Load("testdata/overridenvalues.xml", device1)
|
||||
cfg, err := load("testdata/overridenvalues.xml", device1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -270,7 +271,7 @@ func TestDeviceAddressesDynamic(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := Load("testdata/deviceaddressesdynamic.xml", device4)
|
||||
cfg, err := load("testdata/deviceaddressesdynamic.xml", device4)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -319,7 +320,7 @@ func TestDeviceCompression(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := Load("testdata/devicecompression.xml", device4)
|
||||
cfg, err := load("testdata/devicecompression.xml", device4)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -365,7 +366,7 @@ func TestDeviceAddressesStatic(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := Load("testdata/deviceaddressesstatic.xml", device4)
|
||||
cfg, err := load("testdata/deviceaddressesstatic.xml", device4)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -377,7 +378,7 @@ func TestDeviceAddressesStatic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersioningConfig(t *testing.T) {
|
||||
cfg, err := Load("testdata/versioningconfig.xml", device4)
|
||||
cfg, err := load("testdata/versioningconfig.xml", device4)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -404,7 +405,7 @@ func TestIssue1262(t *testing.T) {
|
||||
t.Skipf("path gets converted to absolute as part of the filesystem initialization on linux")
|
||||
}
|
||||
|
||||
cfg, err := Load("testdata/issue-1262.xml", device4)
|
||||
cfg, err := load("testdata/issue-1262.xml", device4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -418,7 +419,7 @@ func TestIssue1262(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue1750(t *testing.T) {
|
||||
cfg, err := Load("testdata/issue-1750.xml", device4)
|
||||
cfg, err := load("testdata/issue-1750.xml", device4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -520,7 +521,7 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
}
|
||||
|
||||
intCfg := New(device1)
|
||||
cfg := Wrap(path, intCfg)
|
||||
cfg := wrap(path, intCfg)
|
||||
|
||||
// To make the equality pass later
|
||||
cfg.(*wrapper).cfg.XMLName.Local = "configuration"
|
||||
@@ -537,7 +538,7 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
t.Error(path, "does not exist")
|
||||
}
|
||||
|
||||
cfg2, err := Load(path, device1)
|
||||
cfg2, err := load(path, device1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -564,7 +565,7 @@ func TestPrepare(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
wrapper, err := Load("testdata/example.xml", device1)
|
||||
wrapper, err := load("testdata/example.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -603,7 +604,7 @@ func TestCopy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPullOrder(t *testing.T) {
|
||||
wrapper, err := Load("testdata/pullorder.xml", device1)
|
||||
wrapper, err := load("testdata/pullorder.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -643,7 +644,7 @@ func TestPullOrder(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wrapper = Wrap("testdata/pullorder.xml", cfg)
|
||||
wrapper = wrap("testdata/pullorder.xml", cfg)
|
||||
folders = wrapper.Folders()
|
||||
|
||||
for _, tc := range expected {
|
||||
@@ -654,7 +655,7 @@ func TestPullOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLargeRescanInterval(t *testing.T) {
|
||||
wrapper, err := Load("testdata/largeinterval.xml", device1)
|
||||
wrapper, err := load("testdata/largeinterval.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -692,7 +693,7 @@ func TestGUIConfigURL(t *testing.T) {
|
||||
func TestDuplicateDevices(t *testing.T) {
|
||||
// Duplicate devices should be removed
|
||||
|
||||
wrapper, err := Load("testdata/dupdevices.xml", device1)
|
||||
wrapper, err := load("testdata/dupdevices.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -710,7 +711,7 @@ func TestDuplicateDevices(t *testing.T) {
|
||||
func TestDuplicateFolders(t *testing.T) {
|
||||
// Duplicate folders are a loading error
|
||||
|
||||
_, err := Load("testdata/dupfolders.xml", device1)
|
||||
_, err := load("testdata/dupfolders.xml", device1)
|
||||
if err == nil || !strings.Contains(err.Error(), errFolderIDDuplicate.Error()) {
|
||||
t.Fatal(`Expected error to mention "duplicate folder ID":`, err)
|
||||
}
|
||||
@@ -721,7 +722,7 @@ func TestEmptyFolderPaths(t *testing.T) {
|
||||
// get messed up by the prepare steps (e.g., become the current dir or
|
||||
// get a slash added so that it becomes the root directory or similar).
|
||||
|
||||
_, err := Load("testdata/nopath.xml", device1)
|
||||
_, err := load("testdata/nopath.xml", device1)
|
||||
if err == nil || !strings.Contains(err.Error(), errFolderPathEmpty.Error()) {
|
||||
t.Fatal("Expected error due to empty folder path, got", err)
|
||||
}
|
||||
@@ -790,7 +791,7 @@ func TestIgnoredDevices(t *testing.T) {
|
||||
// Verify that ignored devices that are also present in the
|
||||
// configuration are not in fact ignored.
|
||||
|
||||
wrapper, err := Load("testdata/ignoreddevices.xml", device1)
|
||||
wrapper, err := load("testdata/ignoreddevices.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -808,7 +809,7 @@ func TestIgnoredFolders(t *testing.T) {
|
||||
// configuration are not in fact ignored.
|
||||
// Also, verify that folders that are shared with a device are not ignored.
|
||||
|
||||
wrapper, err := Load("testdata/ignoredfolders.xml", device1)
|
||||
wrapper, err := load("testdata/ignoredfolders.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -844,7 +845,7 @@ func TestIgnoredFolders(t *testing.T) {
|
||||
func TestGetDevice(t *testing.T) {
|
||||
// Verify that the Device() call does the right thing
|
||||
|
||||
wrapper, err := Load("testdata/ignoreddevices.xml", device1)
|
||||
wrapper, err := load("testdata/ignoreddevices.xml", device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -871,7 +872,7 @@ func TestGetDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
|
||||
wrapper, err := Load("testdata/example.xml", device1)
|
||||
wrapper, err := load("testdata/example.xml", device1)
|
||||
if err != nil {
|
||||
t.Errorf("Failed: %s", err)
|
||||
}
|
||||
@@ -956,7 +957,7 @@ func TestIssue4219(t *testing.T) {
|
||||
t.Errorf("There should be three ignored folders, not %d", ignoredFolders)
|
||||
}
|
||||
|
||||
w := Wrap("/tmp/cfg", cfg)
|
||||
w := wrap("/tmp/cfg", cfg)
|
||||
if !w.IgnoredFolder(device2, "t1") {
|
||||
t.Error("Folder device2 t1 should be ignored")
|
||||
}
|
||||
@@ -1145,3 +1146,11 @@ func defaultConfigAsMap() map[string]interface{} {
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func load(path string, myID protocol.DeviceID) (Wrapper, error) {
|
||||
return Load(path, myID, events.NoopLogger)
|
||||
}
|
||||
|
||||
func wrap(path string, cfg Configuration) Wrapper {
|
||||
return Wrap(path, cfg, events.NoopLogger)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
@@ -53,8 +57,10 @@ type FolderConfiguration struct {
|
||||
WeakHashThresholdPct int `xml:"weakHashThresholdPct" json:"weakHashThresholdPct"` // Use weak hash if more than X percent of the file has changed. Set to -1 to always use weak hash.
|
||||
MarkerName string `xml:"markerName" json:"markerName"`
|
||||
CopyOwnershipFromParent bool `xml:"copyOwnershipFromParent" json:"copyOwnershipFromParent"`
|
||||
RawModTimeWindowS int `xml:"modTimeWindowS" json:"modTimeWindowS"`
|
||||
|
||||
cachedFilesystem fs.Filesystem
|
||||
cachedFilesystem fs.Filesystem
|
||||
cachedModTimeWindow time.Duration
|
||||
|
||||
DeprecatedReadOnly bool `xml:"ro,attr,omitempty" json:"-"`
|
||||
DeprecatedMinDiskFreePct float64 `xml:"minDiskFreePct,omitempty" json:"-"`
|
||||
@@ -111,6 +117,10 @@ func (f FolderConfiguration) Versioner() versioner.Versioner {
|
||||
return versionerFactory(f.ID, f.Filesystem(), f.Versioning.Params)
|
||||
}
|
||||
|
||||
func (f FolderConfiguration) ModTimeWindow() time.Duration {
|
||||
return f.cachedModTimeWindow
|
||||
}
|
||||
|
||||
func (f *FolderConfiguration) CreateMarker() error {
|
||||
if err := f.CheckPath(); err != ErrMarkerMissing {
|
||||
return err
|
||||
@@ -233,6 +243,21 @@ func (f *FolderConfiguration) prepare() {
|
||||
if f.MarkerName == "" {
|
||||
f.MarkerName = DefaultMarkerName
|
||||
}
|
||||
|
||||
switch {
|
||||
case f.RawModTimeWindowS > 0:
|
||||
f.cachedModTimeWindow = time.Duration(f.RawModTimeWindowS) * time.Second
|
||||
case runtime.GOOS == "android":
|
||||
if usage, err := disk.Usage(f.Filesystem().URI()); err != nil {
|
||||
f.cachedModTimeWindow = 2 * time.Second
|
||||
l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: err == "%v"`, f.Path, err)
|
||||
} else if usage.Fstype == "" || strings.Contains(strings.ToLower(usage.Fstype), "fat") {
|
||||
f.cachedModTimeWindow = 2 * time.Second
|
||||
l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: usage.Fstype == "%v"`, f.Path, usage.Fstype)
|
||||
} else {
|
||||
l.Debugf(`Detecting FS at %v on android: Leaving mtime window at 0: usage.Fstype == "%v"`, f.Path, usage.Fstype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RequiresRestartOnly returns a copy with only the attributes that require
|
||||
|
||||
@@ -57,6 +57,7 @@ type OptionsConfiguration struct {
|
||||
StunKeepaliveStartS int `xml:"stunKeepaliveStartS" json:"stunKeepaliveStartS" default:"180"` // 0 for off
|
||||
StunKeepaliveMinS int `xml:"stunKeepaliveMinS" json:"stunKeepaliveMinS" default:"20"` // 0 for off
|
||||
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
|
||||
DatabaseTuning Tuning `xml:"databaseTuning" json:"databaseTuning" restart:"true"`
|
||||
|
||||
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
|
||||
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
|
||||
|
||||
@@ -87,11 +87,26 @@ func CheckFreeSpace(req Size, usage fs.Usage) error {
|
||||
if req.Percentage() {
|
||||
freePct := (float64(usage.Free) / float64(usage.Total)) * 100
|
||||
if freePct < val {
|
||||
return fmt.Errorf("%f %% < %v", freePct, req)
|
||||
return fmt.Errorf("%.1f %% < %v", freePct, req)
|
||||
}
|
||||
} else if float64(usage.Free) < val {
|
||||
return fmt.Errorf("%v < %v", usage.Free, req)
|
||||
return fmt.Errorf("%sB < %v", formatSI(usage.Free), req)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatSI(b int64) string {
|
||||
switch {
|
||||
case b < 1000:
|
||||
return fmt.Sprintf("%d ", b)
|
||||
case b < 1000*1000:
|
||||
return fmt.Sprintf("%.1f K", float64(b)/1000)
|
||||
case b < 1000*1000*1000:
|
||||
return fmt.Sprintf("%.1f M", float64(b)/(1000*1000))
|
||||
case b < 1000*1000*1000*1000:
|
||||
return fmt.Sprintf("%.1f G", float64(b)/(1000*1000*1000))
|
||||
default:
|
||||
return fmt.Sprintf("%.1f T", float64(b)/(1000*1000*1000*1000))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,3 +91,42 @@ func TestParseSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatSI(t *testing.T) {
|
||||
cases := []struct {
|
||||
bytes int64
|
||||
result string
|
||||
}{
|
||||
{
|
||||
bytes: 0,
|
||||
result: "0 ", // space for unit
|
||||
},
|
||||
{
|
||||
bytes: 999,
|
||||
result: "999 ",
|
||||
},
|
||||
{
|
||||
bytes: 1000,
|
||||
result: "1.0 K",
|
||||
},
|
||||
{
|
||||
bytes: 1023 * 1000,
|
||||
result: "1.0 M",
|
||||
},
|
||||
{
|
||||
bytes: 5 * 1000 * 1000 * 1000,
|
||||
result: "5.0 G",
|
||||
},
|
||||
{
|
||||
bytes: 50000 * 1000 * 1000 * 1000 * 1000,
|
||||
result: "50000.0 T",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := formatSI(tc.bytes)
|
||||
if res != tc.result {
|
||||
t.Errorf("formatSI(%d) => %q, expected %q", tc.bytes, res, tc.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
lib/config/tuning.go
Normal file
47
lib/config/tuning.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (C) 2019 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package config
|
||||
|
||||
type Tuning int
|
||||
|
||||
const (
|
||||
// N.b. these constants must match those in lib/db.Tuning!
|
||||
TuningAuto Tuning = iota // default is auto
|
||||
TuningSmall
|
||||
TuningLarge
|
||||
)
|
||||
|
||||
func (t Tuning) String() string {
|
||||
switch t {
|
||||
case TuningAuto:
|
||||
return "auto"
|
||||
case TuningSmall:
|
||||
return "small"
|
||||
case TuningLarge:
|
||||
return "large"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (t Tuning) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
func (t *Tuning) UnmarshalText(bs []byte) error {
|
||||
switch string(bs) {
|
||||
case "auto":
|
||||
*t = TuningAuto
|
||||
case "small":
|
||||
*t = TuningSmall
|
||||
case "large":
|
||||
*t = TuningLarge
|
||||
default:
|
||||
*t = TuningAuto
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
lib/config/tuning_test.go
Normal file
26
lib/config/tuning_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2019 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
)
|
||||
|
||||
func TestTuningMatches(t *testing.T) {
|
||||
if int(config.TuningAuto) != int(db.TuningAuto) {
|
||||
t.Error("mismatch for TuningAuto")
|
||||
}
|
||||
if int(config.TuningSmall) != int(db.TuningSmall) {
|
||||
t.Error("mismatch for TuningSmall")
|
||||
}
|
||||
if int(config.TuningLarge) != int(db.TuningLarge) {
|
||||
t.Error("mismatch for TuningLarge")
|
||||
}
|
||||
}
|
||||
@@ -96,8 +96,9 @@ type Wrapper interface {
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
cfg Configuration
|
||||
path string
|
||||
cfg Configuration
|
||||
path string
|
||||
evLogger events.Logger
|
||||
|
||||
deviceMap map[protocol.DeviceID]DeviceConfiguration
|
||||
folderMap map[string]FolderConfiguration
|
||||
@@ -133,18 +134,19 @@ func (w *wrapper) StunServers() []string {
|
||||
|
||||
// Wrap wraps an existing Configuration structure and ties it to a file on
|
||||
// disk.
|
||||
func Wrap(path string, cfg Configuration) Wrapper {
|
||||
func Wrap(path string, cfg Configuration, evLogger events.Logger) Wrapper {
|
||||
w := &wrapper{
|
||||
cfg: cfg,
|
||||
path: path,
|
||||
mut: sync.NewMutex(),
|
||||
cfg: cfg,
|
||||
path: path,
|
||||
evLogger: evLogger,
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Load loads an existing file on disk and returns a new configuration
|
||||
// wrapper.
|
||||
func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
|
||||
func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper, error) {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,7 +158,7 @@ func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Wrap(path, cfg), nil
|
||||
return Wrap(path, cfg, evLogger), nil
|
||||
}
|
||||
|
||||
func (w *wrapper) ConfigPath() string {
|
||||
@@ -450,7 +452,7 @@ func (w *wrapper) Save() error {
|
||||
return err
|
||||
}
|
||||
|
||||
events.Default.Log(events.ConfigSaved, w.cfg)
|
||||
w.evLogger.Log(events.ConfigSaved, w.cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -501,8 +503,6 @@ func (w *wrapper) MyName() string {
|
||||
}
|
||||
|
||||
func (w *wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
|
||||
defer w.Save()
|
||||
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -524,8 +524,6 @@ func (w *wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, addre
|
||||
}
|
||||
|
||||
func (w *wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
|
||||
defer w.Save()
|
||||
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
)
|
||||
|
||||
func TestIsLANHost(t *testing.T) {
|
||||
@@ -35,7 +36,7 @@ func TestIsLANHost(t *testing.T) {
|
||||
Options: config.OptionsConfiguration{
|
||||
AlwaysLocalNets: []string{"10.20.30.0/24"},
|
||||
},
|
||||
})
|
||||
}, events.NoopLogger)
|
||||
s := &service{cfg: cfg}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@@ -32,9 +32,13 @@ type limiter struct {
|
||||
type waiter interface {
|
||||
// This is the rate limiting operation
|
||||
WaitN(ctx context.Context, n int) error
|
||||
Limit() rate.Limit
|
||||
}
|
||||
|
||||
const limiterBurstSize = 4 * 128 << 10
|
||||
const (
|
||||
limiterBurstSize = 4 * 128 << 10
|
||||
maxSingleWriteSize = 8 << 10
|
||||
)
|
||||
|
||||
func newLimiter(cfg config.Wrapper) *limiter {
|
||||
l := &limiter{
|
||||
@@ -186,19 +190,23 @@ func (lim *limiter) getLimiters(remoteID protocol.DeviceID, rw io.ReadWriter, is
|
||||
|
||||
func (lim *limiter) newLimitedReaderLocked(remoteID protocol.DeviceID, r io.Reader, isLAN bool) io.Reader {
|
||||
return &limitedReader{
|
||||
reader: r,
|
||||
limitsLAN: &lim.limitsLAN,
|
||||
waiter: totalWaiter{lim.getReadLimiterLocked(remoteID), lim.read},
|
||||
isLAN: isLAN,
|
||||
reader: r,
|
||||
waiterHolder: waiterHolder{
|
||||
waiter: totalWaiter{lim.getReadLimiterLocked(remoteID), lim.read},
|
||||
limitsLAN: &lim.limitsLAN,
|
||||
isLAN: isLAN,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (lim *limiter) newLimitedWriterLocked(remoteID protocol.DeviceID, w io.Writer, isLAN bool) io.Writer {
|
||||
return &limitedWriter{
|
||||
writer: w,
|
||||
limitsLAN: &lim.limitsLAN,
|
||||
waiter: totalWaiter{lim.getWriteLimiterLocked(remoteID), lim.write},
|
||||
isLAN: isLAN,
|
||||
writer: w,
|
||||
waiterHolder: waiterHolder{
|
||||
waiter: totalWaiter{lim.getWriteLimiterLocked(remoteID), lim.write},
|
||||
limitsLAN: &lim.limitsLAN,
|
||||
isLAN: isLAN,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,53 +229,87 @@ func getRateLimiter(m map[protocol.DeviceID]*rate.Limiter, deviceID protocol.Dev
|
||||
|
||||
// limitedReader is a rate limited io.Reader
|
||||
type limitedReader struct {
|
||||
reader io.Reader
|
||||
limitsLAN *atomicBool
|
||||
waiter waiter
|
||||
isLAN bool
|
||||
reader io.Reader
|
||||
waiterHolder
|
||||
}
|
||||
|
||||
func (r *limitedReader) Read(buf []byte) (int, error) {
|
||||
n, err := r.reader.Read(buf)
|
||||
if !r.isLAN || r.limitsLAN.get() {
|
||||
take(r.waiter, n)
|
||||
if !r.unlimited() {
|
||||
r.take(n)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// limitedWriter is a rate limited io.Writer
|
||||
type limitedWriter struct {
|
||||
writer io.Writer
|
||||
limitsLAN *atomicBool
|
||||
waiter waiter
|
||||
isLAN bool
|
||||
writer io.Writer
|
||||
waiterHolder
|
||||
}
|
||||
|
||||
func (w *limitedWriter) Write(buf []byte) (int, error) {
|
||||
if !w.isLAN || w.limitsLAN.get() {
|
||||
take(w.waiter, len(buf))
|
||||
if w.unlimited() {
|
||||
return w.writer.Write(buf)
|
||||
}
|
||||
return w.writer.Write(buf)
|
||||
|
||||
// This does (potentially) multiple smaller writes in order to be less
|
||||
// bursty with large writes and slow rates.
|
||||
written := 0
|
||||
for written < len(buf) {
|
||||
toWrite := maxSingleWriteSize
|
||||
if toWrite > len(buf)-written {
|
||||
toWrite = len(buf) - written
|
||||
}
|
||||
w.take(toWrite)
|
||||
n, err := w.writer.Write(buf[written : written+toWrite])
|
||||
written += n
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// take is a utility function to consume tokens from a overall rate.Limiter and deviceLimiter.
|
||||
// No call to WaitN can be larger than the limiter burst size so we split it up into
|
||||
// several calls when necessary.
|
||||
func take(waiter waiter, tokens int) {
|
||||
// waiterHolder is the common functionality around having and evaluating a
|
||||
// waiter, valid for both writers and readers
|
||||
type waiterHolder struct {
|
||||
waiter waiter
|
||||
limitsLAN *atomicBool
|
||||
isLAN bool
|
||||
}
|
||||
|
||||
// unlimited returns true if the waiter is not limiting the rate
|
||||
func (w waiterHolder) unlimited() bool {
|
||||
if w.isLAN && !w.limitsLAN.get() {
|
||||
return true
|
||||
}
|
||||
return w.waiter.Limit() == rate.Inf
|
||||
}
|
||||
|
||||
// take is a utility function to consume tokens, because no call to WaitN
|
||||
// must be larger than the limiter burst size or it will hang.
|
||||
func (w waiterHolder) take(tokens int) {
|
||||
// For writes we already split the buffer into smaller operations so those
|
||||
// will always end up in the fast path below. For reads, however, we don't
|
||||
// control the size of the incoming buffer and don't split the calls
|
||||
// into the lower level reads so we might get a large amount of data and
|
||||
// end up in the loop further down.
|
||||
|
||||
if tokens < limiterBurstSize {
|
||||
// This is the by far more common case so we get it out of the way
|
||||
// early.
|
||||
waiter.WaitN(context.TODO(), tokens)
|
||||
// Fast path. We won't get an error from WaitN as we don't pass a
|
||||
// context with a deadline.
|
||||
_ = w.waiter.WaitN(context.TODO(), tokens)
|
||||
return
|
||||
}
|
||||
|
||||
for tokens > 0 {
|
||||
// Consume limiterBurstSize tokens at a time until we're done.
|
||||
if tokens > limiterBurstSize {
|
||||
waiter.WaitN(context.TODO(), limiterBurstSize)
|
||||
_ = w.waiter.WaitN(context.TODO(), limiterBurstSize)
|
||||
tokens -= limiterBurstSize
|
||||
} else {
|
||||
waiter.WaitN(context.TODO(), tokens)
|
||||
_ = w.waiter.WaitN(context.TODO(), tokens)
|
||||
tokens = 0
|
||||
}
|
||||
}
|
||||
@@ -300,3 +342,13 @@ func (tw totalWaiter) WaitN(ctx context.Context, n int) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tw totalWaiter) Limit() rate.Limit {
|
||||
min := rate.Inf
|
||||
for _, w := range tw {
|
||||
if l := w.Limit(); l < min {
|
||||
min = l
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
@@ -7,11 +7,16 @@
|
||||
package connections
|
||||
|
||||
import (
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"golang.org/x/time/rate"
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"io"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
var device1, device2, device3, device4 protocol.DeviceID
|
||||
@@ -25,7 +30,7 @@ func init() {
|
||||
}
|
||||
|
||||
func initConfig() config.Wrapper {
|
||||
cfg := config.Wrap("/dev/null", config.New(device1))
|
||||
cfg := config.Wrap("/dev/null", config.New(device1), events.NoopLogger)
|
||||
dev1Conf = config.NewDeviceConfiguration(device1, "device1")
|
||||
dev2Conf = config.NewDeviceConfiguration(device2, "device2")
|
||||
dev3Conf = config.NewDeviceConfiguration(device3, "device3")
|
||||
@@ -184,6 +189,151 @@ func TestAddAndRemove(t *testing.T) {
|
||||
checkActualAndExpected(t, actualR, actualW, expectedR, expectedW)
|
||||
}
|
||||
|
||||
func TestLimitedWriterWrite(t *testing.T) {
|
||||
// Check that the limited writer writes the correct data in the correct manner.
|
||||
|
||||
// A buffer with random data that is larger than the write size and not
|
||||
// a precise multiple either.
|
||||
src := make([]byte, int(12.5*maxSingleWriteSize))
|
||||
if _, err := crand.Reader.Read(src); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write it to the destination using a limited writer, with a wrapper to
|
||||
// count the write calls. The defaults on the limited writer should mean
|
||||
// it is used (and doesn't take the fast path). In practice the limiter
|
||||
// won't delay the test as the burst size is large enough to accommodate
|
||||
// regardless of the rate.
|
||||
dst := new(bytes.Buffer)
|
||||
cw := &countingWriter{w: dst}
|
||||
lw := &limitedWriter{
|
||||
writer: cw,
|
||||
waiterHolder: waiterHolder{
|
||||
waiter: rate.NewLimiter(rate.Limit(42), limiterBurstSize),
|
||||
limitsLAN: new(atomicBool),
|
||||
isLAN: false, // enables limiting
|
||||
},
|
||||
}
|
||||
if _, err := io.Copy(lw, bytes.NewReader(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify there were lots of writes and that the end result is identical.
|
||||
if cw.writeCount != 13 {
|
||||
t.Error("expected lots of smaller writes, but not too many")
|
||||
}
|
||||
if !bytes.Equal(src, dst.Bytes()) {
|
||||
t.Error("results should be equal")
|
||||
}
|
||||
|
||||
// Write it to the destination using a limited writer, with a wrapper to
|
||||
// count the write calls. Now we make sure the fast path is used.
|
||||
dst = new(bytes.Buffer)
|
||||
cw = &countingWriter{w: dst}
|
||||
lw = &limitedWriter{
|
||||
writer: cw,
|
||||
waiterHolder: waiterHolder{
|
||||
waiter: rate.NewLimiter(rate.Limit(42), limiterBurstSize),
|
||||
limitsLAN: new(atomicBool),
|
||||
isLAN: true, // disables limiting
|
||||
},
|
||||
}
|
||||
if _, err := io.Copy(lw, bytes.NewReader(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify there were a single write and that the end result is identical.
|
||||
if cw.writeCount != 1 {
|
||||
t.Error("expected just the one write")
|
||||
}
|
||||
if !bytes.Equal(src, dst.Bytes()) {
|
||||
t.Error("results should be equal")
|
||||
}
|
||||
|
||||
// Once more, but making sure the fast path is used for an unlimited
|
||||
// rate, with multiple unlimited raters even (global and per-device).
|
||||
dst = new(bytes.Buffer)
|
||||
cw = &countingWriter{w: dst}
|
||||
lw = &limitedWriter{
|
||||
writer: cw,
|
||||
waiterHolder: waiterHolder{
|
||||
waiter: totalWaiter{rate.NewLimiter(rate.Inf, limiterBurstSize), rate.NewLimiter(rate.Inf, limiterBurstSize)},
|
||||
limitsLAN: new(atomicBool),
|
||||
isLAN: false, // enables limiting
|
||||
},
|
||||
}
|
||||
if _, err := io.Copy(lw, bytes.NewReader(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify there were a single write and that the end result is identical.
|
||||
if cw.writeCount != 1 {
|
||||
t.Error("expected just the one write")
|
||||
}
|
||||
if !bytes.Equal(src, dst.Bytes()) {
|
||||
t.Error("results should be equal")
|
||||
}
|
||||
|
||||
// Once more, but making sure we *don't* take the fast path when there
|
||||
// is a combo of limited and unlimited writers.
|
||||
dst = new(bytes.Buffer)
|
||||
cw = &countingWriter{w: dst}
|
||||
lw = &limitedWriter{
|
||||
writer: cw,
|
||||
waiterHolder: waiterHolder{
|
||||
waiter: totalWaiter{
|
||||
rate.NewLimiter(rate.Inf, limiterBurstSize),
|
||||
rate.NewLimiter(rate.Limit(42), limiterBurstSize),
|
||||
rate.NewLimiter(rate.Inf, limiterBurstSize),
|
||||
},
|
||||
limitsLAN: new(atomicBool),
|
||||
isLAN: false, // enables limiting
|
||||
},
|
||||
}
|
||||
if _, err := io.Copy(lw, bytes.NewReader(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify there were lots of writes and that the end result is identical.
|
||||
if cw.writeCount != 13 {
|
||||
t.Error("expected just the one write")
|
||||
}
|
||||
if !bytes.Equal(src, dst.Bytes()) {
|
||||
t.Error("results should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTotalWaiterLimit(t *testing.T) {
|
||||
cases := []struct {
|
||||
w waiter
|
||||
r rate.Limit
|
||||
}{
|
||||
{
|
||||
totalWaiter{},
|
||||
rate.Inf,
|
||||
},
|
||||
{
|
||||
totalWaiter{rate.NewLimiter(rate.Inf, 42)},
|
||||
rate.Inf,
|
||||
},
|
||||
{
|
||||
totalWaiter{rate.NewLimiter(rate.Inf, 42), rate.NewLimiter(rate.Inf, 42)},
|
||||
rate.Inf,
|
||||
},
|
||||
{
|
||||
totalWaiter{rate.NewLimiter(rate.Inf, 42), rate.NewLimiter(rate.Limit(12), 42), rate.NewLimiter(rate.Limit(15), 42)},
|
||||
rate.Limit(12),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
l := tc.w.Limit()
|
||||
if l != tc.r {
|
||||
t.Error("incorrect limit returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkActualAndExpected(t *testing.T, actualR, actualW, expectedR, expectedW map[protocol.DeviceID]*rate.Limiter) {
|
||||
t.Helper()
|
||||
if len(expectedW) != len(actualW) || len(expectedR) != len(actualR) {
|
||||
@@ -203,3 +353,13 @@ func checkActualAndExpected(t *testing.T, actualR, actualW, expectedR, expectedW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type countingWriter struct {
|
||||
w io.Writer
|
||||
writeCount int
|
||||
}
|
||||
|
||||
func (w *countingWriter) Write(data []byte) (int, error) {
|
||||
w.writeCount++
|
||||
return w.w.Write(data)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package connections
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
@@ -22,7 +23,13 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
const quicPriority = 100
|
||||
const (
|
||||
quicPriority = 100
|
||||
|
||||
// The timeout for connecting, accepting and creating the various
|
||||
// streams.
|
||||
quicOperationTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func init() {
|
||||
factory := &quicDialerFactory{}
|
||||
@@ -36,7 +43,7 @@ type quicDialer struct {
|
||||
tlsCfg *tls.Config
|
||||
}
|
||||
|
||||
func (d *quicDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
|
||||
func (d *quicDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, error) {
|
||||
uri = fixupPort(uri, config.DefaultQUICPort)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", uri.Host)
|
||||
@@ -60,38 +67,25 @@ func (d *quicDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, err
|
||||
}
|
||||
}
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), quicOperationTimeout)
|
||||
defer cancel()
|
||||
|
||||
session, err := quic.DialContext(ctx, conn, addr, uri.Host, d.tlsCfg, quicConfig)
|
||||
if err != nil {
|
||||
if createdConn != nil {
|
||||
_ = createdConn.Close()
|
||||
}
|
||||
return internalConn{}, err
|
||||
return internalConn{}, fmt.Errorf("dial: %v", err)
|
||||
}
|
||||
|
||||
// OpenStreamSync is blocks, but we want to make sure the connection is usable
|
||||
// before we start killing off other connections, so do the dance.
|
||||
ok := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-ok:
|
||||
return
|
||||
case <-time.After(10 * time.Second):
|
||||
l.Debugln("timed out waiting for OpenStream on", session.RemoteAddr())
|
||||
// This will unblock OpenStreamSync
|
||||
_ = session.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
stream, err := session.OpenStreamSync()
|
||||
close(ok)
|
||||
stream, err := session.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
// It's ok to close these, this does not close the underlying packetConn.
|
||||
_ = session.Close()
|
||||
if createdConn != nil {
|
||||
_ = createdConn.Close()
|
||||
}
|
||||
return internalConn{}, err
|
||||
return internalConn{}, fmt.Errorf("open stream: %v", err)
|
||||
}
|
||||
|
||||
return internalConn{&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority}, nil
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
package connections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
|
||||
@@ -80,6 +80,10 @@ func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string)
|
||||
func (t *quicListener) serve(stop chan struct{}) error {
|
||||
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
|
||||
|
||||
// Convert the stop channel into a context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() { <-stop; cancel() }()
|
||||
|
||||
packetConn, err := net.ListenPacket(network, t.uri.Host)
|
||||
if err != nil {
|
||||
l.Infoln("Listen (BEP/quic):", err)
|
||||
@@ -101,57 +105,32 @@ func (t *quicListener) serve(stop chan struct{}) error {
|
||||
l.Infoln("Listen (BEP/quic):", err)
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
l.Infof("QUIC listener (%v) starting", packetConn.LocalAddr())
|
||||
defer l.Infof("QUIC listener (%v) shutting down", packetConn.LocalAddr())
|
||||
|
||||
// Accept is forever, so handle stops externally.
|
||||
go func() {
|
||||
select {
|
||||
case <-stop:
|
||||
_ = listener.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// Blocks forever, see https://github.com/lucas-clemente/quic-go/issues/1915
|
||||
session, err := listener.Accept()
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
if err == nil {
|
||||
_ = session.Close()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); !ok || !err.Timeout() {
|
||||
l.Warnln("Listen (BEP/quic): Accepting connection:", err)
|
||||
}
|
||||
|
||||
session, err := listener.Accept(ctx)
|
||||
if err == context.Canceled {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
l.Warnln("Listen (BEP/quic): Accepting connection:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
l.Debugln("connect from", session.RemoteAddr())
|
||||
|
||||
// Accept blocks forever, give it 10s to do it's thing.
|
||||
ok := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-ok:
|
||||
return
|
||||
case <-stop:
|
||||
_ = session.Close()
|
||||
case <-time.After(10 * time.Second):
|
||||
l.Debugln("timed out waiting for AcceptStream on", session.RemoteAddr())
|
||||
_ = session.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
stream, err := session.AcceptStream()
|
||||
close(ok)
|
||||
streamCtx, cancel := context.WithTimeout(ctx, quicOperationTimeout)
|
||||
stream, err := session.AcceptStream(streamCtx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
l.Debugln("failed to accept stream from", session.RemoteAddr(), err.Error())
|
||||
l.Debugf("failed to accept stream from %s: %v", session.RemoteAddr(), err)
|
||||
_ = session.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/dialer"
|
||||
"github.com/syncthing/syncthing/lib/nat"
|
||||
"github.com/syncthing/syncthing/lib/relay/client"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -26,6 +27,7 @@ func init() {
|
||||
}
|
||||
|
||||
type relayListener struct {
|
||||
util.ServiceWithError
|
||||
onAddressesChangedNotifier
|
||||
|
||||
uri *url.URL
|
||||
@@ -34,30 +36,22 @@ type relayListener struct {
|
||||
conns chan internalConn
|
||||
factory listenerFactory
|
||||
|
||||
err error
|
||||
client client.RelayClient
|
||||
mut sync.RWMutex
|
||||
}
|
||||
|
||||
func (t *relayListener) Serve() {
|
||||
t.mut.Lock()
|
||||
t.err = nil
|
||||
t.mut.Unlock()
|
||||
|
||||
func (t *relayListener) serve(stop chan struct{}) error {
|
||||
clnt, err := client.NewClient(t.uri, t.tlsCfg.Certificates, nil, 10*time.Second)
|
||||
invitations := clnt.Invitations()
|
||||
if err != nil {
|
||||
t.mut.Lock()
|
||||
t.err = err
|
||||
t.mut.Unlock()
|
||||
l.Warnln("Listen (BEP/relay):", err)
|
||||
return
|
||||
l.Infoln("Listen (BEP/relay):", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go clnt.Serve()
|
||||
invitations := clnt.Invitations()
|
||||
|
||||
t.mut.Lock()
|
||||
t.client = clnt
|
||||
go clnt.Serve()
|
||||
defer clnt.Stop()
|
||||
t.mut.Unlock()
|
||||
|
||||
oldURI := clnt.URI()
|
||||
@@ -69,7 +63,10 @@ func (t *relayListener) Serve() {
|
||||
select {
|
||||
case inv, ok := <-invitations:
|
||||
if !ok {
|
||||
return
|
||||
if err := clnt.Error(); err != nil {
|
||||
l.Infoln("Listen (BEP/relay):", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := client.JoinSession(inv)
|
||||
@@ -114,18 +111,13 @@ func (t *relayListener) Serve() {
|
||||
oldURI = currentURI
|
||||
t.notifyAddressesChanged(t)
|
||||
}
|
||||
|
||||
case <-stop:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *relayListener) Stop() {
|
||||
t.mut.RLock()
|
||||
if t.client != nil {
|
||||
t.client.Stop()
|
||||
}
|
||||
t.mut.RUnlock()
|
||||
}
|
||||
|
||||
func (t *relayListener) URI() *url.URL {
|
||||
return t.uri
|
||||
}
|
||||
@@ -152,18 +144,16 @@ func (t *relayListener) LANAddresses() []*url.URL {
|
||||
}
|
||||
|
||||
func (t *relayListener) Error() error {
|
||||
t.mut.RLock()
|
||||
err := t.err
|
||||
var cerr error
|
||||
if t.client != nil {
|
||||
cerr = t.client.Error()
|
||||
}
|
||||
t.mut.RUnlock()
|
||||
|
||||
err := t.ServiceWithError.Error()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cerr
|
||||
t.mut.RLock()
|
||||
defer t.mut.RUnlock()
|
||||
if t.client != nil {
|
||||
return t.client.Error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *relayListener) Factory() listenerFactory {
|
||||
@@ -181,13 +171,15 @@ func (t *relayListener) NATType() string {
|
||||
type relayListenerFactory struct{}
|
||||
|
||||
func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
|
||||
return &relayListener{
|
||||
t := &relayListener{
|
||||
uri: uri,
|
||||
cfg: cfg,
|
||||
tlsCfg: tlsCfg,
|
||||
conns: conns,
|
||||
factory: f,
|
||||
}
|
||||
t.ServiceWithError = util.AsServiceWithError(t.serve)
|
||||
return t
|
||||
}
|
||||
|
||||
func (relayListenerFactory) Valid(cfg config.Configuration) error {
|
||||
|
||||
@@ -90,6 +90,7 @@ var tlsVersionNames = map[uint16]string{
|
||||
// dialers. Successful connections are handed to the model.
|
||||
type Service interface {
|
||||
suture.Service
|
||||
discover.AddressLister
|
||||
ListenerStatus() map[string]ListenerStatusEntry
|
||||
ConnectionStatus() map[string]ConnectionStatusEntry
|
||||
NATType() string
|
||||
@@ -119,6 +120,7 @@ type service struct {
|
||||
limiter *limiter
|
||||
natService *nat.Service
|
||||
natServiceToken *suture.ServiceToken
|
||||
evLogger events.Logger
|
||||
|
||||
listenersMut sync.RWMutex
|
||||
listeners map[string]genericListener
|
||||
@@ -129,9 +131,7 @@ type service struct {
|
||||
connectionStatus map[string]ConnectionStatusEntry // address -> latest error/status
|
||||
}
|
||||
|
||||
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
|
||||
bepProtocolName string, tlsDefaultCommonName string) *service {
|
||||
|
||||
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger) Service {
|
||||
service := &service{
|
||||
Supervisor: suture.New("connections.Service", suture.Spec{
|
||||
Log: func(line string) {
|
||||
@@ -149,6 +149,7 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
|
||||
tlsDefaultCommonName: tlsDefaultCommonName,
|
||||
limiter: newLimiter(cfg),
|
||||
natService: nat.NewService(myID, cfg),
|
||||
evLogger: evLogger,
|
||||
|
||||
listenersMut: sync.NewRWMutex(),
|
||||
listeners: make(map[string]genericListener),
|
||||
@@ -231,7 +232,7 @@ func (s *service) handle(stop chan struct{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.SetDeadline(time.Now().Add(20 * time.Second))
|
||||
_ = c.SetDeadline(time.Now().Add(20 * time.Second))
|
||||
hello, err := protocol.ExchangeHello(c, s.model.GetHello(remoteID))
|
||||
if err != nil {
|
||||
if protocol.IsVersionMismatch(err) {
|
||||
@@ -255,7 +256,7 @@ func (s *service) handle(stop chan struct{}) {
|
||||
c.Close()
|
||||
continue
|
||||
}
|
||||
c.SetDeadline(time.Time{})
|
||||
_ = c.SetDeadline(time.Time{})
|
||||
|
||||
// The Model will return an error for devices that we don't want to
|
||||
// have a connection with for whatever reason, for example unknown devices.
|
||||
@@ -553,7 +554,7 @@ func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
|
||||
}
|
||||
|
||||
func (s *service) logListenAddressesChangedEvent(l genericListener) {
|
||||
events.Default.Log(events.ListenAddressesChanged, map[string]interface{}{
|
||||
s.evLogger.Log(events.ListenAddressesChanged, map[string]interface{}{
|
||||
"address": l.URI(),
|
||||
"lan": l.LANAddresses(),
|
||||
"wan": l.WANAddresses(),
|
||||
@@ -580,7 +581,7 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
|
||||
s.listenersMut.Lock()
|
||||
seen := make(map[string]struct{})
|
||||
for _, addr := range config.Wrap("", to).ListenAddresses() {
|
||||
for _, addr := range config.Wrap("", to, s.evLogger).ListenAddresses() {
|
||||
if addr == "" {
|
||||
// We can get an empty address if there is an empty listener
|
||||
// element in the config, indicating no listeners should be
|
||||
@@ -850,8 +851,15 @@ func (s *service) dialParallel(deviceID protocol.DeviceID, dialTargets []dialTar
|
||||
wg.Add(1)
|
||||
go func(tgt dialTarget) {
|
||||
conn, err := tgt.Dial()
|
||||
s.setConnectionStatus(tgt.addr, err)
|
||||
if err == nil {
|
||||
// Closes the connection on error
|
||||
err = s.validateIdentity(conn, deviceID)
|
||||
}
|
||||
s.setConnectionStatus(tgt.addr, err)
|
||||
if err != nil {
|
||||
l.Debugln("dialing", deviceID, tgt.uri, "error:", err)
|
||||
} else {
|
||||
l.Debugln("dialing", deviceID, tgt.uri, "success:", conn)
|
||||
res <- conn
|
||||
}
|
||||
wg.Done()
|
||||
@@ -884,3 +892,36 @@ func (s *service) dialParallel(deviceID protocol.DeviceID, dialTargets []dialTar
|
||||
}
|
||||
return internalConn{}, false
|
||||
}
|
||||
|
||||
func (s *service) validateIdentity(c internalConn, expectedID protocol.DeviceID) error {
|
||||
cs := c.ConnectionState()
|
||||
|
||||
// We should have received exactly one certificate from the other
|
||||
// side. If we didn't, they don't have a device ID and we drop the
|
||||
// connection.
|
||||
certs := cs.PeerCertificates
|
||||
if cl := len(certs); cl != 1 {
|
||||
l.Infof("Got peer certificate list of length %d != 1 from peer at %s; protocol error", cl, c)
|
||||
c.Close()
|
||||
return fmt.Errorf("expected 1 certificate, got %d", cl)
|
||||
}
|
||||
remoteCert := certs[0]
|
||||
remoteID := protocol.NewDeviceID(remoteCert.Raw)
|
||||
|
||||
// The device ID should not be that of ourselves. It can happen
|
||||
// though, especially in the presence of NAT hairpinning, multiple
|
||||
// clients between the same NAT gateway, and global discovery.
|
||||
if remoteID == s.myID {
|
||||
l.Infof("Connected to myself (%s) at %s - should not happen", remoteID, c)
|
||||
c.Close()
|
||||
return fmt.Errorf("connected to self")
|
||||
}
|
||||
|
||||
// We should see the expected device ID
|
||||
if !remoteID.Equals(expectedID) {
|
||||
c.Close()
|
||||
return fmt.Errorf("unexpected device id, expected %s got %s", expectedID, remoteID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -215,11 +215,5 @@ type dialTarget struct {
|
||||
|
||||
func (t dialTarget) Dial() (internalConn, error) {
|
||||
l.Debugln("dialing", t.deviceID, t.uri, "prio", t.priority)
|
||||
conn, err := t.dialer.Dial(t.deviceID, t.uri)
|
||||
if err != nil {
|
||||
l.Debugln("dialing", t.deviceID, t.uri, "error:", err)
|
||||
} else {
|
||||
l.Debugln("dialing", t.deviceID, t.uri, "success:", conn)
|
||||
}
|
||||
return conn, err
|
||||
return t.dialer.Dial(t.deviceID, t.uri)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type tcpDialer struct {
|
||||
tlsCfg *tls.Config
|
||||
}
|
||||
|
||||
func (d *tcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
|
||||
func (d *tcpDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, error) {
|
||||
uri = fixupPort(uri, config.DefaultTCPPort)
|
||||
|
||||
conn, err := dialer.DialTimeout(uri.Scheme, uri.Host, 10*time.Second)
|
||||
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
)
|
||||
|
||||
var files, oneFile, firstHalf, secondHalf []protocol.FileInfo
|
||||
var benchS *db.FileSet
|
||||
|
||||
func lazyInitBenchFileSet() {
|
||||
if benchS != nil {
|
||||
func lazyInitBenchFiles() {
|
||||
if files != nil {
|
||||
return
|
||||
}
|
||||
|
||||
files = make([]protocol.FileInfo, 0, 1000)
|
||||
for i := 0; i < 1000; i++ {
|
||||
files = append(files, protocol.FileInfo{
|
||||
Name: fmt.Sprintf("file%d", i),
|
||||
@@ -35,11 +35,17 @@ func lazyInitBenchFileSet() {
|
||||
firstHalf = files[:middle]
|
||||
secondHalf = files[middle:]
|
||||
oneFile = firstHalf[middle-1 : middle]
|
||||
}
|
||||
|
||||
func getBenchFileSet() (*db.Lowlevel, *db.FileSet) {
|
||||
lazyInitBenchFiles()
|
||||
|
||||
ldb := db.OpenMemory()
|
||||
benchS = db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
|
||||
benchS := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
|
||||
replace(benchS, remoteDevice0, files)
|
||||
replace(benchS, protocol.LocalDeviceID, firstHalf)
|
||||
|
||||
return ldb, benchS
|
||||
}
|
||||
|
||||
func BenchmarkReplaceAll(b *testing.B) {
|
||||
@@ -56,7 +62,8 @@ func BenchmarkReplaceAll(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkUpdateOneChanged(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
changed := make([]protocol.FileInfo, 1)
|
||||
changed[0] = oneFile[0]
|
||||
@@ -75,7 +82,8 @@ func BenchmarkUpdateOneChanged(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkUpdate100Changed(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
unchanged := files[100:200]
|
||||
changed := append([]protocol.FileInfo{}, unchanged...)
|
||||
@@ -96,7 +104,8 @@ func BenchmarkUpdate100Changed(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkUpdate100ChangedRemote(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
unchanged := files[100:200]
|
||||
changed := append([]protocol.FileInfo{}, unchanged...)
|
||||
@@ -117,7 +126,8 @@ func BenchmarkUpdate100ChangedRemote(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkUpdateOneUnchanged(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -128,7 +138,8 @@ func BenchmarkUpdateOneUnchanged(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkNeedHalf(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -146,8 +157,6 @@ func BenchmarkNeedHalf(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkNeedHalfRemote(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
|
||||
ldb := db.OpenMemory()
|
||||
defer ldb.Close()
|
||||
fset := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
|
||||
@@ -170,7 +179,8 @@ func BenchmarkNeedHalfRemote(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkHave(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -188,7 +198,8 @@ func BenchmarkHave(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkGlobal(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -206,7 +217,8 @@ func BenchmarkGlobal(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkNeedHalfTruncated(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -224,7 +236,8 @@ func BenchmarkNeedHalfTruncated(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkHaveTruncated(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -242,7 +255,8 @@ func BenchmarkHaveTruncated(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkGlobalTruncated(b *testing.B) {
|
||||
lazyInitBenchFileSet()
|
||||
ldb, benchS := getBenchFileSet()
|
||||
defer ldb.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestUpdate0to3(t *testing.T) {
|
||||
t.Error("Unexpected additional file via sequence", f.FileName())
|
||||
return true
|
||||
}
|
||||
if e := haveUpdate0to3[protocol.LocalDeviceID][0]; f.IsEquivalentOptional(e, true, true, 0) {
|
||||
if e := haveUpdate0to3[protocol.LocalDeviceID][0]; f.IsEquivalentOptional(e, 0, true, true, 0) {
|
||||
found = true
|
||||
} else {
|
||||
t.Errorf("Wrong file via sequence, got %v, expected %v", f, e)
|
||||
@@ -192,7 +192,7 @@ func TestUpdate0to3(t *testing.T) {
|
||||
}
|
||||
f := fi.(protocol.FileInfo)
|
||||
delete(need, f.Name)
|
||||
if !f.IsEquivalentOptional(e, true, true, 0) {
|
||||
if !f.IsEquivalentOptional(e, 0, true, true, 0) {
|
||||
t.Errorf("Wrong needed file, got %v, expected %v", f, e)
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -8,6 +8,7 @@ package db
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -22,8 +23,26 @@ import (
|
||||
|
||||
const (
|
||||
dbMaxOpenFiles = 100
|
||||
dbWriteBuffer = 16 << 20
|
||||
dbFlushBatch = dbWriteBuffer / 4 // Some leeway for any leveldb in-memory optimizations
|
||||
dbFlushBatch = 4 << MiB
|
||||
|
||||
// A large database is > 200 MiB. It's a mostly arbitrary value, but
|
||||
// it's also the case that each file is 2 MiB by default and when we
|
||||
// have dbMaxOpenFiles of them we will need to start thrashing fd:s.
|
||||
// Switching to large database settings causes larger files to be used
|
||||
// when compacting, reducing the number.
|
||||
dbLargeThreshold = dbMaxOpenFiles * (2 << MiB)
|
||||
|
||||
KiB = 10
|
||||
MiB = 20
|
||||
)
|
||||
|
||||
type Tuning int
|
||||
|
||||
const (
|
||||
// N.b. these constants must match those in lib/config.Tuning!
|
||||
TuningAuto Tuning = iota
|
||||
TuningSmall
|
||||
TuningLarge
|
||||
)
|
||||
|
||||
// Lowlevel is the lowest level database interface. It has a very simple
|
||||
@@ -45,14 +64,79 @@ type Lowlevel struct {
|
||||
// Open attempts to open the database at the given location, and runs
|
||||
// recovery on it if opening fails. Worst case, if recovery is not possible,
|
||||
// the database is erased and created from scratch.
|
||||
func Open(location string) (*Lowlevel, error) {
|
||||
opts := &opt.Options{
|
||||
OpenFilesCacheCapacity: dbMaxOpenFiles,
|
||||
WriteBuffer: dbWriteBuffer,
|
||||
}
|
||||
func Open(location string, tuning Tuning) (*Lowlevel, error) {
|
||||
opts := optsFor(location, tuning)
|
||||
return open(location, opts)
|
||||
}
|
||||
|
||||
// optsFor returns the database options to use when opening a database with
|
||||
// the given location and tuning. Settings can be overridden by debug
|
||||
// environment variables.
|
||||
func optsFor(location string, tuning Tuning) *opt.Options {
|
||||
large := false
|
||||
switch tuning {
|
||||
case TuningLarge:
|
||||
large = true
|
||||
case TuningAuto:
|
||||
large = dbIsLarge(location)
|
||||
}
|
||||
|
||||
var (
|
||||
// Set defaults used for small databases.
|
||||
defaultBlockCacheCapacity = 0 // 0 means let leveldb use default
|
||||
defaultBlockSize = 0
|
||||
defaultCompactionTableSize = 0
|
||||
defaultCompactionTableSizeMultiplier = 0
|
||||
defaultWriteBuffer = 16 << MiB // increased from leveldb default of 4 MiB
|
||||
defaultCompactionL0Trigger = opt.DefaultCompactionL0Trigger // explicit because we use it as base for other stuff
|
||||
)
|
||||
|
||||
if large {
|
||||
// Change the parameters for better throughput at the price of some
|
||||
// RAM and larger files. This results in larger batches of writes
|
||||
// and compaction at a lower frequency.
|
||||
l.Infoln("Using large-database tuning")
|
||||
|
||||
defaultBlockCacheCapacity = 64 << MiB
|
||||
defaultBlockSize = 64 << KiB
|
||||
defaultCompactionTableSize = 16 << MiB
|
||||
defaultCompactionTableSizeMultiplier = 20 // 2.0 after division by ten
|
||||
defaultWriteBuffer = 64 << MiB
|
||||
defaultCompactionL0Trigger = 8 // number of l0 files
|
||||
}
|
||||
|
||||
opts := &opt.Options{
|
||||
BlockCacheCapacity: debugEnvValue("BlockCacheCapacity", defaultBlockCacheCapacity),
|
||||
BlockCacheEvictRemoved: debugEnvValue("BlockCacheEvictRemoved", 0) != 0,
|
||||
BlockRestartInterval: debugEnvValue("BlockRestartInterval", 0),
|
||||
BlockSize: debugEnvValue("BlockSize", defaultBlockSize),
|
||||
CompactionExpandLimitFactor: debugEnvValue("CompactionExpandLimitFactor", 0),
|
||||
CompactionGPOverlapsFactor: debugEnvValue("CompactionGPOverlapsFactor", 0),
|
||||
CompactionL0Trigger: debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger),
|
||||
CompactionSourceLimitFactor: debugEnvValue("CompactionSourceLimitFactor", 0),
|
||||
CompactionTableSize: debugEnvValue("CompactionTableSize", defaultCompactionTableSize),
|
||||
CompactionTableSizeMultiplier: float64(debugEnvValue("CompactionTableSizeMultiplier", defaultCompactionTableSizeMultiplier)) / 10.0,
|
||||
CompactionTotalSize: debugEnvValue("CompactionTotalSize", 0),
|
||||
CompactionTotalSizeMultiplier: float64(debugEnvValue("CompactionTotalSizeMultiplier", 0)) / 10.0,
|
||||
DisableBufferPool: debugEnvValue("DisableBufferPool", 0) != 0,
|
||||
DisableBlockCache: debugEnvValue("DisableBlockCache", 0) != 0,
|
||||
DisableCompactionBackoff: debugEnvValue("DisableCompactionBackoff", 0) != 0,
|
||||
DisableLargeBatchTransaction: debugEnvValue("DisableLargeBatchTransaction", 0) != 0,
|
||||
NoSync: debugEnvValue("NoSync", 0) != 0,
|
||||
NoWriteMerge: debugEnvValue("NoWriteMerge", 0) != 0,
|
||||
OpenFilesCacheCapacity: debugEnvValue("OpenFilesCacheCapacity", dbMaxOpenFiles),
|
||||
WriteBuffer: debugEnvValue("WriteBuffer", defaultWriteBuffer),
|
||||
// The write slowdown and pause can be overridden, but even if they
|
||||
// are not and the compaction trigger is overridden we need to
|
||||
// adjust so that we don't pause writes for L0 compaction before we
|
||||
// even *start* L0 compaction...
|
||||
WriteL0SlowdownTrigger: debugEnvValue("WriteL0SlowdownTrigger", 2*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)),
|
||||
WriteL0PauseTrigger: debugEnvValue("WriteL0SlowdownTrigger", 3*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)),
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
// OpenRO attempts to open the database at the given location, read only.
|
||||
func OpenRO(location string) (*Lowlevel, error) {
|
||||
opts := &opt.Options{
|
||||
@@ -80,6 +164,13 @@ func open(location string, opts *opt.Options) (*Lowlevel, error) {
|
||||
if err != nil {
|
||||
return nil, errorSuggestion{err, "is another instance of Syncthing running?"}
|
||||
}
|
||||
|
||||
if debugEnvValue("CompactEverything", 0) != 0 {
|
||||
if err := db.CompactRange(util.Range{}); err != nil {
|
||||
l.Warnln("Compacting database:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return NewLowlevel(db, location), nil
|
||||
}
|
||||
|
||||
@@ -173,6 +264,31 @@ func (db *Lowlevel) Close() {
|
||||
db.DB.Close()
|
||||
}
|
||||
|
||||
// dbIsLarge returns whether the estimated size of the database at location
|
||||
// is large enough to warrant optimization for large databases.
|
||||
func dbIsLarge(location string) bool {
|
||||
dir, err := os.Open(location)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
fis, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var size int64
|
||||
for _, fi := range fis {
|
||||
if fi.Name() == "LOG" {
|
||||
// don't count the size
|
||||
continue
|
||||
}
|
||||
size += fi.Size()
|
||||
}
|
||||
|
||||
return size > dbLargeThreshold
|
||||
}
|
||||
|
||||
// NewLowlevel wraps the given *leveldb.DB into a *lowlevel
|
||||
func NewLowlevel(db *leveldb.DB, location string) *Lowlevel {
|
||||
return &Lowlevel{
|
||||
@@ -304,3 +420,11 @@ func (it *iter) execIfNotClosed(fn func() bool) bool {
|
||||
}
|
||||
return fn()
|
||||
}
|
||||
|
||||
func debugEnvValue(key string, def int) int {
|
||||
v, err := strconv.ParseInt(os.Getenv("STDEBUG_"+key), 10, 63)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
@@ -729,7 +729,7 @@ func BenchmarkUpdateOneFile(b *testing.B) {
|
||||
protocol.FileInfo{Name: "zajksdhaskjdh/askjdhaskjdashkajshd/kasjdhaskjdhaskdjhaskdjash/dkjashdaksjdhaskdjahskdjh", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)},
|
||||
}
|
||||
|
||||
ldb, err := db.Open("testdata/benchmarkupdate.db")
|
||||
ldb, err := db.Open("testdata/benchmarkupdate.db", db.TuningAuto)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -905,7 +905,7 @@ func TestWithHaveSequence(t *testing.T) {
|
||||
|
||||
i := 2
|
||||
s.WithHaveSequence(int64(i), func(fi db.FileIntf) bool {
|
||||
if f := fi.(protocol.FileInfo); !f.IsEquivalent(localHave[i-1]) {
|
||||
if f := fi.(protocol.FileInfo); !f.IsEquivalent(localHave[i-1], 0) {
|
||||
t.Fatalf("Got %v\nExpected %v", f, localHave[i-1])
|
||||
}
|
||||
i++
|
||||
@@ -1004,7 +1004,7 @@ func TestMoveGlobalBack(t *testing.T) {
|
||||
|
||||
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
|
||||
t.Error("Expected 1 local need, got", need)
|
||||
} else if !need[0].IsEquivalent(remote0Have[0]) {
|
||||
} else if !need[0].IsEquivalent(remote0Have[0], 0) {
|
||||
t.Errorf("Local need incorrect;\n A: %v !=\n E: %v", need[0], remote0Have[0])
|
||||
}
|
||||
|
||||
@@ -1030,7 +1030,7 @@ func TestMoveGlobalBack(t *testing.T) {
|
||||
|
||||
if need := needList(s, remoteDevice0); len(need) != 1 {
|
||||
t.Error("Expected 1 need for remote 0, got", need)
|
||||
} else if !need[0].IsEquivalent(localHave[0]) {
|
||||
} else if !need[0].IsEquivalent(localHave[0], 0) {
|
||||
t.Errorf("Need for remote 0 incorrect;\n A: %v !=\n E: %v", need[0], localHave[0])
|
||||
}
|
||||
|
||||
@@ -1066,7 +1066,7 @@ func TestIssue5007(t *testing.T) {
|
||||
|
||||
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
|
||||
t.Fatal("Expected 1 local need, got", need)
|
||||
} else if !need[0].IsEquivalent(fs[0]) {
|
||||
} else if !need[0].IsEquivalent(fs[0], 0) {
|
||||
t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0])
|
||||
}
|
||||
|
||||
@@ -1101,7 +1101,7 @@ func TestNeedDeleted(t *testing.T) {
|
||||
|
||||
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
|
||||
t.Fatal("Expected 1 local need, got", need)
|
||||
} else if !need[0].IsEquivalent(fs[0]) {
|
||||
} else if !need[0].IsEquivalent(fs[0], 0) {
|
||||
t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0])
|
||||
}
|
||||
|
||||
@@ -1243,7 +1243,7 @@ func TestNeedAfterUnignore(t *testing.T) {
|
||||
|
||||
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
|
||||
t.Fatal("Expected one local need, got", need)
|
||||
} else if !need[0].IsEquivalent(remote) {
|
||||
} else if !need[0].IsEquivalent(remote, 0) {
|
||||
t.Fatalf("Got %v, expected %v", need[0], remote)
|
||||
}
|
||||
}
|
||||
@@ -1287,7 +1287,7 @@ func TestNeedWithNewerInvalid(t *testing.T) {
|
||||
if len(need) != 1 {
|
||||
t.Fatal("Locally missing file should be needed")
|
||||
}
|
||||
if !need[0].IsEquivalent(file) {
|
||||
if !need[0].IsEquivalent(file, 0) {
|
||||
t.Fatalf("Got needed file %v, expected %v", need[0], file)
|
||||
}
|
||||
|
||||
@@ -1302,7 +1302,7 @@ func TestNeedWithNewerInvalid(t *testing.T) {
|
||||
if len(need) != 1 {
|
||||
t.Fatal("Locally missing file should be needed regardless of invalid files")
|
||||
}
|
||||
if !need[0].IsEquivalent(file) {
|
||||
if !need[0].IsEquivalent(file, 0) {
|
||||
t.Fatalf("Got needed file %v, expected %v", need[0], file)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ type globalClient struct {
|
||||
queryClient httpClient
|
||||
noAnnounce bool
|
||||
noLookup bool
|
||||
evLogger events.Logger
|
||||
errorHolder
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ func (e lookupError) CacheFor() time.Duration {
|
||||
return e.cacheFor
|
||||
}
|
||||
|
||||
func NewGlobal(server string, cert tls.Certificate, addrList AddressLister) (FinderService, error) {
|
||||
func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
|
||||
server, opts, err := parseOptions(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -125,6 +126,7 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister) (Fin
|
||||
queryClient: queryClient,
|
||||
noAnnounce: opts.noAnnounce,
|
||||
noLookup: opts.noLookup,
|
||||
evLogger: evLogger,
|
||||
}
|
||||
cl.Service = util.AsService(cl.serve)
|
||||
if !opts.noAnnounce {
|
||||
@@ -197,8 +199,8 @@ func (c *globalClient) serve(stop chan struct{}) {
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
|
||||
eventSub := events.Default.Subscribe(events.ListenAddressesChanged)
|
||||
defer events.Default.Unsubscribe(eventSub)
|
||||
eventSub := c.evLogger.Subscribe(events.ListenAddressesChanged)
|
||||
defer eventSub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
)
|
||||
@@ -54,15 +55,15 @@ func TestGlobalOverHTTP(t *testing.T) {
|
||||
// is only allowed in combination with the "insecure" and "noannounce"
|
||||
// parameters.
|
||||
|
||||
if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil); err == nil {
|
||||
if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil, events.NoopLogger); err == nil {
|
||||
t.Fatal("http is not allowed without insecure and noannounce")
|
||||
}
|
||||
|
||||
if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil); err == nil {
|
||||
if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil, events.NoopLogger); err == nil {
|
||||
t.Fatal("http is not allowed without noannounce")
|
||||
}
|
||||
|
||||
if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil); err == nil {
|
||||
if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil, events.NoopLogger); err == nil {
|
||||
t.Fatal("http is not allowed without insecure")
|
||||
}
|
||||
|
||||
@@ -193,7 +194,7 @@ func TestGlobalAnnounce(t *testing.T) {
|
||||
go func() { _ = http.Serve(list, mux) }()
|
||||
|
||||
url := "https://" + list.Addr().String() + "?insecure"
|
||||
disco, err := NewGlobal(url, cert, new(fakeAddressLister))
|
||||
disco, err := NewGlobal(url, cert, new(fakeAddressLister), events.NoopLogger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -217,7 +218,7 @@ func TestGlobalAnnounce(t *testing.T) {
|
||||
}
|
||||
|
||||
func testLookup(url string) ([]string, error) {
|
||||
disco, err := NewGlobal(url, tls.Certificate{}, nil)
|
||||
disco, err := NewGlobal(url, tls.Certificate{}, nil, events.NoopLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,7 @@ type localClient struct {
|
||||
myID protocol.DeviceID
|
||||
addrList AddressLister
|
||||
name string
|
||||
evLogger events.Logger
|
||||
|
||||
beacon beacon.Interface
|
||||
localBcastStart time.Time
|
||||
@@ -46,13 +48,14 @@ const (
|
||||
v13Magic = uint32(0x7D79BC40) // previous version
|
||||
)
|
||||
|
||||
func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister) (FinderService, error) {
|
||||
func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
|
||||
c := &localClient{
|
||||
Supervisor: suture.New("local", suture.Spec{
|
||||
PassThroughPanics: true,
|
||||
}),
|
||||
myID: id,
|
||||
addrList: addrList,
|
||||
evLogger: evLogger,
|
||||
localBcastTick: time.NewTicker(BroadcastInterval).C,
|
||||
forcedBcastTick: make(chan time.Time),
|
||||
localBcastStart: time.Now(),
|
||||
@@ -71,30 +74,20 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister) (Finder
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.startLocalIPv4Broadcasts(bcPort)
|
||||
c.beacon = beacon.NewBroadcast(bcPort)
|
||||
} else {
|
||||
// A multicast client
|
||||
c.name = "IPv6 local"
|
||||
c.startLocalIPv6Multicasts(addr)
|
||||
c.beacon = beacon.NewMulticast(addr)
|
||||
}
|
||||
c.Add(c.beacon)
|
||||
c.Add(util.AsService(c.recvAnnouncements))
|
||||
|
||||
go c.sendLocalAnnouncements()
|
||||
c.Add(util.AsService(c.sendLocalAnnouncements))
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *localClient) startLocalIPv4Broadcasts(localPort int) {
|
||||
c.beacon = beacon.NewBroadcast(localPort)
|
||||
c.Add(c.beacon)
|
||||
go c.recvAnnouncements(c.beacon)
|
||||
}
|
||||
|
||||
func (c *localClient) startLocalIPv6Multicasts(localMCAddr string) {
|
||||
c.beacon = beacon.NewMulticast(localMCAddr)
|
||||
c.Add(c.beacon)
|
||||
go c.recvAnnouncements(c.beacon)
|
||||
}
|
||||
|
||||
// Lookup returns a list of addresses the device is available at.
|
||||
func (c *localClient) Lookup(device protocol.DeviceID) (addresses []string, err error) {
|
||||
if cache, ok := c.Get(device); ok {
|
||||
@@ -142,7 +135,7 @@ func (c *localClient) announcementPkt(instanceID int64, msg []byte) ([]byte, boo
|
||||
return msg, true
|
||||
}
|
||||
|
||||
func (c *localClient) sendLocalAnnouncements() {
|
||||
func (c *localClient) sendLocalAnnouncements(stop chan struct{}) {
|
||||
var msg []byte
|
||||
var ok bool
|
||||
instanceID := rand.Int63()
|
||||
@@ -154,13 +147,22 @@ func (c *localClient) sendLocalAnnouncements() {
|
||||
select {
|
||||
case <-c.localBcastTick:
|
||||
case <-c.forcedBcastTick:
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *localClient) recvAnnouncements(b beacon.Interface) {
|
||||
func (c *localClient) recvAnnouncements(stop chan struct{}) {
|
||||
b := c.beacon
|
||||
warnedAbout := make(map[string]bool)
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
buf, addr := b.Recv()
|
||||
if len(buf) < 4 {
|
||||
l.Debugf("discover: short packet from %s")
|
||||
@@ -272,7 +274,7 @@ func (c *localClient) registerDevice(src net.Addr, device Announce) bool {
|
||||
})
|
||||
|
||||
if isNewDevice {
|
||||
events.Default.Log(events.DeviceDiscovered, map[string]interface{}{
|
||||
c.evLogger.Log(events.DeviceDiscovered, map[string]interface{}{
|
||||
"device": device.ID.String(),
|
||||
"addrs": validAddresses,
|
||||
})
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
|
||||
package discover
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
import github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
|
||||
|
||||
import io "io"
|
||||
import (
|
||||
fmt "fmt"
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
@@ -21,7 +22,7 @@ var _ = math.Inf
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Announce struct {
|
||||
ID github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=id,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"id"`
|
||||
@@ -33,7 +34,7 @@ func (m *Announce) Reset() { *m = Announce{} }
|
||||
func (m *Announce) String() string { return proto.CompactTextString(m) }
|
||||
func (*Announce) ProtoMessage() {}
|
||||
func (*Announce) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_local_652287d527eec38f, []int{0}
|
||||
return fileDescriptor_aaf1a48d01603033, []int{0}
|
||||
}
|
||||
func (m *Announce) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -43,15 +44,15 @@ func (m *Announce) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Announce.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *Announce) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Announce.Merge(dst, src)
|
||||
func (m *Announce) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Announce.Merge(m, src)
|
||||
}
|
||||
func (m *Announce) XXX_Size() int {
|
||||
return m.ProtoSize()
|
||||
@@ -65,10 +66,33 @@ var xxx_messageInfo_Announce proto.InternalMessageInfo
|
||||
func init() {
|
||||
proto.RegisterType((*Announce)(nil), "discover.Announce")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("local.proto", fileDescriptor_aaf1a48d01603033) }
|
||||
|
||||
var fileDescriptor_aaf1a48d01603033 = []byte{
|
||||
// 252 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0x31, 0x4e, 0x84, 0x40,
|
||||
0x14, 0x86, 0x67, 0x20, 0x31, 0xbb, 0xb3, 0xc6, 0x82, 0x8a, 0x18, 0x33, 0x10, 0x6d, 0xa8, 0xa0,
|
||||
0xd0, 0x0b, 0x48, 0x68, 0xa6, 0xe5, 0x02, 0x06, 0x66, 0x46, 0xf6, 0x25, 0x38, 0x6f, 0xc3, 0xc0,
|
||||
0x26, 0xde, 0xc2, 0x23, 0x78, 0x01, 0xef, 0x41, 0xb9, 0xa5, 0xb1, 0x20, 0x3a, 0x5c, 0xc4, 0x04,
|
||||
0x34, 0xda, 0x7d, 0xef, 0xcb, 0x97, 0xbc, 0x9f, 0xed, 0x5a, 0x94, 0x55, 0x9b, 0x1e, 0x3a, 0xec,
|
||||
0x31, 0xd8, 0x28, 0xb0, 0x12, 0x8f, 0xba, 0xbb, 0xbc, 0xe9, 0xf4, 0x01, 0x6d, 0xb6, 0xe8, 0x7a,
|
||||
0x78, 0xcc, 0x1a, 0x6c, 0x70, 0x39, 0x16, 0x5a, 0xf3, 0xeb, 0x37, 0xca, 0x36, 0xf7, 0xc6, 0xe0,
|
||||
0x60, 0xa4, 0x0e, 0x4a, 0xe6, 0x81, 0x0a, 0x69, 0x4c, 0x93, 0xf3, 0x3c, 0x1f, 0xa7, 0x88, 0x7c,
|
||||
0x4c, 0xd1, 0x5d, 0x03, 0xfd, 0x7e, 0xa8, 0x53, 0x89, 0x4f, 0x99, 0x7d, 0x36, 0xb2, 0xdf, 0x83,
|
||||
0x69, 0xfe, 0x51, 0x0b, 0xf5, 0xfa, 0x42, 0x62, 0x9b, 0x16, 0xfa, 0x08, 0x52, 0x8b, 0xc2, 0x4d,
|
||||
0x91, 0x27, 0x8a, 0xd2, 0x03, 0x15, 0x5c, 0xb1, 0x6d, 0xa5, 0x54, 0xa7, 0xad, 0xd5, 0x36, 0xf4,
|
||||
0x62, 0x3f, 0xd9, 0x96, 0x7f, 0x22, 0xc8, 0xd8, 0x0e, 0x8c, 0xed, 0x2b, 0x23, 0xf5, 0x03, 0xa8,
|
||||
0xd0, 0x8f, 0x69, 0xe2, 0xe7, 0x17, 0x6e, 0x8a, 0x98, 0xf8, 0xd1, 0xa2, 0x28, 0xd9, 0x6f, 0x22,
|
||||
0x54, 0x1e, 0x8f, 0x5f, 0x9c, 0x8c, 0x8e, 0xd3, 0x93, 0xe3, 0xf4, 0xd3, 0x71, 0xf2, 0x32, 0x73,
|
||||
0xf2, 0x3a, 0x73, 0x7a, 0x9a, 0x39, 0x79, 0x9f, 0x39, 0xa9, 0xcf, 0x96, 0x35, 0xb7, 0xdf, 0x01,
|
||||
0x00, 0x00, 0xff, 0xff, 0xbc, 0x46, 0xaf, 0x1d, 0x16, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *Announce) Marshal() (dAtA []byte, err error) {
|
||||
size := m.ProtoSize()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,49 +100,52 @@ func (m *Announce) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *Announce) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.ProtoSize()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Announce) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintLocal(dAtA, i, uint64(m.ID.ProtoSize()))
|
||||
n1, err := m.ID.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if m.InstanceID != 0 {
|
||||
i = encodeVarintLocal(dAtA, i, uint64(m.InstanceID))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
i += n1
|
||||
if len(m.Addresses) > 0 {
|
||||
for _, s := range m.Addresses {
|
||||
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
i -= len(m.Addresses[iNdEx])
|
||||
copy(dAtA[i:], m.Addresses[iNdEx])
|
||||
i = encodeVarintLocal(dAtA, i, uint64(len(m.Addresses[iNdEx])))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
l = len(s)
|
||||
for l >= 1<<7 {
|
||||
dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
|
||||
l >>= 7
|
||||
i++
|
||||
}
|
||||
dAtA[i] = uint8(l)
|
||||
i++
|
||||
i += copy(dAtA[i:], s)
|
||||
}
|
||||
}
|
||||
if m.InstanceID != 0 {
|
||||
dAtA[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintLocal(dAtA, i, uint64(m.InstanceID))
|
||||
{
|
||||
size := m.ID.ProtoSize()
|
||||
i -= size
|
||||
if _, err := m.ID.MarshalTo(dAtA[i:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i = encodeVarintLocal(dAtA, i, uint64(size))
|
||||
}
|
||||
return i, nil
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintLocal(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovLocal(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
return base
|
||||
}
|
||||
func (m *Announce) ProtoSize() (n int) {
|
||||
if m == nil {
|
||||
@@ -141,14 +168,7 @@ func (m *Announce) ProtoSize() (n int) {
|
||||
}
|
||||
|
||||
func sovLocal(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozLocal(x uint64) (n int) {
|
||||
return sovLocal(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
@@ -168,7 +188,7 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -196,7 +216,7 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -205,6 +225,9 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthLocal
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthLocal
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -226,7 +249,7 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -236,6 +259,9 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthLocal
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthLocal
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -255,7 +281,7 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.InstanceID |= (int64(b) & 0x7F) << shift
|
||||
m.InstanceID |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -269,6 +295,9 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthLocal
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthLocal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -335,10 +364,13 @@ func skipLocal(dAtA []byte) (n int, err error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthLocal
|
||||
}
|
||||
iNdEx += length
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthLocal
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
@@ -367,6 +399,9 @@ func skipLocal(dAtA []byte) (n int, err error) {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthLocal
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
@@ -385,25 +420,3 @@ var (
|
||||
ErrInvalidLengthLocal = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowLocal = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("local.proto", fileDescriptor_local_652287d527eec38f) }
|
||||
|
||||
var fileDescriptor_local_652287d527eec38f = []byte{
|
||||
// 252 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0x31, 0x4e, 0x84, 0x40,
|
||||
0x14, 0x86, 0x67, 0x20, 0x31, 0xbb, 0xb3, 0xc6, 0x82, 0x8a, 0x18, 0x33, 0x10, 0x6d, 0xa8, 0xa0,
|
||||
0xd0, 0x0b, 0x48, 0x68, 0xa6, 0xe5, 0x02, 0x06, 0x66, 0x46, 0xf6, 0x25, 0x38, 0x6f, 0xc3, 0xc0,
|
||||
0x26, 0xde, 0xc2, 0x23, 0x78, 0x01, 0xef, 0x41, 0xb9, 0xa5, 0xb1, 0x20, 0x3a, 0x5c, 0xc4, 0x04,
|
||||
0x34, 0xda, 0x7d, 0xef, 0xcb, 0x97, 0xbc, 0x9f, 0xed, 0x5a, 0x94, 0x55, 0x9b, 0x1e, 0x3a, 0xec,
|
||||
0x31, 0xd8, 0x28, 0xb0, 0x12, 0x8f, 0xba, 0xbb, 0xbc, 0xe9, 0xf4, 0x01, 0x6d, 0xb6, 0xe8, 0x7a,
|
||||
0x78, 0xcc, 0x1a, 0x6c, 0x70, 0x39, 0x16, 0x5a, 0xf3, 0xeb, 0x37, 0xca, 0x36, 0xf7, 0xc6, 0xe0,
|
||||
0x60, 0xa4, 0x0e, 0x4a, 0xe6, 0x81, 0x0a, 0x69, 0x4c, 0x93, 0xf3, 0x3c, 0x1f, 0xa7, 0x88, 0x7c,
|
||||
0x4c, 0xd1, 0x5d, 0x03, 0xfd, 0x7e, 0xa8, 0x53, 0x89, 0x4f, 0x99, 0x7d, 0x36, 0xb2, 0xdf, 0x83,
|
||||
0x69, 0xfe, 0x51, 0x0b, 0xf5, 0xfa, 0x42, 0x62, 0x9b, 0x16, 0xfa, 0x08, 0x52, 0x8b, 0xc2, 0x4d,
|
||||
0x91, 0x27, 0x8a, 0xd2, 0x03, 0x15, 0x5c, 0xb1, 0x6d, 0xa5, 0x54, 0xa7, 0xad, 0xd5, 0x36, 0xf4,
|
||||
0x62, 0x3f, 0xd9, 0x96, 0x7f, 0x22, 0xc8, 0xd8, 0x0e, 0x8c, 0xed, 0x2b, 0x23, 0xf5, 0x03, 0xa8,
|
||||
0xd0, 0x8f, 0x69, 0xe2, 0xe7, 0x17, 0x6e, 0x8a, 0x98, 0xf8, 0xd1, 0xa2, 0x28, 0xd9, 0x6f, 0x22,
|
||||
0x54, 0x1e, 0x8f, 0x5f, 0x9c, 0x8c, 0x8e, 0xd3, 0x93, 0xe3, 0xf4, 0xd3, 0x71, 0xf2, 0x32, 0x73,
|
||||
0xf2, 0x3a, 0x73, 0x7a, 0x9a, 0x39, 0x79, 0x9f, 0x39, 0xa9, 0xcf, 0x96, 0x35, 0xb7, 0xdf, 0x01,
|
||||
0x00, 0x00, 0xff, 0xff, 0xbc, 0x46, 0xaf, 0x1d, 0x16, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
func TestLocalInstanceID(t *testing.T) {
|
||||
c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{})
|
||||
c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{}, events.NoopLogger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -38,7 +39,7 @@ func TestLocalInstanceID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLocalInstanceIDShouldTriggerNew(t *testing.T) {
|
||||
c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{})
|
||||
c, err := NewLocal(protocol.LocalDeviceID, ":0", &fakeAddressLister{}, events.NoopLogger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
liblogger "github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
dl = logger.DefaultLogger.NewFacility("events", "Event generation and logging")
|
||||
dl = liblogger.DefaultLogger.NewFacility("events", "Event generation and logging")
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -13,7 +13,10 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
type EventType int
|
||||
@@ -51,7 +54,10 @@ const (
|
||||
AllEvents = (1 << iota) - 1
|
||||
)
|
||||
|
||||
var runningTests = false
|
||||
var (
|
||||
runningTests = false
|
||||
errNoop = errors.New("method of a noop object called")
|
||||
)
|
||||
|
||||
const eventLogTimeout = 15 * time.Millisecond
|
||||
|
||||
@@ -199,13 +205,21 @@ func UnmarshalEventType(s string) EventType {
|
||||
|
||||
const BufferSize = 64
|
||||
|
||||
type Logger struct {
|
||||
subs []*Subscription
|
||||
type Logger interface {
|
||||
suture.Service
|
||||
Log(t EventType, data interface{})
|
||||
Subscribe(mask EventType) Subscription
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
suture.Service
|
||||
subs []*subscription
|
||||
nextSubscriptionIDs []int
|
||||
nextGlobalID int
|
||||
timeout *time.Timer
|
||||
events chan Event
|
||||
funcs chan func()
|
||||
toUnsubscribe chan *subscription
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
@@ -219,19 +233,17 @@ type Event struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
mask EventType
|
||||
events chan Event
|
||||
timeout *time.Timer
|
||||
type Subscription interface {
|
||||
C() <-chan Event
|
||||
Poll(timeout time.Duration) (Event, error)
|
||||
Unsubscribe()
|
||||
}
|
||||
|
||||
var Default = NewLogger()
|
||||
|
||||
func init() {
|
||||
// The default logger never stops. To ensure this we nil out the stop
|
||||
// channel so any attempt to stop it will panic.
|
||||
Default.stop = nil
|
||||
go Default.Serve()
|
||||
type subscription struct {
|
||||
mask EventType
|
||||
events chan Event
|
||||
toUnsubscribe chan *subscription
|
||||
timeout *time.Timer
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -239,13 +251,14 @@ var (
|
||||
ErrClosed = errors.New("closed")
|
||||
)
|
||||
|
||||
func NewLogger() *Logger {
|
||||
l := &Logger{
|
||||
timeout: time.NewTimer(time.Second),
|
||||
events: make(chan Event, BufferSize),
|
||||
funcs: make(chan func()),
|
||||
stop: make(chan struct{}),
|
||||
func NewLogger() Logger {
|
||||
l := &logger{
|
||||
timeout: time.NewTimer(time.Second),
|
||||
events: make(chan Event, BufferSize),
|
||||
funcs: make(chan func()),
|
||||
toUnsubscribe: make(chan *subscription),
|
||||
}
|
||||
l.Service = util.AsService(l.serve)
|
||||
// Make sure the timer is in the stopped state and hasn't fired anything
|
||||
// into the channel.
|
||||
if !l.timeout.Stop() {
|
||||
@@ -254,7 +267,7 @@ func NewLogger() *Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logger) Serve() {
|
||||
func (l *logger) serve(stop chan struct{}) {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
@@ -263,10 +276,13 @@ loop:
|
||||
l.sendEvent(e)
|
||||
|
||||
case fn := <-l.funcs:
|
||||
// Subscriptions etc are handled here.
|
||||
// Subscriptions are handled here.
|
||||
fn()
|
||||
|
||||
case <-l.stop:
|
||||
case s := <-l.toUnsubscribe:
|
||||
l.unsubscribe(s)
|
||||
|
||||
case <-stop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
@@ -279,11 +295,7 @@ loop:
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Stop() {
|
||||
close(l.stop)
|
||||
}
|
||||
|
||||
func (l *Logger) Log(t EventType, data interface{}) {
|
||||
func (l *logger) Log(t EventType, data interface{}) {
|
||||
l.events <- Event{
|
||||
Time: time.Now(),
|
||||
Type: t,
|
||||
@@ -292,7 +304,7 @@ func (l *Logger) Log(t EventType, data interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) sendEvent(e Event) {
|
||||
func (l *logger) sendEvent(e Event) {
|
||||
l.nextGlobalID++
|
||||
dl.Debugln("log", l.nextGlobalID, e.Type, e.Data)
|
||||
|
||||
@@ -323,15 +335,16 @@ func (l *Logger) sendEvent(e Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
res := make(chan *Subscription)
|
||||
func (l *logger) Subscribe(mask EventType) Subscription {
|
||||
res := make(chan Subscription)
|
||||
l.funcs <- func() {
|
||||
dl.Debugln("subscribe", mask)
|
||||
|
||||
s := &Subscription{
|
||||
mask: mask,
|
||||
events: make(chan Event, BufferSize),
|
||||
timeout: time.NewTimer(0),
|
||||
s := &subscription{
|
||||
mask: mask,
|
||||
events: make(chan Event, BufferSize),
|
||||
toUnsubscribe: l.toUnsubscribe,
|
||||
timeout: time.NewTimer(0),
|
||||
}
|
||||
|
||||
// We need to create the timeout timer in the stopped, non-fired state so
|
||||
@@ -355,32 +368,30 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
return <-res
|
||||
}
|
||||
|
||||
func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
l.funcs <- func() {
|
||||
dl.Debugln("unsubscribe")
|
||||
for i, ss := range l.subs {
|
||||
if s == ss {
|
||||
last := len(l.subs) - 1
|
||||
func (l *logger) unsubscribe(s *subscription) {
|
||||
dl.Debugln("unsubscribe", s.mask)
|
||||
for i, ss := range l.subs {
|
||||
if s == ss {
|
||||
last := len(l.subs) - 1
|
||||
|
||||
l.subs[i] = l.subs[last]
|
||||
l.subs[last] = nil
|
||||
l.subs = l.subs[:last]
|
||||
l.subs[i] = l.subs[last]
|
||||
l.subs[last] = nil
|
||||
l.subs = l.subs[:last]
|
||||
|
||||
l.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
|
||||
l.nextSubscriptionIDs[last] = 0
|
||||
l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
|
||||
l.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
|
||||
l.nextSubscriptionIDs[last] = 0
|
||||
l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
|
||||
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
close(s.events)
|
||||
}
|
||||
close(s.events)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (s *subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
dl.Debugln("poll", timeout)
|
||||
|
||||
s.timeout.Reset(timeout)
|
||||
@@ -409,12 +420,16 @@ func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) C() <-chan Event {
|
||||
func (s *subscription) C() <-chan Event {
|
||||
return s.events
|
||||
}
|
||||
|
||||
func (s *subscription) Unsubscribe() {
|
||||
s.toUnsubscribe <- s
|
||||
}
|
||||
|
||||
type bufferedSubscription struct {
|
||||
sub *Subscription
|
||||
sub Subscription
|
||||
buf []Event
|
||||
next int
|
||||
cur int // Current SubscriptionID
|
||||
@@ -426,7 +441,7 @@ type BufferedSubscription interface {
|
||||
Since(id int, into []Event, timeout time.Duration) []Event
|
||||
}
|
||||
|
||||
func NewBufferedSubscription(s *Subscription, size int) BufferedSubscription {
|
||||
func NewBufferedSubscription(s Subscription, size int) BufferedSubscription {
|
||||
bs := &bufferedSubscription{
|
||||
sub: s,
|
||||
buf: make([]Event, size),
|
||||
@@ -489,3 +504,29 @@ func Error(err error) *string {
|
||||
str := err.Error()
|
||||
return &str
|
||||
}
|
||||
|
||||
type noopLogger struct{}
|
||||
|
||||
var NoopLogger Logger = &noopLogger{}
|
||||
|
||||
func (*noopLogger) Serve() {}
|
||||
|
||||
func (*noopLogger) Stop() {}
|
||||
|
||||
func (*noopLogger) Log(t EventType, data interface{}) {}
|
||||
|
||||
func (*noopLogger) Subscribe(mask EventType) Subscription {
|
||||
return &noopSubscription{}
|
||||
}
|
||||
|
||||
type noopSubscription struct{}
|
||||
|
||||
func (*noopSubscription) C() <-chan Event {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*noopSubscription) Poll(timeout time.Duration) (Event, error) {
|
||||
return Event{}, errNoop
|
||||
}
|
||||
|
||||
func (*noopSubscription) Unsubscribe() {}
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestSubscriber(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(0)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
if s == nil {
|
||||
t.Fatal("Unexpected nil Subscription")
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func TestTimeout(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(0)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
_, err := s.Poll(timeout)
|
||||
if err != ErrTimeout {
|
||||
t.Fatal("Unexpected non-Timeout error:", err)
|
||||
@@ -59,7 +59,7 @@ func TestEventBeforeSubscribe(t *testing.T) {
|
||||
|
||||
l.Log(DeviceConnected, "foo")
|
||||
s := l.Subscribe(0)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
_, err := s.Poll(timeout)
|
||||
if err != ErrTimeout {
|
||||
@@ -73,7 +73,7 @@ func TestEventAfterSubscribe(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
|
||||
ev, err := s.Poll(timeout)
|
||||
@@ -100,7 +100,7 @@ func TestEventAfterSubscribeIgnoreMask(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(DeviceDisconnected)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
|
||||
_, err := s.Poll(timeout)
|
||||
@@ -115,7 +115,7 @@ func TestBufferOverflow(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
// The first BufferSize events will be logged pretty much
|
||||
// instantaneously. The next BufferSize events will each block for up to
|
||||
@@ -147,7 +147,7 @@ func TestUnsubscribe(t *testing.T) {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
|
||||
l.Unsubscribe(s)
|
||||
s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
|
||||
_, err = s.Poll(timeout)
|
||||
@@ -162,7 +162,7 @@ func TestGlobalIDs(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
l.Subscribe(AllEvents)
|
||||
l.Log(DeviceConnected, "bar")
|
||||
@@ -194,7 +194,7 @@ func TestSubscriptionIDs(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(DeviceConnected)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
l.Log(DeviceDisconnected, "a")
|
||||
l.Log(DeviceConnected, "b")
|
||||
@@ -236,7 +236,7 @@ func TestBufferedSub(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
bs := NewBufferedSubscription(s, 10*BufferSize)
|
||||
|
||||
go func() {
|
||||
@@ -267,7 +267,7 @@ func BenchmarkBufferedSub(b *testing.B) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
bufferSize := BufferSize
|
||||
bs := NewBufferedSubscription(s, bufferSize)
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestSinceUsesSubscriptionId(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(DeviceConnected)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
bs := NewBufferedSubscription(s, 10*BufferSize)
|
||||
|
||||
l.Log(DeviceConnected, "a") // SubscriptionID = 1
|
||||
@@ -390,7 +390,7 @@ func TestUnsubscribeContention(t *testing.T) {
|
||||
defer listenerWg.Done()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -449,7 +449,7 @@ func BenchmarkLogEvent(b *testing.B) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
NewBufferedSubscription(s, 1) // runs in the background
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/calmh/du"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -266,11 +266,14 @@ func (f *BasicFilesystem) Usage(name string) (Usage, error) {
|
||||
if err != nil {
|
||||
return Usage{}, err
|
||||
}
|
||||
u, err := du.Get(name)
|
||||
u, err := disk.Usage(name)
|
||||
if err != nil {
|
||||
return Usage{}, err
|
||||
}
|
||||
return Usage{
|
||||
Free: u.FreeBytes,
|
||||
Total: u.TotalBytes,
|
||||
}, err
|
||||
Free: int64(u.Free),
|
||||
Total: int64(u.Total),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Type() FilesystemType {
|
||||
|
||||
@@ -461,7 +461,18 @@ func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd Chan
|
||||
line = filepath.ToSlash(line)
|
||||
switch {
|
||||
case strings.HasPrefix(line, "#include"):
|
||||
includeRel := strings.TrimSpace(line[len("#include "):])
|
||||
fields := strings.SplitN(line, " ", 2)
|
||||
if len(fields) != 2 {
|
||||
err = fmt.Errorf("failed to parse #include line: no file?")
|
||||
break
|
||||
}
|
||||
|
||||
includeRel := strings.TrimSpace(fields[1])
|
||||
if includeRel == "" {
|
||||
err = fmt.Errorf("failed to parse #include line: no file?")
|
||||
break
|
||||
}
|
||||
|
||||
includeFile := filepath.Join(filepath.Dir(currentFile), includeRel)
|
||||
var includePatterns []Pattern
|
||||
if includePatterns, err = loadParseIncludeFile(fs, includeFile, cd, linesSeen); err == nil {
|
||||
|
||||
@@ -1079,3 +1079,22 @@ func TestSpecialChars(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialIncludeLine(t *testing.T) {
|
||||
// Loading a partial #include line (no file mentioned) should error but not crash.
|
||||
|
||||
pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
|
||||
cases := []string{
|
||||
"#include",
|
||||
"#include\n",
|
||||
"#include ",
|
||||
"#include \n",
|
||||
"#include \n\n\n",
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
if err := pats.Parse(bytes.NewBufferString(tc), ".stignore"); err == nil {
|
||||
t.Fatal("should error out")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ const (
|
||||
func init() {
|
||||
err := expandLocations()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Println(err)
|
||||
panic("Failed to expand locations at init time")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +125,8 @@ func defaultConfigDir() string {
|
||||
case "darwin":
|
||||
dir, err := fs.ExpandTilde("~/Library/Application Support/Syncthing")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Println(err)
|
||||
panic("Failed to get default config dir")
|
||||
}
|
||||
return dir
|
||||
|
||||
@@ -134,7 +136,8 @@ func defaultConfigDir() string {
|
||||
}
|
||||
dir, err := fs.ExpandTilde("~/.config/syncthing")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Println(err)
|
||||
panic("Failed to get default config dir")
|
||||
}
|
||||
return dir
|
||||
}
|
||||
@@ -144,7 +147,8 @@ func defaultConfigDir() string {
|
||||
func homeDir() string {
|
||||
home, err := fs.ExpandTilde("~")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Println(err)
|
||||
panic("Failed to get user home dir")
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
@@ -77,11 +77,11 @@ type puller interface {
|
||||
pull() bool // true when successfull and should not be retried
|
||||
}
|
||||
|
||||
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration) folder {
|
||||
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger) folder {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return folder{
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
stateTracker: newStateTracker(cfg.ID, evLogger),
|
||||
FolderConfiguration: cfg,
|
||||
FolderStatisticsReference: stats.NewFolderStatisticsReference(model.db, cfg.ID),
|
||||
|
||||
@@ -171,20 +171,23 @@ func (f *folder) serve(_ chan struct{}) {
|
||||
}
|
||||
|
||||
case <-f.scanTimer.C:
|
||||
l.Debugln(f, "Scanning subdirectories")
|
||||
l.Debugln(f, "Scanning due to timer")
|
||||
f.scanTimerFired()
|
||||
|
||||
case req := <-f.scanNow:
|
||||
l.Debugln(f, "Scanning due to request")
|
||||
req.err <- f.scanSubdirs(req.subdirs)
|
||||
|
||||
case next := <-f.scanDelay:
|
||||
l.Debugln(f, "Delaying scan")
|
||||
f.scanTimer.Reset(next)
|
||||
|
||||
case fsEvents := <-f.watchChan:
|
||||
l.Debugln(f, "filesystem notification rescan")
|
||||
l.Debugln(f, "Scan due to watcher")
|
||||
f.scanSubdirs(fsEvents)
|
||||
|
||||
case <-f.restartWatchChan:
|
||||
l.Debugln(f, "Restart watcher")
|
||||
f.restartWatch()
|
||||
}
|
||||
}
|
||||
@@ -283,13 +286,38 @@ func (f *folder) getHealthError() error {
|
||||
}
|
||||
|
||||
func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
if err := f.CheckHealth(); err != nil {
|
||||
if err := f.getHealthError(); err != nil {
|
||||
// If there is a health error we set it as the folder error. We do not
|
||||
// clear the folder error if there is no health error, as there might be
|
||||
// an *other* folder error (failed to load ignores, for example). Hence
|
||||
// we do not use the CheckHealth() convenience function here.
|
||||
f.setError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
mtimefs := f.fset.MtimeFS()
|
||||
oldHash := f.ignores.Hash()
|
||||
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||
err = fmt.Errorf("loading ignores: %v", err)
|
||||
f.setError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check on the way out if the ignore patterns changed as part of scanning
|
||||
// this folder. If they did we should schedule a pull of the folder so that
|
||||
// we request things we might have suddenly become unignored and so on.
|
||||
defer func() {
|
||||
if f.ignores.Hash() != oldHash {
|
||||
l.Debugln("Folder", f.Description(), "ignore patterns change detected while scanning; triggering puller")
|
||||
f.ignoresUpdated()
|
||||
f.SchedulePull()
|
||||
}
|
||||
}()
|
||||
|
||||
// We've passed all health checks so now mark ourselves healthy and queued
|
||||
// for scanning.
|
||||
f.setError(nil)
|
||||
f.setState(FolderScanWaiting)
|
||||
|
||||
scanLimiter.take(1)
|
||||
defer scanLimiter.give(1)
|
||||
|
||||
@@ -306,24 +334,6 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
subDirs[i] = sub
|
||||
}
|
||||
|
||||
// Check if the ignore patterns changed as part of scanning this folder.
|
||||
// If they did we should schedule a pull of the folder so that we
|
||||
// request things we might have suddenly become unignored and so on.
|
||||
oldHash := f.ignores.Hash()
|
||||
defer func() {
|
||||
if f.ignores.Hash() != oldHash {
|
||||
l.Debugln("Folder", f.Description(), "ignore patterns change detected while scanning; triggering puller")
|
||||
f.ignoresUpdated()
|
||||
f.SchedulePull()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||
err = fmt.Errorf("loading ignores: %v", err)
|
||||
f.setError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean the list of subitems to ensure that we start at a known
|
||||
// directory, and don't scan subdirectories of things we've already
|
||||
// scanned.
|
||||
@@ -334,6 +344,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
|
||||
f.setState(FolderScanning)
|
||||
|
||||
mtimefs := f.fset.MtimeFS()
|
||||
fchan := scanner.Walk(f.ctx, scanner.Config{
|
||||
Folder: f.ID,
|
||||
Subs: subDirs,
|
||||
@@ -347,6 +358,8 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
ShortID: f.shortID,
|
||||
ProgressTickIntervalS: f.ScanProgressIntervalS,
|
||||
LocalFlags: f.localFlags,
|
||||
ModTimeWindow: f.ModTimeWindow(),
|
||||
EventLogger: f.evLogger,
|
||||
})
|
||||
|
||||
batchFn := func(fs []protocol.FileInfo) error {
|
||||
@@ -365,7 +378,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
switch gf, ok := f.fset.GetGlobal(fs[i].Name); {
|
||||
case !ok:
|
||||
continue
|
||||
case gf.IsEquivalentOptional(fs[i], false, false, protocol.FlagLocalReceiveOnly):
|
||||
case gf.IsEquivalentOptional(fs[i], f.ModTimeWindow(), false, false, protocol.FlagLocalReceiveOnly):
|
||||
// What we have locally is equivalent to the global file.
|
||||
fs[i].Version = fs[i].Version.Merge(gf.Version)
|
||||
fallthrough
|
||||
@@ -629,7 +642,7 @@ func (f *folder) monitorWatch(ctx context.Context) {
|
||||
failTimer.Reset(time.Minute)
|
||||
continue
|
||||
}
|
||||
watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, aggrCtx)
|
||||
watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, f.evLogger, aggrCtx)
|
||||
l.Debugln("Started filesystem watcher for folder", f.Description())
|
||||
case err = <-errChan:
|
||||
f.setWatchError(err)
|
||||
@@ -668,7 +681,7 @@ func (f *folder) setWatchError(err error) {
|
||||
if err != nil {
|
||||
data["to"] = err.Error()
|
||||
}
|
||||
events.Default.Log(events.FolderWatchStateChanged, data)
|
||||
f.evLogger.Log(events.FolderWatchStateChanged, data)
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
@@ -684,10 +697,11 @@ func (f *folder) setWatchError(err error) {
|
||||
// scanOnWatchErr schedules a full scan immediately if an error occurred while watching.
|
||||
func (f *folder) scanOnWatchErr() {
|
||||
f.watchMut.Lock()
|
||||
if f.watchErr != nil {
|
||||
err := f.watchErr
|
||||
f.watchMut.Unlock()
|
||||
if err != nil {
|
||||
f.Delay(0)
|
||||
}
|
||||
f.watchMut.Unlock()
|
||||
}
|
||||
|
||||
func (f *folder) setError(err error) {
|
||||
@@ -798,7 +812,7 @@ func (f *folder) updateLocals(fs []protocol.FileInfo) {
|
||||
filenames[i] = file.Name
|
||||
}
|
||||
|
||||
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
|
||||
f.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{
|
||||
"folder": f.ID,
|
||||
"items": len(fs),
|
||||
"filenames": filenames,
|
||||
@@ -837,7 +851,7 @@ func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events
|
||||
}
|
||||
|
||||
// Two different events can be fired here based on what EventType is passed into function
|
||||
events.Default.Log(typeOfEvent, map[string]string{
|
||||
f.evLogger.Log(typeOfEvent, map[string]string{
|
||||
"folder": f.ID,
|
||||
"folderID": f.ID, // incorrect, deprecated, kept for historical compliance
|
||||
"label": f.Label,
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
@@ -56,8 +57,8 @@ type receiveOnlyFolder struct {
|
||||
*sendReceiveFolder
|
||||
}
|
||||
|
||||
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
|
||||
sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs).(*sendReceiveFolder)
|
||||
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger) service {
|
||||
sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs, evLogger).(*sendReceiveFolder)
|
||||
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
|
||||
return &receiveOnlyFolder{sr}
|
||||
}
|
||||
|
||||
@@ -321,6 +321,7 @@ func setupROFolder() (*model, *sendOnlyFolder) {
|
||||
|
||||
f := &sendOnlyFolder{
|
||||
folder: folder{
|
||||
stateTracker: newStateTracker(fcfg.ID, m.evLogger),
|
||||
fset: m.folderFiles[fcfg.ID],
|
||||
FolderConfiguration: fcfg,
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ package model
|
||||
import (
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
@@ -24,9 +25,9 @@ type sendOnlyFolder struct {
|
||||
folder
|
||||
}
|
||||
|
||||
func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service {
|
||||
func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem, evLogger events.Logger) service {
|
||||
f := &sendOnlyFolder{
|
||||
folder: newFolder(model, fset, ignores, cfg),
|
||||
folder: newFolder(model, fset, ignores, cfg, evLogger),
|
||||
}
|
||||
f.folder.puller = f
|
||||
f.folder.Service = util.AsService(f.serve)
|
||||
@@ -74,7 +75,7 @@ func (f *sendOnlyFolder) pull() bool {
|
||||
}
|
||||
|
||||
file := intf.(protocol.FileInfo)
|
||||
if !file.IsEquivalentOptional(curFile, f.IgnorePerms, false, 0) {
|
||||
if !file.IsEquivalentOptional(curFile, f.ModTimeWindow(), f.IgnorePerms, false, 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +108,9 @@ type sendReceiveFolder struct {
|
||||
pullErrorsMut sync.Mutex
|
||||
}
|
||||
|
||||
func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
|
||||
func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger) service {
|
||||
f := &sendReceiveFolder{
|
||||
folder: newFolder(model, fset, ignores, cfg),
|
||||
folder: newFolder(model, fset, ignores, cfg, evLogger),
|
||||
fs: fs,
|
||||
versioner: ver,
|
||||
queue: newJobQueue(),
|
||||
@@ -211,7 +211,7 @@ func (f *sendReceiveFolder) pull() bool {
|
||||
// errors preventing us. Flag this with a warning and
|
||||
// wait a bit longer before retrying.
|
||||
if errors := f.Errors(); len(errors) > 0 {
|
||||
events.Default.Log(events.FolderErrors, map[string]interface{}{
|
||||
f.evLogger.Log(events.FolderErrors, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"errors": errors,
|
||||
})
|
||||
@@ -544,7 +544,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
|
||||
f.resetPullError(file.Name)
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"type": "dir",
|
||||
@@ -552,7 +552,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
})
|
||||
|
||||
defer func() {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -594,9 +594,9 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
// Symlinks aren't checked for conflicts.
|
||||
|
||||
file.Version = file.Version.Merge(curFile.Version)
|
||||
err = osutil.InWritableDir(func(name string) error {
|
||||
err = f.inWritableDir(func(name string) error {
|
||||
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
|
||||
}, f.fs, curFile.Name)
|
||||
}, curFile.Name)
|
||||
} else {
|
||||
err = f.deleteItemOnDisk(curFile, scanChan)
|
||||
}
|
||||
@@ -633,7 +633,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
return f.fs.Chmod(path, mode|(info.Mode()&retainBits))
|
||||
}
|
||||
|
||||
if err = osutil.InWritableDir(mkdir, f.fs, file.Name); err == nil {
|
||||
if err = f.inWritableDir(mkdir, file.Name); err == nil {
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
} else {
|
||||
f.newPullError(file.Name, errors.Wrap(err, "creating directory"))
|
||||
@@ -700,7 +700,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
|
||||
|
||||
f.resetPullError(file.Name)
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"type": "symlink",
|
||||
@@ -708,7 +708,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
|
||||
})
|
||||
|
||||
defer func() {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -748,9 +748,9 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
|
||||
// Directories and symlinks aren't checked for conflicts.
|
||||
|
||||
file.Version = file.Version.Merge(curFile.Version)
|
||||
err = osutil.InWritableDir(func(name string) error {
|
||||
err = f.inWritableDir(func(name string) error {
|
||||
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
|
||||
}, f.fs, curFile.Name)
|
||||
}, curFile.Name)
|
||||
} else {
|
||||
err = f.deleteItemOnDisk(curFile, scanChan)
|
||||
}
|
||||
@@ -769,7 +769,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
|
||||
return f.maybeCopyOwner(path)
|
||||
}
|
||||
|
||||
if err = osutil.InWritableDir(createLink, f.fs, file.Name); err == nil {
|
||||
if err = f.inWritableDir(createLink, file.Name); err == nil {
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleSymlink}
|
||||
} else {
|
||||
f.newPullError(file.Name, errors.Wrap(err, "symlink create"))
|
||||
@@ -782,7 +782,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
// care not declare another err.
|
||||
var err error
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"type": "dir",
|
||||
@@ -790,7 +790,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
})
|
||||
|
||||
defer func() {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -822,7 +822,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
|
||||
|
||||
f.resetPullError(file.Name)
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"type": "file",
|
||||
@@ -833,7 +833,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
|
||||
if err != nil {
|
||||
f.newPullError(file.Name, errors.Wrap(err, "delete file"))
|
||||
}
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -869,9 +869,9 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
|
||||
}
|
||||
|
||||
if f.versioner != nil && !cur.IsSymlink() {
|
||||
err = osutil.InWritableDir(f.versioner.Archive, f.fs, file.Name)
|
||||
err = f.inWritableDir(f.versioner.Archive, file.Name)
|
||||
} else {
|
||||
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
|
||||
err = f.inWritableDir(f.fs.Remove, file.Name)
|
||||
}
|
||||
|
||||
if err == nil || fs.IsNotExist(err) {
|
||||
@@ -897,13 +897,13 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
|
||||
// care not declare another err.
|
||||
var err error
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": source.Name,
|
||||
"type": "file",
|
||||
"action": "delete",
|
||||
})
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": target.Name,
|
||||
"type": "file",
|
||||
@@ -911,14 +911,14 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
|
||||
})
|
||||
|
||||
defer func() {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": source.Name,
|
||||
"error": events.Error(err),
|
||||
"type": "file",
|
||||
"action": "delete",
|
||||
})
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": target.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -953,7 +953,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
|
||||
default:
|
||||
var fi protocol.FileInfo
|
||||
if fi, err = scanner.CreateFileInfo(stat, target.Name, f.fs); err == nil {
|
||||
if !fi.IsEquivalentOptional(curTarget, f.IgnorePerms, true, protocol.LocalAllFlags) {
|
||||
if !fi.IsEquivalentOptional(curTarget, f.ModTimeWindow(), f.IgnorePerms, true, protocol.LocalAllFlags) {
|
||||
// Target changed
|
||||
scanChan <- target.Name
|
||||
err = errModified
|
||||
@@ -971,7 +971,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
|
||||
if err == nil {
|
||||
err = osutil.Copy(f.fs, f.fs, source.Name, tempName)
|
||||
if err == nil {
|
||||
err = osutil.InWritableDir(f.versioner.Archive, f.fs, source.Name)
|
||||
err = f.inWritableDir(f.versioner.Archive, source.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1045,7 +1045,6 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
populateOffsets(file.Blocks)
|
||||
|
||||
blocks := make([]protocol.BlockInfo, 0, len(file.Blocks))
|
||||
var blocksSize int64
|
||||
reused := make([]int32, 0, len(file.Blocks))
|
||||
|
||||
// Check for an old temporary file which might have some blocks we could
|
||||
@@ -1066,7 +1065,6 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
_, ok := existingBlocks[block.String()]
|
||||
if !ok {
|
||||
blocks = append(blocks, block)
|
||||
blocksSize += int64(block.Size)
|
||||
} else {
|
||||
reused = append(reused, int32(i))
|
||||
}
|
||||
@@ -1078,24 +1076,17 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
// Otherwise, discard the file ourselves in order for the
|
||||
// sharedpuller not to panic when it fails to exclusively create a
|
||||
// file which already exists
|
||||
osutil.InWritableDir(f.fs.Remove, f.fs, tempName)
|
||||
f.inWritableDir(f.fs.Remove, tempName)
|
||||
}
|
||||
} else {
|
||||
// Copy the blocks, as we don't want to shuffle them on the FileInfo
|
||||
blocks = append(blocks, file.Blocks...)
|
||||
blocksSize = file.Size
|
||||
}
|
||||
|
||||
if err := f.CheckAvailableSpace(blocksSize); err != nil {
|
||||
f.newPullError(file.Name, err)
|
||||
f.queue.Done(file.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// Shuffle the blocks
|
||||
rand.Shuffle(blocks)
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"type": "file",
|
||||
@@ -1178,7 +1169,7 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
|
||||
|
||||
f.resetPullError(file.Name)
|
||||
|
||||
events.Default.Log(events.ItemStarted, map[string]string{
|
||||
f.evLogger.Log(events.ItemStarted, map[string]string{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"type": "file",
|
||||
@@ -1186,7 +1177,7 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
|
||||
})
|
||||
|
||||
var err error
|
||||
defer events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
defer f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": file.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -1221,6 +1212,13 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
||||
}()
|
||||
|
||||
for state := range in {
|
||||
if err := f.CheckAvailableSpace(state.file.Size); err != nil {
|
||||
state.fail(err)
|
||||
// Nothing more to do for this failed file, since it would use to much disk space
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
}
|
||||
|
||||
dstFd, err := state.tempFile()
|
||||
if err != nil {
|
||||
// Nothing more to do for this failed file, since we couldn't create a temporary for it.
|
||||
@@ -1522,9 +1520,9 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu
|
||||
// Directories and symlinks aren't checked for conflicts.
|
||||
|
||||
file.Version = file.Version.Merge(curFile.Version)
|
||||
err = osutil.InWritableDir(func(name string) error {
|
||||
err = f.inWritableDir(func(name string) error {
|
||||
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
|
||||
}, f.fs, curFile.Name)
|
||||
}, curFile.Name)
|
||||
} else {
|
||||
err = f.deleteItemOnDisk(curFile, scanChan)
|
||||
}
|
||||
@@ -1575,7 +1573,7 @@ func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpda
|
||||
|
||||
f.model.progressEmitter.Deregister(state)
|
||||
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"item": state.file.Name,
|
||||
"error": events.Error(err),
|
||||
@@ -1825,10 +1823,10 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan ch
|
||||
// an error.
|
||||
// Symlinks aren't archived.
|
||||
|
||||
return osutil.InWritableDir(f.versioner.Archive, f.fs, item.Name)
|
||||
return f.inWritableDir(f.versioner.Archive, item.Name)
|
||||
}
|
||||
|
||||
return osutil.InWritableDir(f.fs.Remove, f.fs, item.Name)
|
||||
return f.inWritableDir(f.fs.Remove, item.Name)
|
||||
}
|
||||
|
||||
// deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside
|
||||
@@ -1879,7 +1877,7 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, scanChan chan<- string)
|
||||
f.fs.RemoveAll(del)
|
||||
}
|
||||
|
||||
err := osutil.InWritableDir(f.fs.Remove, f.fs, dir)
|
||||
err := f.inWritableDir(f.fs.Remove, dir)
|
||||
if err == nil || fs.IsNotExist(err) {
|
||||
// It was removed or it doesn't exist to start with
|
||||
return nil
|
||||
@@ -1919,7 +1917,7 @@ func (f *sendReceiveFolder) scanIfItemChanged(stat fs.FileInfo, item protocol.Fi
|
||||
return errors.Wrap(err, "comparing item on disk to db")
|
||||
}
|
||||
|
||||
if !statItem.IsEquivalentOptional(item, f.IgnorePerms, true, protocol.LocalAllFlags) {
|
||||
if !statItem.IsEquivalentOptional(item, f.ModTimeWindow(), f.IgnorePerms, true, protocol.LocalAllFlags) {
|
||||
return errModified
|
||||
}
|
||||
|
||||
@@ -1963,6 +1961,10 @@ func (f *sendReceiveFolder) maybeCopyOwner(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) inWritableDir(fn func(string) error, path string) error {
|
||||
return inWritableDir(fn, f.fs, path, f.IgnorePerms)
|
||||
}
|
||||
|
||||
// A []FileError is sent as part of an event and will be JSON serialized.
|
||||
type FileError struct {
|
||||
Path string `json:"path"`
|
||||
|
||||
@@ -96,7 +96,7 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
|
||||
|
||||
f := &sendReceiveFolder{
|
||||
folder: folder{
|
||||
stateTracker: newStateTracker("default"),
|
||||
stateTracker: newStateTracker("default", model.evLogger),
|
||||
model: model,
|
||||
fset: model.folderFiles[fcfg.ID],
|
||||
initialScanFinished: make(chan struct{}),
|
||||
@@ -121,6 +121,12 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
|
||||
return model, f
|
||||
}
|
||||
|
||||
func cleanupSRFolder(f *sendReceiveFolder, m *model) {
|
||||
m.evLogger.Stop()
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}
|
||||
|
||||
// Layout of the files: (indexes from the above array)
|
||||
// 12345678 - Required file
|
||||
// 02005008 - Existing file (currently in the index)
|
||||
@@ -137,10 +143,7 @@ func TestHandleFile(t *testing.T) {
|
||||
requiredFile.Blocks = blocks[1:]
|
||||
|
||||
m, f := setupSendReceiveFolder(existingFile)
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}()
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
copyChan := make(chan copyBlocksState, 1)
|
||||
dbUpdateChan := make(chan dbUpdateJob, 1)
|
||||
@@ -183,10 +186,7 @@ func TestHandleFileWithTemp(t *testing.T) {
|
||||
requiredFile.Blocks = blocks[1:]
|
||||
|
||||
m, f := setupSendReceiveFolder(existingFile)
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}()
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -236,10 +236,7 @@ func TestCopierFinder(t *testing.T) {
|
||||
requiredFile.Name = "file2"
|
||||
|
||||
m, f := setupSendReceiveFolder(existingFile)
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}()
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -257,6 +254,7 @@ func TestCopierFinder(t *testing.T) {
|
||||
|
||||
pulls := []pullBlockState{<-pullChan, <-pullChan, <-pullChan, <-pullChan}
|
||||
finish := <-finisherChan
|
||||
defer cleanupSharedPullerState(finish)
|
||||
|
||||
select {
|
||||
case <-pullChan:
|
||||
@@ -296,17 +294,13 @@ func TestCopierFinder(t *testing.T) {
|
||||
t.Errorf("Block %d mismatch: %s != %s", eq, blks[eq-1].String(), blocks[eq].String())
|
||||
}
|
||||
}
|
||||
finish.fd.Close()
|
||||
}
|
||||
|
||||
func TestWeakHash(t *testing.T) {
|
||||
// Setup the model/pull environment
|
||||
model, fo := setupSendReceiveFolder()
|
||||
defer cleanupSRFolder(fo, model)
|
||||
ffs := fo.Filesystem()
|
||||
defer func() {
|
||||
os.Remove(model.cfg.ConfigPath())
|
||||
os.Remove(ffs.URI())
|
||||
}()
|
||||
|
||||
tempFile := fs.TempName("weakhash")
|
||||
var shift int64 = 10
|
||||
@@ -395,7 +389,7 @@ func TestWeakHash(t *testing.T) {
|
||||
default:
|
||||
}
|
||||
|
||||
finish.fd.Close()
|
||||
cleanupSharedPullerState(finish)
|
||||
if err := ffs.Remove(tempFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -415,7 +409,7 @@ func TestWeakHash(t *testing.T) {
|
||||
}
|
||||
|
||||
finish = <-finisherChan
|
||||
finish.fd.Close()
|
||||
cleanupSharedPullerState(finish)
|
||||
|
||||
expectShifted := expectBlocks - expectPulls
|
||||
if finish.copyOriginShifted != expectShifted {
|
||||
@@ -432,10 +426,7 @@ func TestCopierCleanup(t *testing.T) {
|
||||
// Create a file
|
||||
file := setupFile("test", []int{0})
|
||||
m, f := setupSendReceiveFolder(file)
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}()
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
file.Blocks = []protocol.BlockInfo{blocks[1]}
|
||||
file.Version = file.Version.Update(myID.Short())
|
||||
@@ -468,13 +459,10 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
|
||||
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}()
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
// Set up our evet subscription early
|
||||
s := events.Default.Subscribe(events.ItemFinished)
|
||||
s := m.evLogger.Subscribe(events.ItemFinished)
|
||||
|
||||
// queue.Done should be called by the finisher routine
|
||||
f.queue.Push("filex", 0, time.Time{})
|
||||
@@ -528,9 +516,9 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
t.Log("event took", time.Since(t0))
|
||||
|
||||
state.mut.Lock()
|
||||
stateFd := state.fd
|
||||
stateWriter := state.writer
|
||||
state.mut.Unlock()
|
||||
if stateFd != nil {
|
||||
if stateWriter != nil {
|
||||
t.Fatal("File not closed?")
|
||||
}
|
||||
|
||||
@@ -558,13 +546,10 @@ func TestDeregisterOnFailInPull(t *testing.T) {
|
||||
file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
|
||||
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(f.Filesystem().URI())
|
||||
}()
|
||||
defer cleanupSRFolder(f, m)
|
||||
|
||||
// Set up our evet subscription early
|
||||
s := events.Default.Subscribe(events.ItemFinished)
|
||||
s := m.evLogger.Subscribe(events.ItemFinished)
|
||||
|
||||
// queue.Done should be called by the finisher routine
|
||||
f.queue.Push("filex", 0, time.Time{})
|
||||
@@ -609,9 +594,9 @@ func TestDeregisterOnFailInPull(t *testing.T) {
|
||||
t.Log("event took", time.Since(t0))
|
||||
|
||||
state.mut.Lock()
|
||||
stateFd := state.fd
|
||||
stateWriter := state.writer
|
||||
state.mut.Unlock()
|
||||
if stateFd != nil {
|
||||
if stateWriter != nil {
|
||||
t.Fatal("File not closed?")
|
||||
}
|
||||
|
||||
@@ -636,12 +621,9 @@ func TestDeregisterOnFailInPull(t *testing.T) {
|
||||
|
||||
func TestIssue3164(t *testing.T) {
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer cleanupSRFolder(f, m)
|
||||
ffs := f.Filesystem()
|
||||
tmpDir := ffs.URI()
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(tmpDir)
|
||||
}()
|
||||
|
||||
ignDir := filepath.Join("issue3164", "oktodelete")
|
||||
subDir := filepath.Join(ignDir, "foobar")
|
||||
@@ -728,11 +710,8 @@ func TestDiffEmpty(t *testing.T) {
|
||||
// in the db.
|
||||
func TestDeleteIgnorePerms(t *testing.T) {
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer cleanupSRFolder(f, m)
|
||||
ffs := f.Filesystem()
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(ffs.URI())
|
||||
}()
|
||||
f.IgnorePerms = true
|
||||
|
||||
name := "deleteIgnorePerms"
|
||||
@@ -778,7 +757,7 @@ func TestCopyOwner(t *testing.T) {
|
||||
// filesystem.
|
||||
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer os.Remove(m.cfg.ConfigPath())
|
||||
defer cleanupSRFolder(f, m)
|
||||
f.folder.FolderConfiguration = config.NewFolderConfiguration(m.id, f.ID, f.Label, fs.FilesystemTypeFake, "/TestCopyOwner")
|
||||
f.folder.FolderConfiguration.CopyOwnershipFromParent = true
|
||||
|
||||
@@ -867,11 +846,8 @@ func TestCopyOwner(t *testing.T) {
|
||||
// is replaced with a directory and versions are conflicting
|
||||
func TestSRConflictReplaceFileByDir(t *testing.T) {
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer cleanupSRFolder(f, m)
|
||||
ffs := f.Filesystem()
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(ffs.URI())
|
||||
}()
|
||||
|
||||
name := "foo"
|
||||
|
||||
@@ -902,11 +878,8 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
|
||||
// is replaced with a link and versions are conflicting
|
||||
func TestSRConflictReplaceFileByLink(t *testing.T) {
|
||||
m, f := setupSendReceiveFolder()
|
||||
defer cleanupSRFolder(f, m)
|
||||
ffs := f.Filesystem()
|
||||
defer func() {
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
os.Remove(ffs.URI())
|
||||
}()
|
||||
|
||||
name := "foo"
|
||||
|
||||
@@ -933,3 +906,14 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
|
||||
t.Fatal("Expected request to scan", confls[0], "got", scan)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupSharedPullerState(s *sharedPullerState) {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
if s.writer == nil {
|
||||
return
|
||||
}
|
||||
s.writer.mut.Lock()
|
||||
s.writer.fd.Close()
|
||||
s.writer.mut.Unlock()
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type folderSummaryService struct {
|
||||
cfg config.Wrapper
|
||||
model Model
|
||||
id protocol.DeviceID
|
||||
evLogger events.Logger
|
||||
immediate chan string
|
||||
|
||||
// For keeping track of folders to recalculate for
|
||||
@@ -47,7 +48,7 @@ type folderSummaryService struct {
|
||||
lastEventReqMut sync.Mutex
|
||||
}
|
||||
|
||||
func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID) FolderSummaryService {
|
||||
func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID, evLogger events.Logger) FolderSummaryService {
|
||||
service := &folderSummaryService{
|
||||
Supervisor: suture.New("folderSummaryService", suture.Spec{
|
||||
PassThroughPanics: true,
|
||||
@@ -55,6 +56,7 @@ func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID)
|
||||
cfg: cfg,
|
||||
model: m,
|
||||
id: id,
|
||||
evLogger: evLogger,
|
||||
immediate: make(chan string),
|
||||
folders: make(map[string]struct{}),
|
||||
foldersMut: sync.NewMutex(),
|
||||
@@ -144,8 +146,8 @@ func (c *folderSummaryService) OnEventRequest() {
|
||||
// listenForUpdates subscribes to the event bus and makes note of folders that
|
||||
// need their data recalculated.
|
||||
func (c *folderSummaryService) listenForUpdates(stop chan struct{}) {
|
||||
sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
sub := c.evLogger.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
// This loop needs to be fast so we don't miss too many events.
|
||||
@@ -291,7 +293,7 @@ func (c *folderSummaryService) sendSummary(folder string) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
events.Default.Log(events.FolderSummary, map[string]interface{}{
|
||||
c.evLogger.Log(events.FolderSummary, map[string]interface{}{
|
||||
"folder": folder,
|
||||
"summary": data,
|
||||
})
|
||||
@@ -311,6 +313,6 @@ func (c *folderSummaryService) sendSummary(folder string) {
|
||||
comp := c.model.Completion(devCfg.DeviceID, folder).Map()
|
||||
comp["folder"] = folder
|
||||
comp["device"] = devCfg.DeviceID.String()
|
||||
events.Default.Log(events.FolderCompletion, comp)
|
||||
c.evLogger.Log(events.FolderCompletion, comp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ func (s folderState) String() string {
|
||||
|
||||
type stateTracker struct {
|
||||
folderID string
|
||||
evLogger events.Logger
|
||||
|
||||
mut sync.Mutex
|
||||
current folderState
|
||||
@@ -49,9 +50,10 @@ type stateTracker struct {
|
||||
changed time.Time
|
||||
}
|
||||
|
||||
func newStateTracker(id string) stateTracker {
|
||||
func newStateTracker(id string, evLogger events.Logger) stateTracker {
|
||||
return stateTracker{
|
||||
folderID: id,
|
||||
evLogger: evLogger,
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
}
|
||||
@@ -83,7 +85,7 @@ func (s *stateTracker) setState(newState folderState) {
|
||||
s.current = newState
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
s.evLogger.Log(events.StateChanged, eventData)
|
||||
}
|
||||
s.mut.Unlock()
|
||||
}
|
||||
@@ -124,5 +126,5 @@ func (s *stateTracker) setError(err error) {
|
||||
s.err = err
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
s.evLogger.Log(events.StateChanged, eventData)
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ type model struct {
|
||||
shortID protocol.ShortID
|
||||
cacheIgnoredFiles bool
|
||||
protectedFiles []string
|
||||
evLogger events.Logger
|
||||
|
||||
clientName string
|
||||
clientVersion string
|
||||
@@ -152,7 +153,7 @@ type model struct {
|
||||
foldersRunning int32 // for testing only
|
||||
}
|
||||
|
||||
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service
|
||||
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem, events.Logger) service
|
||||
|
||||
var (
|
||||
folderFactories = make(map[config.FolderType]folderFactory)
|
||||
@@ -175,7 +176,7 @@ var (
|
||||
// NewModel creates and starts a new model. The model starts in read-only mode,
|
||||
// where it sends index information to connected peers and responds to requests
|
||||
// for file data without altering the local folder in any way.
|
||||
func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) Model {
|
||||
func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger) Model {
|
||||
m := &model{
|
||||
Supervisor: suture.New("model", suture.Spec{
|
||||
Log: func(line string) {
|
||||
@@ -186,11 +187,12 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
|
||||
cfg: cfg,
|
||||
db: ldb,
|
||||
finder: db.NewBlockFinder(ldb),
|
||||
progressEmitter: NewProgressEmitter(cfg),
|
||||
progressEmitter: NewProgressEmitter(cfg, evLogger),
|
||||
id: id,
|
||||
shortID: id.Short(),
|
||||
cacheIgnoredFiles: cfg.Options().CacheIgnoredFiles,
|
||||
protectedFiles: protectedFiles,
|
||||
evLogger: evLogger,
|
||||
clientName: clientName,
|
||||
clientVersion: clientVersion,
|
||||
folderCfgs: make(map[string]config.FolderConfiguration),
|
||||
@@ -310,7 +312,7 @@ func (m *model) startFolderLocked(cfg config.FolderConfiguration) {
|
||||
ffs.Hide(".stversions")
|
||||
ffs.Hide(".stignore")
|
||||
|
||||
p := folderFactory(m, fset, m.folderIgnores[folder], cfg, ver, ffs)
|
||||
p := folderFactory(m, fset, m.folderIgnores[folder], cfg, ver, ffs, m.evLogger)
|
||||
|
||||
m.folderRunners[folder] = p
|
||||
|
||||
@@ -597,10 +599,8 @@ func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// ConnectionStats returns a map with connection statistics for each device.
|
||||
func (m *model) ConnectionStats() map[string]interface{} {
|
||||
m.fmut.RLock()
|
||||
m.pmut.RLock()
|
||||
defer m.pmut.RUnlock()
|
||||
defer m.fmut.RUnlock()
|
||||
|
||||
res := make(map[string]interface{})
|
||||
devs := m.cfg.Devices()
|
||||
@@ -767,8 +767,9 @@ func addSizeOfFile(s *db.Counts, f db.FileIntf) {
|
||||
// files in the global model.
|
||||
func (m *model) GlobalSize(folder string) db.Counts {
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
rf, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
if ok {
|
||||
return rf.GlobalSize()
|
||||
}
|
||||
return db.Counts{}
|
||||
@@ -778,8 +779,9 @@ func (m *model) GlobalSize(folder string) db.Counts {
|
||||
// files in the local folder.
|
||||
func (m *model) LocalSize(folder string) db.Counts {
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
rf, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
if ok {
|
||||
return rf.LocalSize()
|
||||
}
|
||||
return db.Counts{}
|
||||
@@ -790,8 +792,9 @@ func (m *model) LocalSize(folder string) db.Counts {
|
||||
// folder.
|
||||
func (m *model) ReceiveOnlyChangedSize(folder string) db.Counts {
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
rf, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
if ok {
|
||||
return rf.ReceiveOnlyChangedSize()
|
||||
}
|
||||
return db.Counts{}
|
||||
@@ -1008,8 +1011,9 @@ func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
|
||||
}
|
||||
|
||||
m.pmut.RLock()
|
||||
m.deviceDownloads[deviceID].Update(folder, makeForgetUpdate(fs))
|
||||
downloads := m.deviceDownloads[deviceID]
|
||||
m.pmut.RUnlock()
|
||||
downloads.Update(folder, makeForgetUpdate(fs))
|
||||
|
||||
if !update {
|
||||
files.Drop(deviceID)
|
||||
@@ -1021,7 +1025,7 @@ func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
|
||||
}
|
||||
files.Update(deviceID, fs)
|
||||
|
||||
events.Default.Log(events.RemoteIndexUpdated, map[string]interface{}{
|
||||
m.evLogger.Log(events.RemoteIndexUpdated, map[string]interface{}{
|
||||
"device": deviceID.String(),
|
||||
"folder": folder,
|
||||
"items": len(fs),
|
||||
@@ -1064,8 +1068,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
}
|
||||
}
|
||||
|
||||
m.fmut.Lock()
|
||||
defer m.fmut.Unlock()
|
||||
m.fmut.RLock()
|
||||
var paused []string
|
||||
for _, folder := range cm.Folders {
|
||||
cfg, ok := m.cfg.Folder(folder.ID)
|
||||
@@ -1075,7 +1078,8 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
continue
|
||||
}
|
||||
m.cfg.AddOrUpdatePendingFolder(folder.ID, folder.Label, deviceID)
|
||||
events.Default.Log(events.FolderRejected, map[string]string{
|
||||
changed = true
|
||||
m.evLogger.Log(events.FolderRejected, map[string]string{
|
||||
"folder": folder.ID,
|
||||
"folderLabel": folder.Label,
|
||||
"device": deviceID.String(),
|
||||
@@ -1178,6 +1182,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
fset: fs,
|
||||
prevSequence: startSequence,
|
||||
dropSymlinks: dropSymlinks,
|
||||
evLogger: m.evLogger,
|
||||
}
|
||||
is.Service = util.AsService(is.serve)
|
||||
// The token isn't tracked as the service stops when the connection
|
||||
@@ -1185,6 +1190,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
// implementing suture.IsCompletable).
|
||||
m.Add(is)
|
||||
}
|
||||
m.fmut.RUnlock()
|
||||
|
||||
m.pmut.Lock()
|
||||
m.remotePausedFolders[deviceID] = paused
|
||||
@@ -1414,12 +1420,11 @@ func (m *model) Closed(conn protocol.Connection, err error) {
|
||||
device := conn.ID()
|
||||
|
||||
m.pmut.Lock()
|
||||
defer m.pmut.Unlock()
|
||||
conn, ok := m.conn[device]
|
||||
if !ok {
|
||||
m.pmut.Unlock()
|
||||
return
|
||||
}
|
||||
m.progressEmitter.temporaryIndexUnsubscribe(conn)
|
||||
delete(m.conn, device)
|
||||
delete(m.connRequestLimiters, device)
|
||||
delete(m.helloMessages, device)
|
||||
@@ -1427,9 +1432,12 @@ func (m *model) Closed(conn protocol.Connection, err error) {
|
||||
delete(m.remotePausedFolders, device)
|
||||
closed := m.closed[device]
|
||||
delete(m.closed, device)
|
||||
m.pmut.Unlock()
|
||||
|
||||
m.progressEmitter.temporaryIndexUnsubscribe(conn)
|
||||
|
||||
l.Infof("Connection to %s at %s closed: %v", device, conn.Name(), err)
|
||||
events.Default.Log(events.DeviceDisconnected, map[string]string{
|
||||
m.evLogger.Log(events.DeviceDisconnected, map[string]string{
|
||||
"id": device.String(),
|
||||
"error": err.Error(),
|
||||
})
|
||||
@@ -1653,9 +1661,9 @@ func (m *model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem,
|
||||
// to what we have in the database, yet the content we've read off the filesystem doesn't
|
||||
// Something is fishy, invalidate the file and rescan it.
|
||||
// The file will temporarily become invalid, which is ok as the content is messed up.
|
||||
m.fmut.Lock()
|
||||
m.fmut.RLock()
|
||||
runner, ok := m.folderRunners[folder]
|
||||
m.fmut.Unlock()
|
||||
m.fmut.RUnlock()
|
||||
if !ok {
|
||||
l.Debugf("%v recheckFile: %s: %q / %q: Folder stopped before rescan could be scheduled", m, deviceID, folder, name)
|
||||
return
|
||||
@@ -1769,7 +1777,8 @@ func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco
|
||||
cfg, ok := m.cfg.Device(remoteID)
|
||||
if !ok {
|
||||
m.cfg.AddOrUpdatePendingDevice(remoteID, hello.DeviceName, addr.String())
|
||||
events.Default.Log(events.DeviceRejected, map[string]string{
|
||||
_ = m.cfg.Save() // best effort
|
||||
m.evLogger.Log(events.DeviceRejected, map[string]string{
|
||||
"name": hello.DeviceName,
|
||||
"device": remoteID.String(),
|
||||
"address": addr.String(),
|
||||
@@ -1855,7 +1864,7 @@ func (m *model) AddConnection(conn connections.Connection, hello protocol.HelloR
|
||||
event["addr"] = addr.String()
|
||||
}
|
||||
|
||||
events.Default.Log(events.DeviceConnected, event)
|
||||
m.evLogger.Log(events.DeviceConnected, event)
|
||||
|
||||
l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID, hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
|
||||
|
||||
@@ -1885,11 +1894,12 @@ func (m *model) DownloadProgress(device protocol.DeviceID, folder string, update
|
||||
}
|
||||
|
||||
m.pmut.RLock()
|
||||
m.deviceDownloads[device].Update(folder, updates)
|
||||
state := m.deviceDownloads[device].GetBlockCounts(folder)
|
||||
downloads := m.deviceDownloads[device]
|
||||
m.pmut.RUnlock()
|
||||
downloads.Update(folder, updates)
|
||||
state := downloads.GetBlockCounts(folder)
|
||||
|
||||
events.Default.Log(events.RemoteDownloadProgress, map[string]interface{}{
|
||||
m.evLogger.Log(events.RemoteDownloadProgress, map[string]interface{}{
|
||||
"device": device.String(),
|
||||
"folder": folder,
|
||||
"state": state,
|
||||
@@ -1921,6 +1931,7 @@ type indexSender struct {
|
||||
fset *db.FileSet
|
||||
prevSequence int64
|
||||
dropSymlinks bool
|
||||
evLogger events.Logger
|
||||
connClosed chan struct{}
|
||||
}
|
||||
|
||||
@@ -1936,8 +1947,8 @@ func (s *indexSender) serve(stop chan struct{}) {
|
||||
// Subscribe to LocalIndexUpdated (we have new information to send) and
|
||||
// DeviceDisconnected (it might be us who disconnected, so we should
|
||||
// exit).
|
||||
sub := events.Default.Subscribe(events.LocalIndexUpdated | events.DeviceDisconnected)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
sub := s.evLogger.Subscribe(events.LocalIndexUpdated | events.DeviceDisconnected)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
evChan := sub.C()
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
@@ -2227,20 +2238,24 @@ func (m *model) State(folder string) (string, time.Time, error) {
|
||||
|
||||
func (m *model) FolderErrors(folder string) ([]FileError, error) {
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if err := m.checkFolderRunningLocked(folder); err != nil {
|
||||
err := m.checkFolderRunningLocked(folder)
|
||||
runner := m.folderRunners[folder]
|
||||
m.fmut.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.folderRunners[folder].Errors(), nil
|
||||
return runner.Errors(), nil
|
||||
}
|
||||
|
||||
func (m *model) WatchError(folder string) error {
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if err := m.checkFolderRunningLocked(folder); err != nil {
|
||||
err := m.checkFolderRunningLocked(folder)
|
||||
runner := m.folderRunners[folder]
|
||||
m.fmut.RUnlock()
|
||||
if err != nil {
|
||||
return nil // If the folder isn't running, there's no error to report.
|
||||
}
|
||||
return m.folderRunners[folder].WatchError()
|
||||
return runner.WatchError()
|
||||
}
|
||||
|
||||
func (m *model) Override(folder string) {
|
||||
@@ -2522,7 +2537,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
if toCfg.Paused {
|
||||
eventType = events.FolderPaused
|
||||
}
|
||||
events.Default.Log(eventType, map[string]string{"id": toCfg.ID, "label": toCfg.Label})
|
||||
m.evLogger.Log(eventType, map[string]string{"id": toCfg.ID, "label": toCfg.Label})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2550,9 +2565,9 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
if toCfg.Paused {
|
||||
l.Infoln("Pausing", deviceID)
|
||||
m.closeConn(deviceID, errDevicePaused)
|
||||
events.Default.Log(events.DevicePaused, map[string]string{"device": deviceID.String()})
|
||||
m.evLogger.Log(events.DevicePaused, map[string]string{"device": deviceID.String()})
|
||||
} else {
|
||||
events.Default.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
|
||||
m.evLogger.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ func createTmpWrapper(cfg config.Configuration) config.Wrapper {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wrapper := config.Wrap(tmpFile.Name(), cfg)
|
||||
wrapper := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger)
|
||||
tmpFile.Close()
|
||||
return wrapper
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func TestDeviceRename(t *testing.T) {
|
||||
DeviceID: device1,
|
||||
},
|
||||
}
|
||||
cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg)
|
||||
cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg, events.NoopLogger)
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := newModel(cfg, myID, "syncthing", "dev", db, nil)
|
||||
@@ -339,7 +339,7 @@ func TestDeviceRename(t *testing.T) {
|
||||
t.Errorf("Device name got overwritten")
|
||||
}
|
||||
|
||||
cfgw, err := config.Load("testdata/tmpconfig.xml", myID)
|
||||
cfgw, err := config.Load("testdata/tmpconfig.xml", myID, events.NoopLogger)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@@ -3305,13 +3305,65 @@ func TestConnCloseOnRestart(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevicePause(t *testing.T) {
|
||||
sub := events.Default.Subscribe(events.DevicePaused)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
func TestModTimeWindow(t *testing.T) {
|
||||
w, fcfg := tmpDefaultWrapper()
|
||||
tfs := fcfg.Filesystem()
|
||||
fcfg.RawModTimeWindowS = 2
|
||||
w.SetFolder(fcfg)
|
||||
m := setupModel(w)
|
||||
defer cleanupModelAndRemoveDir(m, tfs.URI())
|
||||
|
||||
name := "foo"
|
||||
|
||||
fd, err := tfs.Create(name)
|
||||
must(t, err)
|
||||
stat, err := fd.Stat()
|
||||
must(t, err)
|
||||
modTime := stat.ModTime()
|
||||
fd.Close()
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
v := protocol.Vector{}
|
||||
v = v.Update(myID.Short())
|
||||
fi, ok := m.CurrentFolderFile("default", name)
|
||||
if !ok {
|
||||
t.Fatal("File missing")
|
||||
}
|
||||
if !fi.Version.Equal(v) {
|
||||
t.Fatalf("Got version %v, expected %v", fi.Version, v)
|
||||
}
|
||||
|
||||
err = tfs.Chtimes(name, time.Now(), modTime.Add(time.Second))
|
||||
must(t, err)
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
// No change due to window
|
||||
fi, _ = m.CurrentFolderFile("default", name)
|
||||
if !fi.Version.Equal(v) {
|
||||
t.Fatalf("Got version %v, expected %v", fi.Version, v)
|
||||
}
|
||||
|
||||
err = tfs.Chtimes(name, time.Now(), modTime.Add(2*time.Second))
|
||||
must(t, err)
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
v = v.Update(myID.Short())
|
||||
fi, _ = m.CurrentFolderFile("default", name)
|
||||
if !fi.Version.Equal(v) {
|
||||
t.Fatalf("Got version %v, expected %v", fi.Version, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevicePause(t *testing.T) {
|
||||
m, _, fcfg := setupModelWithConnection()
|
||||
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
|
||||
|
||||
sub := m.evLogger.Subscribe(events.DevicePaused)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
m.pmut.RLock()
|
||||
closed := m.closed[device1]
|
||||
m.pmut.RUnlock()
|
||||
|
||||
@@ -29,6 +29,7 @@ type ProgressEmitter struct {
|
||||
connections map[protocol.DeviceID]protocol.Connection
|
||||
foldersByConns map[protocol.DeviceID][]string
|
||||
disabled bool
|
||||
evLogger events.Logger
|
||||
mut sync.Mutex
|
||||
|
||||
timer *time.Timer
|
||||
@@ -36,13 +37,14 @@ type ProgressEmitter struct {
|
||||
|
||||
// NewProgressEmitter creates a new progress emitter which emits
|
||||
// DownloadProgress events every interval.
|
||||
func NewProgressEmitter(cfg config.Wrapper) *ProgressEmitter {
|
||||
func NewProgressEmitter(cfg config.Wrapper, evLogger events.Logger) *ProgressEmitter {
|
||||
t := &ProgressEmitter{
|
||||
registry: make(map[string]map[string]*sharedPullerState),
|
||||
timer: time.NewTimer(time.Millisecond),
|
||||
sentDownloadStates: make(map[protocol.DeviceID]*sentDownloadState),
|
||||
connections: make(map[protocol.DeviceID]protocol.Connection),
|
||||
foldersByConns: make(map[protocol.DeviceID][]string),
|
||||
evLogger: evLogger,
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
t.Service = util.AsService(t.serve)
|
||||
@@ -107,7 +109,7 @@ func (t *ProgressEmitter) sendDownloadProgressEventLocked() {
|
||||
output[folder][name] = puller.Progress()
|
||||
}
|
||||
}
|
||||
events.Default.Log(events.DownloadProgress, output)
|
||||
t.evLogger.Log(events.DownloadProgress, output)
|
||||
l.Debugf("progress emitter: emitting %#v", output)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ func caller(skip int) string {
|
||||
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
|
||||
}
|
||||
|
||||
func expectEvent(w *events.Subscription, t *testing.T, size int) {
|
||||
func expectEvent(w events.Subscription, t *testing.T, size int) {
|
||||
event, err := w.Poll(timeout)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error:", err, "at", caller(1))
|
||||
@@ -44,7 +44,7 @@ func expectEvent(w *events.Subscription, t *testing.T, size int) {
|
||||
}
|
||||
}
|
||||
|
||||
func expectTimeout(w *events.Subscription, t *testing.T) {
|
||||
func expectTimeout(w events.Subscription, t *testing.T) {
|
||||
_, err := w.Poll(timeout)
|
||||
if err != events.ErrTimeout {
|
||||
t.Fatal("Unexpected non-Timeout error:", err, "at", caller(1))
|
||||
@@ -52,7 +52,11 @@ func expectTimeout(w *events.Subscription, t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProgressEmitter(t *testing.T) {
|
||||
w := events.Default.Subscribe(events.DownloadProgress)
|
||||
evLogger := events.NewLogger()
|
||||
go evLogger.Serve()
|
||||
defer evLogger.Stop()
|
||||
|
||||
w := evLogger.Subscribe(events.DownloadProgress)
|
||||
|
||||
c := createTmpWrapper(config.Configuration{})
|
||||
defer os.Remove(c.ConfigPath())
|
||||
@@ -60,7 +64,7 @@ func TestProgressEmitter(t *testing.T) {
|
||||
ProgressUpdateIntervalS: 0,
|
||||
})
|
||||
|
||||
p := NewProgressEmitter(c)
|
||||
p := NewProgressEmitter(c, evLogger)
|
||||
go p.Serve()
|
||||
p.interval = 0
|
||||
|
||||
@@ -112,7 +116,11 @@ func TestSendDownloadProgressMessages(t *testing.T) {
|
||||
|
||||
fc := &fakeConnection{}
|
||||
|
||||
p := NewProgressEmitter(c)
|
||||
evLogger := events.NewLogger()
|
||||
go evLogger.Serve()
|
||||
defer evLogger.Stop()
|
||||
|
||||
p := NewProgressEmitter(c, evLogger)
|
||||
p.temporaryIndexSubscribe(fc, []string{"folder", "folder2"})
|
||||
p.registry["folder"] = make(map[string]*sharedPullerState)
|
||||
p.registry["folder2"] = make(map[string]*sharedPullerState)
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestRequestSimple(t *testing.T) {
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("More than one index update sent")
|
||||
t.Error("More than one index update sent")
|
||||
default:
|
||||
}
|
||||
for _, f := range fs {
|
||||
@@ -82,7 +82,7 @@ func TestSymlinkTraversalRead(t *testing.T) {
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("More than one index update sent")
|
||||
t.Error("More than one index update sent")
|
||||
default:
|
||||
}
|
||||
for _, f := range fs {
|
||||
@@ -188,7 +188,8 @@ func TestRequestCreateTmpSymlink(t *testing.T) {
|
||||
if f.IsInvalid() {
|
||||
goodIdx <- struct{}{}
|
||||
} else {
|
||||
t.Fatal("Received index with non-invalid temporary file")
|
||||
t.Error("Received index with non-invalid temporary file")
|
||||
close(goodIdx)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -349,8 +350,8 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
|
||||
sub := events.Default.Subscribe(events.FolderErrors)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
sub := m.evLogger.Subscribe(events.FolderErrors)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
fc.sendIndexUpdate()
|
||||
|
||||
@@ -366,13 +367,12 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
|
||||
expected := map[string]struct{}{ign: {}, ignExisting: {}}
|
||||
// The indexes will normally arrive in one update, but it is possible
|
||||
// that they arrive in separate ones.
|
||||
secondIndex := false
|
||||
fc.mut.Lock()
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
secondIndex = true
|
||||
for _, f := range fs {
|
||||
if _, ok := expected[f.Name]; !ok {
|
||||
t.Fatalf("Unexpected file %v was updated in index", f.Name)
|
||||
t.Errorf("Unexpected file %v was updated in index", f.Name)
|
||||
continue
|
||||
}
|
||||
if f.IsInvalid() {
|
||||
t.Errorf("File %v is still marked as invalid", f.Name)
|
||||
@@ -394,8 +394,6 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
|
||||
}
|
||||
if len(expected) == 0 {
|
||||
close(done)
|
||||
} else if secondIndex {
|
||||
t.Fatal("Didn't receive index updates for all existing files, missing", expected)
|
||||
}
|
||||
}
|
||||
// Make sure pulling doesn't interfere, as index updates are racy and
|
||||
@@ -411,7 +409,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("timed out before index was received")
|
||||
t.Fatal("timed out before receiving index updates for all existing files, missing", expected)
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
@@ -420,18 +418,22 @@ func TestIssue4841(t *testing.T) {
|
||||
m, fc, fcfg := setupModelWithConnection()
|
||||
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
|
||||
|
||||
received := make(chan protocol.FileInfo)
|
||||
received := make(chan []protocol.FileInfo)
|
||||
fc.mut.Lock()
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
fc.indexFn = func(_ string, fs []protocol.FileInfo) {
|
||||
received <- fs
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
checkReceived := func(fs []protocol.FileInfo) protocol.FileInfo {
|
||||
t.Helper()
|
||||
if len(fs) != 1 {
|
||||
t.Fatalf("Sent index with %d files, should be 1", len(fs))
|
||||
}
|
||||
if fs[0].Name != "foo" {
|
||||
t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
|
||||
}
|
||||
received <- fs[0]
|
||||
return fs[0]
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
|
||||
// Setup file from remote that was ignored locally
|
||||
folder := m.folderRunners[defaultFolderConfig.ID].(*sendReceiveFolder)
|
||||
@@ -441,14 +443,16 @@ func TestIssue4841(t *testing.T) {
|
||||
LocalFlags: protocol.FlagLocalIgnored,
|
||||
Version: protocol.Vector{}.Update(device1.Short()),
|
||||
}})
|
||||
<-received
|
||||
|
||||
checkReceived(<-received)
|
||||
|
||||
// Scan without ignore patterns with "foo" not existing locally
|
||||
if err := m.ScanFolder("default"); err != nil {
|
||||
t.Fatal("Failed scanning:", err)
|
||||
}
|
||||
|
||||
f := <-received
|
||||
f := checkReceived(<-received)
|
||||
|
||||
if expected := (protocol.Vector{}.Update(myID.Short())); !f.Version.Equal(expected) {
|
||||
t.Errorf("Got Version == %v, expected %v", f.Version, expected)
|
||||
}
|
||||
@@ -463,25 +467,29 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
|
||||
|
||||
must(t, ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777))
|
||||
|
||||
received := make(chan protocol.FileInfo)
|
||||
received := make(chan []protocol.FileInfo)
|
||||
fc.mut.Lock()
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
fc.indexFn = func(_ string, fs []protocol.FileInfo) {
|
||||
received <- fs
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
checkReceived := func(fs []protocol.FileInfo) protocol.FileInfo {
|
||||
t.Helper()
|
||||
if len(fs) != 1 {
|
||||
t.Fatalf("Sent index with %d files, should be 1", len(fs))
|
||||
}
|
||||
if fs[0].Name != "foo" {
|
||||
t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
|
||||
}
|
||||
received <- fs[0]
|
||||
return fs[0]
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
|
||||
// Scan without ignore patterns with "foo" not existing locally
|
||||
if err := m.ScanFolder("default"); err != nil {
|
||||
t.Fatal("Failed scanning:", err)
|
||||
}
|
||||
|
||||
f := <-received
|
||||
f := checkReceived(<-received)
|
||||
if f.Blocks[0].WeakHash != 103547413 {
|
||||
t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
|
||||
}
|
||||
@@ -506,7 +514,8 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
|
||||
}
|
||||
|
||||
select {
|
||||
case f := <-received:
|
||||
case fs := <-received:
|
||||
f := checkReceived(fs)
|
||||
if f.Blocks[0].WeakHash != 41943361 {
|
||||
t.Fatalf("unexpected weak hash: %d != 41943361", f.Blocks[0].WeakHash)
|
||||
}
|
||||
@@ -598,14 +607,24 @@ func TestRequestSymlinkWindows(t *testing.T) {
|
||||
m, fc, fcfg := setupModelWithConnection()
|
||||
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
|
||||
|
||||
done := make(chan struct{})
|
||||
received := make(chan []protocol.FileInfo)
|
||||
fc.mut.Lock()
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("More than one index update sent")
|
||||
case <-received:
|
||||
t.Error("More than one index update sent")
|
||||
default:
|
||||
}
|
||||
received <- fs
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
|
||||
fc.addFile("link", 0644, protocol.FileInfoTypeSymlink, nil)
|
||||
fc.sendIndexUpdate()
|
||||
|
||||
select {
|
||||
case fs := <-received:
|
||||
close(received)
|
||||
// expected first index
|
||||
if len(fs) != 1 {
|
||||
t.Fatalf("Expected just one file in index, got %v", fs)
|
||||
@@ -617,21 +636,12 @@ func TestRequestSymlinkWindows(t *testing.T) {
|
||||
if !f.IsInvalid() {
|
||||
t.Errorf(`File info was not marked as invalid`)
|
||||
}
|
||||
close(done)
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
|
||||
fc.addFile("link", 0644, protocol.FileInfoTypeSymlink, nil)
|
||||
fc.sendIndexUpdate()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("timed out before pull was finished")
|
||||
}
|
||||
|
||||
sub := events.Default.Subscribe(events.StateChanged | events.LocalIndexUpdated)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
sub := m.evLogger.Subscribe(events.StateChanged | events.LocalIndexUpdated)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
m.ScanFolder("default")
|
||||
|
||||
@@ -667,18 +677,15 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
|
||||
tmpDir := tfs.URI()
|
||||
defer cleanupModelAndRemoveDir(m, tfs.URI())
|
||||
|
||||
done := make(chan struct{})
|
||||
received := make(chan []protocol.FileInfo)
|
||||
fc.mut.Lock()
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("More than one index update sent")
|
||||
case <-received:
|
||||
t.Error("More than one index update sent")
|
||||
default:
|
||||
}
|
||||
if len(fs) != 2 {
|
||||
t.Fatalf("Received index with %v indexes instead of 2", len(fs))
|
||||
}
|
||||
close(done)
|
||||
received <- fs
|
||||
}
|
||||
fc.mut.Unlock()
|
||||
|
||||
@@ -694,7 +701,11 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
|
||||
}
|
||||
fc.sendIndexUpdate()
|
||||
select {
|
||||
case <-done:
|
||||
case fs := <-received:
|
||||
close(received)
|
||||
if len(fs) != 2 {
|
||||
t.Fatalf("Received index with %v indexes instead of 2", len(fs))
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("timed out")
|
||||
}
|
||||
@@ -704,12 +715,15 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
|
||||
}
|
||||
|
||||
var gotA, gotB, gotConfl bool
|
||||
done = make(chan struct{})
|
||||
bIntermediateVersion := protocol.Vector{}.Update(fc.id.Short()).Update(myID.Short())
|
||||
bFinalVersion := bIntermediateVersion.Copy().Update(fc.id.Short())
|
||||
done := make(chan struct{})
|
||||
fc.mut.Lock()
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("Received more index updates than expected")
|
||||
t.Error("Received more index updates than expected")
|
||||
return
|
||||
default:
|
||||
}
|
||||
for _, f := range fs {
|
||||
@@ -723,7 +737,16 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
|
||||
if gotB {
|
||||
t.Error("Got more than one index update for", f.Name)
|
||||
}
|
||||
gotB = true
|
||||
if f.Version.Equal(bIntermediateVersion) {
|
||||
// This index entry might be superseeded
|
||||
// by the final one or sent before it separately.
|
||||
break
|
||||
}
|
||||
if f.Version.Equal(bFinalVersion) {
|
||||
gotB = true
|
||||
break
|
||||
}
|
||||
t.Errorf("Got unexpected version %v for file %v in index update", f.Version, f.Name)
|
||||
case strings.HasPrefix(f.Name, "b.sync-conflict-"):
|
||||
if gotConfl {
|
||||
t.Error("Got more than one index update for conflicts of", f.Name)
|
||||
@@ -890,7 +913,7 @@ func TestRequestDeleteChanged(t *testing.T) {
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("More than one index update sent")
|
||||
t.Error("More than one index update sent")
|
||||
default:
|
||||
}
|
||||
close(done)
|
||||
@@ -913,7 +936,7 @@ func TestRequestDeleteChanged(t *testing.T) {
|
||||
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("More than one index update sent")
|
||||
t.Error("More than one index update sent")
|
||||
default:
|
||||
}
|
||||
close(done)
|
||||
@@ -955,8 +978,8 @@ func TestNeedFolderFiles(t *testing.T) {
|
||||
tmpDir := tfs.URI()
|
||||
defer cleanupModelAndRemoveDir(m, tmpDir)
|
||||
|
||||
sub := events.Default.Subscribe(events.RemoteIndexUpdated)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
sub := m.evLogger.Subscribe(events.RemoteIndexUpdated)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
errPreventSync := errors.New("you aren't getting any of this")
|
||||
fc.mut.Lock()
|
||||
|
||||
@@ -8,7 +8,6 @@ package model
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -35,19 +34,19 @@ type sharedPullerState struct {
|
||||
created time.Time
|
||||
|
||||
// Mutable, must be locked for access
|
||||
err error // The first error we hit
|
||||
fd fs.File // The fd of the temp file
|
||||
copyTotal int // Total number of copy actions for the whole job
|
||||
pullTotal int // Total number of pull actions for the whole job
|
||||
copyOrigin int // Number of blocks copied from the original file
|
||||
copyOriginShifted int // Number of blocks copied from the original file but shifted
|
||||
copyNeeded int // Number of copy actions still pending
|
||||
pullNeeded int // Number of block pulls still pending
|
||||
updated time.Time // Time when any of the counters above were last updated
|
||||
closed bool // True if the file has been finalClosed.
|
||||
available []int32 // Indexes of the blocks that are available in the temporary file
|
||||
availableUpdated time.Time // Time when list of available blocks was last updated
|
||||
mut sync.RWMutex // Protects the above
|
||||
err error // The first error we hit
|
||||
writer *lockedWriterAt // Wraps fd to prevent fd closing at the same time as writing
|
||||
copyTotal int // Total number of copy actions for the whole job
|
||||
pullTotal int // Total number of pull actions for the whole job
|
||||
copyOrigin int // Number of blocks copied from the original file
|
||||
copyOriginShifted int // Number of blocks copied from the original file but shifted
|
||||
copyNeeded int // Number of copy actions still pending
|
||||
pullNeeded int // Number of block pulls still pending
|
||||
updated time.Time // Time when any of the counters above were last updated
|
||||
closed bool // True if the file has been finalClosed.
|
||||
available []int32 // Indexes of the blocks that are available in the temporary file
|
||||
availableUpdated time.Time // Time when list of available blocks was last updated
|
||||
mut sync.RWMutex // Protects the above
|
||||
}
|
||||
|
||||
// A momentary state representing the progress of the puller
|
||||
@@ -63,17 +62,32 @@ type pullerProgress struct {
|
||||
BytesTotal int64 `json:"bytesTotal"`
|
||||
}
|
||||
|
||||
// A lockedWriterAt synchronizes WriteAt calls with an external mutex.
|
||||
// lockedWriterAt adds a lock to protect from closing the fd at the same time as writing.
|
||||
// WriteAt() is goroutine safe by itself, but not against for example Close().
|
||||
type lockedWriterAt struct {
|
||||
mut *sync.RWMutex
|
||||
wr io.WriterAt
|
||||
mut sync.RWMutex
|
||||
fd fs.File
|
||||
}
|
||||
|
||||
func (w lockedWriterAt) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
(*w.mut).Lock()
|
||||
defer (*w.mut).Unlock()
|
||||
return w.wr.WriteAt(p, off)
|
||||
// WriteAt itself is goroutine safe, thus just needs to acquire a read-lock to
|
||||
// prevent closing concurrently (see SyncClose).
|
||||
func (w *lockedWriterAt) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
w.mut.RLock()
|
||||
defer w.mut.RUnlock()
|
||||
return w.fd.WriteAt(p, off)
|
||||
}
|
||||
|
||||
// SyncClose ensures that no more writes are happening before going ahead and
|
||||
// syncing and closing the fd, thus needs to acquire a write-lock.
|
||||
func (w *lockedWriterAt) SyncClose() error {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
if err := w.fd.Sync(); err != nil {
|
||||
// Sync() is nice if it works but not worth failing the
|
||||
// operation over if it fails.
|
||||
l.Debugf("fsync failed: %v", err)
|
||||
}
|
||||
return w.fd.Close()
|
||||
}
|
||||
|
||||
// tempFile returns the fd for the temporary file, reusing an open fd
|
||||
@@ -88,29 +102,20 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
}
|
||||
|
||||
// If the temp file is already open, return the file descriptor
|
||||
if s.fd != nil {
|
||||
return lockedWriterAt{&s.mut, s.fd}, nil
|
||||
if s.writer != nil {
|
||||
return s.writer, nil
|
||||
}
|
||||
|
||||
// Ensure that the parent directory is writable. This is
|
||||
// osutil.InWritableDir except we need to do more stuff so we duplicate it
|
||||
// here.
|
||||
dir := filepath.Dir(s.tempName)
|
||||
if info, err := s.fs.Stat(dir); err != nil {
|
||||
s.failLocked(errors.Wrap(err, "ensuring parent dir is writeable"))
|
||||
if err := inWritableDir(s.tempFileInWritableDir, s.fs, s.tempName, s.ignorePerms); err != nil {
|
||||
s.failLocked(err)
|
||||
return nil, err
|
||||
} else if info.Mode()&0200 == 0 {
|
||||
err := s.fs.Chmod(dir, 0755)
|
||||
if !s.ignorePerms && err == nil {
|
||||
defer func() {
|
||||
err := s.fs.Chmod(dir, info.Mode()&fs.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return s.writer, nil
|
||||
}
|
||||
|
||||
// tempFileInWritableDir should only be called from tempFile.
|
||||
func (s *sharedPullerState) tempFileInWritableDir(_ string) error {
|
||||
// The permissions to use for the temporary file should be those of the
|
||||
// final file, except we need user read & write at minimum. The
|
||||
// permissions will be set to the final value later, but in the meantime
|
||||
@@ -140,14 +145,12 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
// what the umask dictates.
|
||||
|
||||
if err := s.fs.Chmod(s.tempName, mode); err != nil {
|
||||
s.failLocked(errors.Wrap(err, "setting perms on temp file"))
|
||||
return nil, err
|
||||
return errors.Wrap(err, "setting perms on temp file")
|
||||
}
|
||||
}
|
||||
fd, err := s.fs.OpenFile(s.tempName, flags, mode)
|
||||
if err != nil {
|
||||
s.failLocked(errors.Wrap(err, "opening temp file"))
|
||||
return nil, err
|
||||
return errors.Wrap(err, "opening temp file")
|
||||
}
|
||||
|
||||
// Hide the temporary file
|
||||
@@ -177,16 +180,14 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
l.Debugln("failed to remove temporary file:", remErr)
|
||||
}
|
||||
|
||||
s.failLocked(err)
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same fd will be used by all writers
|
||||
s.fd = fd
|
||||
|
||||
return lockedWriterAt{&s.mut, s.fd}, nil
|
||||
s.writer = &lockedWriterAt{sync.NewRWMutex(), fd}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fail sets the error on the puller state compose of error, and marks the
|
||||
@@ -279,18 +280,12 @@ func (s *sharedPullerState) finalClose() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if s.fd != nil {
|
||||
if err := s.fd.Sync(); err != nil {
|
||||
// Sync() is nice if it works but not worth failing the
|
||||
// operation over if it fails.
|
||||
l.Debugf("fsync %q failed: %v", s.tempName, err)
|
||||
}
|
||||
|
||||
if err := s.fd.Close(); err != nil && s.err == nil {
|
||||
if s.writer != nil {
|
||||
if err := s.writer.SyncClose(); err != nil && s.err == nil {
|
||||
// This is our error as we weren't errored before.
|
||||
s.err = err
|
||||
}
|
||||
s.fd = nil
|
||||
s.writer = nil
|
||||
}
|
||||
|
||||
s.closed = true
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
@@ -117,12 +118,16 @@ func setupModel(w config.Wrapper) *model {
|
||||
}
|
||||
|
||||
func newModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) *model {
|
||||
return NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles).(*model)
|
||||
evLogger := events.NewLogger()
|
||||
m := NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles, evLogger).(*model)
|
||||
go evLogger.Serve()
|
||||
return m
|
||||
}
|
||||
|
||||
func cleanupModel(m *model) {
|
||||
m.Stop()
|
||||
m.db.Close()
|
||||
m.evLogger.Stop()
|
||||
os.Remove(m.cfg.ConfigPath())
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
)
|
||||
|
||||
type Holdable interface {
|
||||
@@ -59,3 +63,39 @@ func (d *deadlockDetector) Watch(name string, mut sync.Locker) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// inWritableDir calls fn(path), while making sure that the directory
|
||||
// containing `path` is writable for the duration of the call.
|
||||
func inWritableDir(fn func(string) error, targetFs fs.Filesystem, path string, ignorePerms bool) error {
|
||||
dir := filepath.Dir(path)
|
||||
info, err := targetFs.Stat(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return errors.New("Not a directory: " + path)
|
||||
}
|
||||
if info.Mode()&0200 == 0 {
|
||||
// A non-writeable directory (for this user; we assume that's the
|
||||
// relevant part). Temporarily change the mode so we can delete the
|
||||
// file or directory inside it.
|
||||
if err := targetFs.Chmod(dir, 0755); err == nil {
|
||||
// Chmod succeeded, we should change the permissions back on the way
|
||||
// out. If we fail we log the error as we have irrevocably messed up
|
||||
// at this point. :( (The operation we were called to wrap has
|
||||
// succeeded or failed on its own so returning an error to the
|
||||
// caller is inappropriate.)
|
||||
defer func() {
|
||||
if err := targetFs.Chmod(dir, info.Mode()&fs.ModePerm); err != nil && !fs.IsNotExist(err) {
|
||||
logFn := l.Warnln
|
||||
if ignorePerms {
|
||||
logFn = l.Debugln
|
||||
}
|
||||
logFn("Failed to restore directory permissions after gaining write access:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return fn(path)
|
||||
}
|
||||
|
||||
195
lib/model/utils_test.go
Normal file
195
lib/model/utils_test.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright (C) 2019 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
)
|
||||
|
||||
func TestInWriteableDir(t *testing.T) {
|
||||
dir := createTmpDir()
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
|
||||
fs.Mkdir("testdata", 0700)
|
||||
fs.Mkdir("testdata/rw", 0700)
|
||||
fs.Mkdir("testdata/ro", 0500)
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := fs.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// These should succeed
|
||||
|
||||
err := inWritableDir(create, fs, "testdata/file", false)
|
||||
if err != nil {
|
||||
t.Error("testdata/file:", err)
|
||||
}
|
||||
err = inWritableDir(create, fs, "testdata/rw/foo", false)
|
||||
if err != nil {
|
||||
t.Error("testdata/rw/foo:", err)
|
||||
}
|
||||
err = inWritableDir(fs.Remove, fs, "testdata/rw/foo", false)
|
||||
if err != nil {
|
||||
t.Error("testdata/rw/foo:", err)
|
||||
}
|
||||
|
||||
err = inWritableDir(create, fs, "testdata/ro/foo", false)
|
||||
if err != nil {
|
||||
t.Error("testdata/ro/foo:", err)
|
||||
}
|
||||
err = inWritableDir(fs.Remove, fs, "testdata/ro/foo", false)
|
||||
if err != nil {
|
||||
t.Error("testdata/ro/foo:", err)
|
||||
}
|
||||
|
||||
// These should not
|
||||
|
||||
err = inWritableDir(create, fs, "testdata/nonexistent/foo", false)
|
||||
if err == nil {
|
||||
t.Error("testdata/nonexistent/foo returned nil error")
|
||||
}
|
||||
err = inWritableDir(create, fs, "testdata/file/foo", false)
|
||||
if err == nil {
|
||||
t.Error("testdata/file/foo returned nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSWindowsRemove(t *testing.T) {
|
||||
// os.Remove should remove read only things on windows
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("Tests not required")
|
||||
return
|
||||
}
|
||||
|
||||
dir := createTmpDir()
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
defer fs.Chmod("testdata/windows/ro/readonlynew", 0700)
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := fs.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
fs.Mkdir("testdata", 0700)
|
||||
|
||||
fs.Mkdir("testdata/windows", 0500)
|
||||
fs.Mkdir("testdata/windows/ro", 0500)
|
||||
create("testdata/windows/ro/readonly")
|
||||
fs.Chmod("testdata/windows/ro/readonly", 0500)
|
||||
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := inWritableDir(fs.Remove, fs, path, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %s: %s", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSWindowsRemoveAll(t *testing.T) {
|
||||
// os.RemoveAll should remove read only things on windows
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("Tests not required")
|
||||
return
|
||||
}
|
||||
|
||||
dir := createTmpDir()
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
defer fs.Chmod("testdata/windows/ro/readonlynew", 0700)
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := fs.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
fs.Mkdir("testdata", 0700)
|
||||
|
||||
fs.Mkdir("testdata/windows", 0500)
|
||||
fs.Mkdir("testdata/windows/ro", 0500)
|
||||
create("testdata/windows/ro/readonly")
|
||||
fs.Chmod("testdata/windows/ro/readonly", 0500)
|
||||
|
||||
if err := fs.RemoveAll("testdata/windows"); err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInWritableDirWindowsRename(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("Tests not required")
|
||||
return
|
||||
}
|
||||
|
||||
dir := createTmpDir()
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
defer fs.Chmod("testdata/windows/ro/readonlynew", 0700)
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := fs.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
fs.Mkdir("testdata", 0700)
|
||||
|
||||
fs.Mkdir("testdata/windows", 0500)
|
||||
fs.Mkdir("testdata/windows/ro", 0500)
|
||||
create("testdata/windows/ro/readonly")
|
||||
fs.Chmod("testdata/windows/ro/readonly", 0500)
|
||||
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := fs.Rename(path, path+"new")
|
||||
if err == nil {
|
||||
t.Skipf("seem like this test doesn't work here")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rename := func(path string) error {
|
||||
return fs.Rename(path, path+"new")
|
||||
}
|
||||
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := inWritableDir(rename, fs, path, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %s: %s", path, err)
|
||||
}
|
||||
_, err = fs.Stat(path + "new")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %s: %s", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -81,38 +80,6 @@ func Copy(src, dst fs.Filesystem, from, to string) (err error) {
|
||||
})
|
||||
}
|
||||
|
||||
// InWritableDir calls fn(path), while making sure that the directory
|
||||
// containing `path` is writable for the duration of the call.
|
||||
func InWritableDir(fn func(string) error, fs fs.Filesystem, path string) error {
|
||||
dir := filepath.Dir(path)
|
||||
info, err := fs.Stat(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return errors.New("Not a directory: " + path)
|
||||
}
|
||||
if info.Mode()&0200 == 0 {
|
||||
// A non-writeable directory (for this user; we assume that's the
|
||||
// relevant part). Temporarily change the mode so we can delete the
|
||||
// file or directory inside it.
|
||||
err = fs.Chmod(dir, 0755)
|
||||
if err == nil {
|
||||
defer func() {
|
||||
err = fs.Chmod(dir, info.Mode())
|
||||
if err != nil {
|
||||
// We managed to change the permission bits like a
|
||||
// millisecond ago, so it'd be bizarre if we couldn't
|
||||
// change it back.
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return fn(path)
|
||||
}
|
||||
|
||||
// Tries hard to succeed on various systems by temporarily tweaking directory
|
||||
// permissions and removing the destination file when necessary.
|
||||
func withPreparedTarget(filesystem fs.Filesystem, from, to string, f func() error) error {
|
||||
|
||||
@@ -18,196 +18,6 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
)
|
||||
|
||||
func TestInWriteableDir(t *testing.T) {
|
||||
err := os.RemoveAll("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll("testdata")
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, ".")
|
||||
|
||||
os.Mkdir("testdata", 0700)
|
||||
os.Mkdir("testdata/rw", 0700)
|
||||
os.Mkdir("testdata/ro", 0500)
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// These should succeed
|
||||
|
||||
err = osutil.InWritableDir(create, fs, "testdata/file")
|
||||
if err != nil {
|
||||
t.Error("testdata/file:", err)
|
||||
}
|
||||
err = osutil.InWritableDir(create, fs, "testdata/rw/foo")
|
||||
if err != nil {
|
||||
t.Error("testdata/rw/foo:", err)
|
||||
}
|
||||
err = osutil.InWritableDir(os.Remove, fs, "testdata/rw/foo")
|
||||
if err != nil {
|
||||
t.Error("testdata/rw/foo:", err)
|
||||
}
|
||||
|
||||
err = osutil.InWritableDir(create, fs, "testdata/ro/foo")
|
||||
if err != nil {
|
||||
t.Error("testdata/ro/foo:", err)
|
||||
}
|
||||
err = osutil.InWritableDir(os.Remove, fs, "testdata/ro/foo")
|
||||
if err != nil {
|
||||
t.Error("testdata/ro/foo:", err)
|
||||
}
|
||||
|
||||
// These should not
|
||||
|
||||
err = osutil.InWritableDir(create, fs, "testdata/nonexistent/foo")
|
||||
if err == nil {
|
||||
t.Error("testdata/nonexistent/foo returned nil error")
|
||||
}
|
||||
err = osutil.InWritableDir(create, fs, "testdata/file/foo")
|
||||
if err == nil {
|
||||
t.Error("testdata/file/foo returned nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInWritableDirWindowsRemove(t *testing.T) {
|
||||
// os.Remove should remove read only things on windows
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("Tests not required")
|
||||
return
|
||||
}
|
||||
|
||||
err := os.RemoveAll("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chmod("testdata/windows/ro/readonlynew", 0700)
|
||||
defer os.RemoveAll("testdata")
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
os.Mkdir("testdata", 0700)
|
||||
|
||||
os.Mkdir("testdata/windows", 0500)
|
||||
os.Mkdir("testdata/windows/ro", 0500)
|
||||
create("testdata/windows/ro/readonly")
|
||||
os.Chmod("testdata/windows/ro/readonly", 0500)
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, ".")
|
||||
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := osutil.InWritableDir(os.Remove, fs, path)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %s: %s", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInWritableDirWindowsRemoveAll(t *testing.T) {
|
||||
// os.RemoveAll should remove read only things on windows
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("Tests not required")
|
||||
return
|
||||
}
|
||||
|
||||
err := os.RemoveAll("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chmod("testdata/windows/ro/readonlynew", 0700)
|
||||
defer os.RemoveAll("testdata")
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
os.Mkdir("testdata", 0700)
|
||||
|
||||
os.Mkdir("testdata/windows", 0500)
|
||||
os.Mkdir("testdata/windows/ro", 0500)
|
||||
create("testdata/windows/ro/readonly")
|
||||
os.Chmod("testdata/windows/ro/readonly", 0500)
|
||||
|
||||
if err := os.RemoveAll("testdata/windows"); err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInWritableDirWindowsRename(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("Tests not required")
|
||||
return
|
||||
}
|
||||
|
||||
err := os.RemoveAll("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chmod("testdata/windows/ro/readonlynew", 0700)
|
||||
defer os.RemoveAll("testdata")
|
||||
|
||||
create := func(name string) error {
|
||||
fd, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
os.Mkdir("testdata", 0700)
|
||||
|
||||
os.Mkdir("testdata/windows", 0500)
|
||||
os.Mkdir("testdata/windows/ro", 0500)
|
||||
create("testdata/windows/ro/readonly")
|
||||
os.Chmod("testdata/windows/ro/readonly", 0500)
|
||||
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, ".")
|
||||
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := os.Rename(path, path+"new")
|
||||
if err == nil {
|
||||
t.Skipf("seem like this test doesn't work here")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rename := func(path string) error {
|
||||
return osutil.RenameOrCopy(fs, fs, path, path+"new")
|
||||
}
|
||||
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := osutil.InWritableDir(rename, fs, path)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %s: %s", path, err)
|
||||
}
|
||||
_, err = os.Stat(path + "new")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %s: %s", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDeleted(t *testing.T) {
|
||||
type tc struct {
|
||||
path string
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -158,12 +158,12 @@ func (f FileInfo) IsEmpty() bool {
|
||||
return f.Version.Counters == nil
|
||||
}
|
||||
|
||||
func (f FileInfo) IsEquivalent(other FileInfo) bool {
|
||||
return f.isEquivalent(other, false, false, 0)
|
||||
func (f FileInfo) IsEquivalent(other FileInfo, modTimeWindow time.Duration) bool {
|
||||
return f.isEquivalent(other, modTimeWindow, false, false, 0)
|
||||
}
|
||||
|
||||
func (f FileInfo) IsEquivalentOptional(other FileInfo, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
|
||||
return f.isEquivalent(other, ignorePerms, ignoreBlocks, ignoreFlags)
|
||||
func (f FileInfo) IsEquivalentOptional(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
|
||||
return f.isEquivalent(other, modTimeWindow, ignorePerms, ignoreBlocks, ignoreFlags)
|
||||
}
|
||||
|
||||
// isEquivalent checks that the two file infos represent the same actual file content,
|
||||
@@ -175,13 +175,13 @@ func (f FileInfo) IsEquivalentOptional(other FileInfo, ignorePerms bool, ignoreB
|
||||
// - invalid flag
|
||||
// - permissions, unless they are ignored
|
||||
// A file is not "equivalent", if it has different
|
||||
// - modification time
|
||||
// - modification time (difference bigger than modTimeWindow)
|
||||
// - size
|
||||
// - blocks, unless there are no blocks to compare (scanning)
|
||||
// A symlink is not "equivalent", if it has different
|
||||
// - target
|
||||
// A directory does not have anything specific to check.
|
||||
func (f FileInfo) isEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
|
||||
func (f FileInfo) isEquivalent(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
|
||||
if f.MustRescan() || other.MustRescan() {
|
||||
// These are per definition not equivalent because they don't
|
||||
// represent a valid state, even if both happen to have the
|
||||
@@ -203,7 +203,7 @@ func (f FileInfo) isEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bo
|
||||
|
||||
switch f.Type {
|
||||
case FileInfoTypeFile:
|
||||
return f.Size == other.Size && f.ModTime().Equal(other.ModTime()) && (ignoreBlocks || BlocksEqual(f.Blocks, other.Blocks))
|
||||
return f.Size == other.Size && ModTimeEqual(f.ModTime(), other.ModTime(), modTimeWindow) && (ignoreBlocks || BlocksEqual(f.Blocks, other.Blocks))
|
||||
case FileInfoTypeSymlink:
|
||||
return f.SymlinkTarget == other.SymlinkTarget
|
||||
case FileInfoTypeDirectory:
|
||||
@@ -213,6 +213,17 @@ func (f FileInfo) isEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bo
|
||||
return false
|
||||
}
|
||||
|
||||
func ModTimeEqual(a, b time.Time, modTimeWindow time.Duration) bool {
|
||||
if a.Equal(b) {
|
||||
return true
|
||||
}
|
||||
diff := a.Sub(b)
|
||||
if diff < 0 {
|
||||
diff *= -1
|
||||
}
|
||||
return diff < modTimeWindow
|
||||
}
|
||||
|
||||
func PermsEqual(a, b uint32) bool {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
package protocol
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
import io "io"
|
||||
import (
|
||||
fmt "fmt"
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
@@ -19,7 +21,7 @@ var _ = math.Inf
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type TestOldDeviceID struct {
|
||||
Test []byte `protobuf:"bytes,1,opt,name=test,proto3" json:"test,omitempty"`
|
||||
@@ -29,7 +31,7 @@ func (m *TestOldDeviceID) Reset() { *m = TestOldDeviceID{} }
|
||||
func (m *TestOldDeviceID) String() string { return proto.CompactTextString(m) }
|
||||
func (*TestOldDeviceID) ProtoMessage() {}
|
||||
func (*TestOldDeviceID) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_deviceid_test_36bcb89b605bdafe, []int{0}
|
||||
return fileDescriptor_a5b590761a4231d0, []int{0}
|
||||
}
|
||||
func (m *TestOldDeviceID) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -39,15 +41,15 @@ func (m *TestOldDeviceID) XXX_Marshal(b []byte, deterministic bool) ([]byte, err
|
||||
return xxx_messageInfo_TestOldDeviceID.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *TestOldDeviceID) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TestOldDeviceID.Merge(dst, src)
|
||||
func (m *TestOldDeviceID) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TestOldDeviceID.Merge(m, src)
|
||||
}
|
||||
func (m *TestOldDeviceID) XXX_Size() int {
|
||||
return m.ProtoSize()
|
||||
@@ -66,7 +68,7 @@ func (m *TestNewDeviceID) Reset() { *m = TestNewDeviceID{} }
|
||||
func (m *TestNewDeviceID) String() string { return proto.CompactTextString(m) }
|
||||
func (*TestNewDeviceID) ProtoMessage() {}
|
||||
func (*TestNewDeviceID) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_deviceid_test_36bcb89b605bdafe, []int{1}
|
||||
return fileDescriptor_a5b590761a4231d0, []int{1}
|
||||
}
|
||||
func (m *TestNewDeviceID) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -76,15 +78,15 @@ func (m *TestNewDeviceID) XXX_Marshal(b []byte, deterministic bool) ([]byte, err
|
||||
return xxx_messageInfo_TestNewDeviceID.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *TestNewDeviceID) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TestNewDeviceID.Merge(dst, src)
|
||||
func (m *TestNewDeviceID) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TestNewDeviceID.Merge(m, src)
|
||||
}
|
||||
func (m *TestNewDeviceID) XXX_Size() int {
|
||||
return m.ProtoSize()
|
||||
@@ -99,10 +101,29 @@ func init() {
|
||||
proto.RegisterType((*TestOldDeviceID)(nil), "protocol.TestOldDeviceID")
|
||||
proto.RegisterType((*TestNewDeviceID)(nil), "protocol.TestNewDeviceID")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("deviceid_test.proto", fileDescriptor_a5b590761a4231d0) }
|
||||
|
||||
var fileDescriptor_a5b590761a4231d0 = []byte{
|
||||
// 182 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x49, 0x2d, 0xcb,
|
||||
0x4c, 0x4e, 0xcd, 0x4c, 0x89, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
|
||||
0xe2, 0x00, 0x53, 0xc9, 0xf9, 0x39, 0x52, 0xca, 0x45, 0xa9, 0x05, 0xf9, 0xc5, 0xfa, 0x60, 0x7e,
|
||||
0x52, 0x69, 0x9a, 0x7e, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0x03, 0x66, 0x41, 0x94, 0x2b, 0xa9, 0x72,
|
||||
0xf1, 0x87, 0xa4, 0x16, 0x97, 0xf8, 0xe7, 0xa4, 0xb8, 0x80, 0x0d, 0xf3, 0x74, 0x11, 0x12, 0xe2,
|
||||
0x62, 0x01, 0x99, 0x27, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0x66, 0x2b, 0x99, 0x43, 0x94,
|
||||
0xf9, 0xa5, 0x96, 0xc3, 0x95, 0xa9, 0x20, 0x2b, 0x73, 0x12, 0x38, 0x71, 0x4f, 0x9e, 0xe1, 0xd6,
|
||||
0x3d, 0x79, 0x0e, 0x98, 0x3c, 0x44, 0xa3, 0x93, 0xc6, 0x89, 0x87, 0x72, 0x0c, 0x17, 0x1e, 0xca,
|
||||
0x31, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x0c, 0x2f, 0x1e,
|
||||
0xc9, 0x31, 0x4c, 0x78, 0x2c, 0xc7, 0xb0, 0xe0, 0xb1, 0x1c, 0xe3, 0x85, 0xc7, 0x72, 0x0c, 0x37,
|
||||
0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x1d, 0x64, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x96,
|
||||
0x3e, 0xc0, 0xd6, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *TestOldDeviceID) Marshal() (dAtA []byte, err error) {
|
||||
size := m.ProtoSize()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,23 +131,29 @@ func (m *TestOldDeviceID) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *TestOldDeviceID) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.ProtoSize()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *TestOldDeviceID) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Test) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i -= len(m.Test)
|
||||
copy(dAtA[i:], m.Test)
|
||||
i = encodeVarintDeviceidTest(dAtA, i, uint64(len(m.Test)))
|
||||
i += copy(dAtA[i:], m.Test)
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return i, nil
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *TestNewDeviceID) Marshal() (dAtA []byte, err error) {
|
||||
size := m.ProtoSize()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -134,29 +161,38 @@ func (m *TestNewDeviceID) Marshal() (dAtA []byte, err error) {
|
||||
}
|
||||
|
||||
func (m *TestNewDeviceID) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
size := m.ProtoSize()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *TestNewDeviceID) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintDeviceidTest(dAtA, i, uint64(m.Test.ProtoSize()))
|
||||
n1, err := m.Test.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
{
|
||||
size := m.Test.ProtoSize()
|
||||
i -= size
|
||||
if _, err := m.Test.MarshalTo(dAtA[i:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i = encodeVarintDeviceidTest(dAtA, i, uint64(size))
|
||||
}
|
||||
i += n1
|
||||
return i, nil
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintDeviceidTest(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovDeviceidTest(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
return base
|
||||
}
|
||||
func (m *TestOldDeviceID) ProtoSize() (n int) {
|
||||
if m == nil {
|
||||
@@ -183,14 +219,7 @@ func (m *TestNewDeviceID) ProtoSize() (n int) {
|
||||
}
|
||||
|
||||
func sovDeviceidTest(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozDeviceidTest(x uint64) (n int) {
|
||||
return sovDeviceidTest(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
@@ -210,7 +239,7 @@ func (m *TestOldDeviceID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -238,7 +267,7 @@ func (m *TestOldDeviceID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -247,6 +276,9 @@ func (m *TestOldDeviceID) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -264,6 +296,9 @@ func (m *TestOldDeviceID) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -291,7 +326,7 @@ func (m *TestNewDeviceID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -319,7 +354,7 @@ func (m *TestNewDeviceID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
@@ -328,6 +363,9 @@ func (m *TestNewDeviceID) Unmarshal(dAtA []byte) error {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -344,6 +382,9 @@ func (m *TestNewDeviceID) Unmarshal(dAtA []byte) error {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -410,10 +451,13 @@ func skipDeviceidTest(dAtA []byte) (n int, err error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
iNdEx += length
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
@@ -442,6 +486,9 @@ func skipDeviceidTest(dAtA []byte) (n int, err error) {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthDeviceidTest
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
@@ -460,21 +507,3 @@ var (
|
||||
ErrInvalidLengthDeviceidTest = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowDeviceidTest = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("deviceid_test.proto", fileDescriptor_deviceid_test_36bcb89b605bdafe) }
|
||||
|
||||
var fileDescriptor_deviceid_test_36bcb89b605bdafe = []byte{
|
||||
// 182 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x49, 0x2d, 0xcb,
|
||||
0x4c, 0x4e, 0xcd, 0x4c, 0x89, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
|
||||
0xe2, 0x00, 0x53, 0xc9, 0xf9, 0x39, 0x52, 0xca, 0x45, 0xa9, 0x05, 0xf9, 0xc5, 0xfa, 0x60, 0x7e,
|
||||
0x52, 0x69, 0x9a, 0x7e, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0x03, 0x66, 0x41, 0x94, 0x2b, 0xa9, 0x72,
|
||||
0xf1, 0x87, 0xa4, 0x16, 0x97, 0xf8, 0xe7, 0xa4, 0xb8, 0x80, 0x0d, 0xf3, 0x74, 0x11, 0x12, 0xe2,
|
||||
0x62, 0x01, 0x99, 0x27, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0x66, 0x2b, 0x99, 0x43, 0x94,
|
||||
0xf9, 0xa5, 0x96, 0xc3, 0x95, 0xa9, 0x20, 0x2b, 0x73, 0x12, 0x38, 0x71, 0x4f, 0x9e, 0xe1, 0xd6,
|
||||
0x3d, 0x79, 0x0e, 0x98, 0x3c, 0x44, 0xa3, 0x93, 0xc6, 0x89, 0x87, 0x72, 0x0c, 0x17, 0x1e, 0xca,
|
||||
0x31, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x0c, 0x2f, 0x1e,
|
||||
0xc9, 0x31, 0x4c, 0x78, 0x2c, 0xc7, 0xb0, 0xe0, 0xb1, 0x1c, 0xe3, 0x85, 0xc7, 0x72, 0x0c, 0x37,
|
||||
0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x1d, 0x64, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x96,
|
||||
0x3e, 0xc0, 0xd6, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -770,10 +770,10 @@ func TestIsEquivalent(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
if res := tc.a.isEquivalent(tc.b, ignPerms, ignBlocks, tc.ignFlags); res != tc.eq {
|
||||
if res := tc.a.isEquivalent(tc.b, 0, ignPerms, ignBlocks, tc.ignFlags); res != tc.eq {
|
||||
t.Errorf("Case %d:\na: %v\nb: %v\na.IsEquivalent(b, %v, %v) => %v, expected %v", i, tc.a, tc.b, ignPerms, ignBlocks, res, tc.eq)
|
||||
}
|
||||
if res := tc.b.isEquivalent(tc.a, ignPerms, ignBlocks, tc.ignFlags); res != tc.eq {
|
||||
if res := tc.b.isEquivalent(tc.a, 0, ignPerms, ignBlocks, tc.ignFlags); res != tc.eq {
|
||||
t.Errorf("Case %d:\na: %v\nb: %v\nb.IsEquivalent(a, %v, %v) => %v, expected %v", i, tc.a, tc.b, ignPerms, ignBlocks, res, tc.eq)
|
||||
}
|
||||
}
|
||||
|
||||
12
lib/rc/rc.go
12
lib/rc/rc.go
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/dialer"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
@@ -154,6 +155,11 @@ func (p *Process) Stop() (*os.ProcessState, error) {
|
||||
return p.cmd.ProcessState, p.stopErr
|
||||
}
|
||||
|
||||
// Stopped returns a channel that will be closed when Syncthing has stopped.
|
||||
func (p *Process) Stopped() chan struct{} {
|
||||
return p.stopped
|
||||
}
|
||||
|
||||
// Get performs an HTTP GET and returns the bytes and/or an error. Any non-200
|
||||
// return code is returned as an error.
|
||||
func (p *Process) Get(path string) ([]byte, error) {
|
||||
@@ -455,7 +461,7 @@ func (p *Process) eventLoop() {
|
||||
default:
|
||||
}
|
||||
|
||||
events, err := p.Events(since)
|
||||
evs, err := p.Events(since)
|
||||
if err != nil {
|
||||
if time.Since(start) < 5*time.Second {
|
||||
// The API has probably not started yet, lets give it some time.
|
||||
@@ -473,7 +479,7 @@ func (p *Process) eventLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ev := range events {
|
||||
for _, ev := range evs {
|
||||
if ev.ID != since+1 {
|
||||
l.Warnln("Event ID jumped", since, "to", ev.ID)
|
||||
}
|
||||
@@ -493,7 +499,7 @@ func (p *Process) eventLoop() {
|
||||
p.id = id
|
||||
|
||||
home := data["home"].(string)
|
||||
w, err := config.Load(filepath.Join(home, "config.xml"), protocol.LocalDeviceID)
|
||||
w, err := config.Load(filepath.Join(home, "config.xml"), protocol.LocalDeviceID, events.NoopLogger)
|
||||
if err != nil {
|
||||
log.Println("eventLoop: Starting:", err)
|
||||
continue
|
||||
|
||||
@@ -54,6 +54,10 @@ type Config struct {
|
||||
ProgressTickIntervalS int
|
||||
// Local flags to set on scanned files
|
||||
LocalFlags uint32
|
||||
// Modification time is to be considered unchanged if the difference is lower.
|
||||
ModTimeWindow time.Duration
|
||||
// Event logger to which the scan progress events are sent
|
||||
EventLogger events.Logger
|
||||
}
|
||||
|
||||
type CurrentFiler interface {
|
||||
@@ -166,7 +170,7 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
|
||||
current := progress.Total()
|
||||
rate := progress.Rate()
|
||||
l.Debugf("Walk %s %s current progress %d/%d at %.01f MiB/s (%d%%)", w.Folder, w.Subs, current, total, rate/1024/1024, current*100/total)
|
||||
events.Default.Log(events.FolderScanProgress, map[string]interface{}{
|
||||
w.EventLogger.Log(events.FolderScanProgress, map[string]interface{}{
|
||||
"folder": w.Folder,
|
||||
"current": current,
|
||||
"total": total,
|
||||
@@ -346,7 +350,7 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
||||
f.RawBlockSize = int32(blockSize)
|
||||
|
||||
if hasCurFile {
|
||||
if curFile.IsEquivalentOptional(f, w.IgnorePerms, true, w.LocalFlags) {
|
||||
if curFile.IsEquivalentOptional(f, w.ModTimeWindow, w.IgnorePerms, true, w.LocalFlags) {
|
||||
return nil
|
||||
}
|
||||
if curFile.ShouldConflict() {
|
||||
@@ -379,7 +383,7 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
|
||||
f.NoPermissions = w.IgnorePerms
|
||||
|
||||
if hasCurFile {
|
||||
if curFile.IsEquivalentOptional(f, w.IgnorePerms, true, w.LocalFlags) {
|
||||
if curFile.IsEquivalentOptional(f, w.ModTimeWindow, w.IgnorePerms, true, w.LocalFlags) {
|
||||
return nil
|
||||
}
|
||||
if curFile.ShouldConflict() {
|
||||
@@ -423,7 +427,7 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, info fs.FileIn
|
||||
f = w.updateFileInfo(f, curFile)
|
||||
|
||||
if hasCurFile {
|
||||
if curFile.IsEquivalentOptional(f, w.IgnorePerms, true, w.LocalFlags) {
|
||||
if curFile.IsEquivalentOptional(f, w.ModTimeWindow, w.IgnorePerms, true, w.LocalFlags) {
|
||||
return nil
|
||||
}
|
||||
if curFile.ShouldConflict() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/d4l3k/messagediff"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
@@ -66,12 +67,10 @@ func TestWalkSub(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fchan := Walk(context.TODO(), Config{
|
||||
Filesystem: testFs,
|
||||
Subs: []string{"dir2"},
|
||||
Matcher: ignores,
|
||||
Hashers: 2,
|
||||
})
|
||||
cfg := testConfig()
|
||||
cfg.Subs = []string{"dir2"}
|
||||
cfg.Matcher = ignores
|
||||
fchan := Walk(context.TODO(), cfg)
|
||||
var files []protocol.FileInfo
|
||||
for f := range fchan {
|
||||
if f.Err != nil {
|
||||
@@ -102,11 +101,9 @@ func TestWalk(t *testing.T) {
|
||||
}
|
||||
t.Log(ignores)
|
||||
|
||||
fchan := Walk(context.TODO(), Config{
|
||||
Filesystem: testFs,
|
||||
Matcher: ignores,
|
||||
Hashers: 2,
|
||||
})
|
||||
cfg := testConfig()
|
||||
cfg.Matcher = ignores
|
||||
fchan := Walk(context.TODO(), cfg)
|
||||
|
||||
var tmp []protocol.FileInfo
|
||||
for f := range fchan {
|
||||
@@ -466,15 +463,14 @@ func TestWalkReceiveOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.Matcher, localFlags uint32) []protocol.FileInfo {
|
||||
fchan := Walk(context.TODO(), Config{
|
||||
Filesystem: fs,
|
||||
Subs: []string{dir},
|
||||
AutoNormalize: true,
|
||||
Hashers: 2,
|
||||
CurrentFiler: cfiler,
|
||||
Matcher: matcher,
|
||||
LocalFlags: localFlags,
|
||||
})
|
||||
cfg := testConfig()
|
||||
cfg.Filesystem = fs
|
||||
cfg.Subs = []string{dir}
|
||||
cfg.AutoNormalize = true
|
||||
cfg.CurrentFiler = cfiler
|
||||
cfg.Matcher = matcher
|
||||
cfg.LocalFlags = localFlags
|
||||
fchan := Walk(context.TODO(), cfg)
|
||||
|
||||
var tmp []protocol.FileInfo
|
||||
for f := range fchan {
|
||||
@@ -576,11 +572,11 @@ func TestStopWalk(t *testing.T) {
|
||||
|
||||
const numHashers = 4
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
fchan := Walk(ctx, Config{
|
||||
Filesystem: fs,
|
||||
Hashers: numHashers,
|
||||
ProgressTickIntervalS: -1, // Don't attempt to build the full list of files before starting to scan...
|
||||
})
|
||||
cfg := testConfig()
|
||||
cfg.Filesystem = fs
|
||||
cfg.Hashers = numHashers
|
||||
cfg.ProgressTickIntervalS = -1 // Don't attempt to build the full list of files before starting to scan...
|
||||
fchan := Walk(ctx, cfg)
|
||||
|
||||
// Receive a few entries to make sure the walker is up and running,
|
||||
// scanning both files and dirs. Do some quick sanity tests on the
|
||||
@@ -705,21 +701,17 @@ func TestIssue4841(t *testing.T) {
|
||||
}
|
||||
fd.Close()
|
||||
|
||||
fchan := Walk(context.TODO(), Config{
|
||||
Filesystem: fs,
|
||||
Subs: nil,
|
||||
AutoNormalize: true,
|
||||
Hashers: 2,
|
||||
CurrentFiler: fakeCurrentFiler{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
Type: protocol.FileInfoTypeFile,
|
||||
LocalFlags: protocol.FlagLocalIgnored,
|
||||
Version: protocol.Vector{}.Update(1),
|
||||
},
|
||||
},
|
||||
ShortID: protocol.LocalDeviceID.Short(),
|
||||
})
|
||||
cfg := testConfig()
|
||||
cfg.Filesystem = fs
|
||||
cfg.AutoNormalize = true
|
||||
cfg.CurrentFiler = fakeCurrentFiler{"foo": {
|
||||
Name: "foo",
|
||||
Type: protocol.FileInfoTypeFile,
|
||||
LocalFlags: protocol.FlagLocalIgnored,
|
||||
Version: protocol.Vector{}.Update(1),
|
||||
}}
|
||||
cfg.ShortID = protocol.LocalDeviceID.Short()
|
||||
fchan := Walk(context.TODO(), cfg)
|
||||
|
||||
var files []protocol.FileInfo
|
||||
for f := range fchan {
|
||||
@@ -745,11 +737,9 @@ func TestNotExistingError(t *testing.T) {
|
||||
t.Fatalf("Lstat returned error %v, while nothing should exist there.", err)
|
||||
}
|
||||
|
||||
fchan := Walk(context.TODO(), Config{
|
||||
Filesystem: testFs,
|
||||
Subs: []string{sub},
|
||||
Hashers: 2,
|
||||
})
|
||||
cfg := testConfig()
|
||||
cfg.Subs = []string{sub}
|
||||
fchan := Walk(context.TODO(), cfg)
|
||||
for f := range fchan {
|
||||
t.Fatalf("Expected no result from scan, got %v", f)
|
||||
}
|
||||
@@ -793,3 +783,13 @@ func (fcf fakeCurrentFiler) CurrentFile(name string) (protocol.FileInfo, bool) {
|
||||
f, ok := fcf[name]
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func testConfig() Config {
|
||||
evLogger := events.NewLogger()
|
||||
go evLogger.Serve()
|
||||
return Config{
|
||||
Filesystem: testFs,
|
||||
Hashers: 2,
|
||||
EventLogger: evLogger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ func (s *Service) String() string {
|
||||
}
|
||||
|
||||
func (s *Service) isCurrentNATTypePunchable() bool {
|
||||
return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull
|
||||
return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull || s.natType == NATSymmetricUDPFirewall
|
||||
}
|
||||
|
||||
func areDifferent(first, second *Host) bool {
|
||||
|
||||
@@ -21,13 +21,13 @@ import (
|
||||
type auditService struct {
|
||||
suture.Service
|
||||
w io.Writer // audit destination
|
||||
sub *events.Subscription
|
||||
sub events.Subscription
|
||||
}
|
||||
|
||||
func newAuditService(w io.Writer) *auditService {
|
||||
func newAuditService(w io.Writer, evLogger events.Logger) *auditService {
|
||||
s := &auditService{
|
||||
w: w,
|
||||
sub: events.Default.Subscribe(events.AllEvents),
|
||||
sub: evLogger.Subscribe(events.AllEvents),
|
||||
}
|
||||
s.Service = util.AsService(s.serve)
|
||||
return s
|
||||
@@ -50,5 +50,5 @@ func (s *auditService) serve(stop chan struct{}) {
|
||||
// Stop stops the audit service.
|
||||
func (s *auditService) Stop() {
|
||||
s.Service.Stop()
|
||||
events.Default.Unsubscribe(s.sub)
|
||||
s.sub.Unsubscribe()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user