Compare commits

...

43 Commits

Author SHA1 Message Date
greatroar
f6fac3e949 lib/ur: Plug file descriptor leak in Linux memorySize (#7266) 2021-01-11 15:15:21 +01:00
Jakob Borg
0b193b76c2 lib/config, lib/connections: Add optional connection limits (fixes #7176) (#7223)
This adds two new configuration options:

    // The number of connections at which we stop trying to connect to more
    // devices, zero meaning no limit. Does not affect incoming connections.
    ConnectionLimitEnough int

    // The maximum number of connections which we will allow in total, zero
    // meaning no limit. Affects incoming connections and prevents
    // attempting outgoing connections.
    ConnectionLimitMax int

These can be used to limit the number of concurrent connections in
various ways.
2021-01-11 15:14:44 +01:00
Jakob Borg
e6f0ed65be gui, man, authors: Update docs, translations, and contributors 2021-01-06 07:45:28 +01:00
Jakob Borg
b13b15758d lib/connections, lib/model: Track last connection duration (ref #7223) (#7242)
This adds a statistic to track the last connection duration per device.
It isn't used for much in this PR, but it's available for #7223 to use
in deciding how to order device connection attempts (deprioritizing
devices that just dropped our connection the last time).
2021-01-05 17:45:07 +01:00
Simon Frei
c48eb4241a lib/model: Fix child-check when deleting dirs in pull (#7236) 2021-01-02 21:40:37 +01:00
Simon Frei
0f8290485e lib/model: Handle index sender terminating due to error (fixes #7231) (#7232) 2020-12-30 09:59:11 +01:00
André Colomb
3d1edd2492 lib/fs: Fix TestChmodDir depending on umask (fixes #6551) (#7241)
The test would fail if the umask on UNIX is greater than 0022, because
the OS transparently subtracts it from the mode passed to Mkdir(), as
the Go documentation confirms.

Our goal here is not to test os.Mkdir(), so just make sure the desired
mode is actually set by forcing it afterwards.
2020-12-30 09:56:10 +01:00
Jakob Borg
a73db6fd84 gui, man, authors: Update docs, translations, and contributors 2020-12-30 07:45:26 +01:00
Simon Frei
a05dc6cc47 lib/model: Cleanup redundant filesystem variables in folders (#7237) 2020-12-27 22:26:25 +01:00
Simon Frei
5440d1dc3b gui: Do not replace zero versioning cleanup interval (#7234) 2020-12-27 10:08:15 +01:00
Simon Frei
f13e6ca631 lib/model: Remove obsolete return val from ccHandleFolders (ref #6443) (#7229) 2020-12-23 13:10:08 +01:00
Jakob Borg
81553b4da7 gui, man, authors: Update docs, translations, and contributors 2020-12-23 07:45:28 +01:00
André Colomb
07618f8674 gui: Fix missing sharing device when adding pending folder (#7227) 2020-12-22 22:37:29 +01:00
André Colomb
1555a4da7f cmd/stevents: Add command line argument for event type filtering. (#7226)
Add a -types flag which can be set to a comma-separated list of event
types.  The contents will be included verbatim in the URL parameter
"events".
2020-12-22 22:10:26 +01:00
Simon Frei
a20a5f61f0 lib/ur: Send unreported failures on shutdown (#7164) 2020-12-22 20:17:14 +01:00
André Colomb
4bcc38cf63 gui: Fix nonfunctional ignore button on pending folder notification (#7224)
The merge of #6443 apparently introduced a wrong argument name in
$scope.ignoreFolder().
2020-12-22 07:28:10 +01:00
Jakob Borg
05f25e600e lib/connections: Refactor connection loop (#7177)
This breaks out some methods from the connection loop to make it simpler
to manage and understand.

Some slight simplifications to remove the `seen` variable (we can filter
`nextDial` based on times are in the future or not, so we don't need to
track `seen`) and adding a minimum loop interval (5s) in case some
dialer goes haywire and requests a 0s redial interval or such.

Otherwise no significant behavioral changes.
2020-12-21 16:40:13 +01:00
Simon Frei
a744dee94c lib/fs: Correct wrapping order for meaningful log-caller (#7209) 2020-12-21 13:01:34 +01:00
Simon Frei
78bd0341a8 all: Handle errors opening db/creating file-set (ref #5907) (#7150) 2020-12-21 12:59:22 +01:00
Roberto Santalla
b5de49917c cmd/relaypoolsrv: Allow validation of relay join requests by certificate (fixes #7196) (#7217) 2020-12-21 11:55:16 +01:00
Simon Frei
c845e245a1 lib: Close underlying conn in protocol (fixes #7165) (#7212) 2020-12-21 11:40:51 +01:00
Simon Frei
4a787986cd lib/db: Prevent IndexID creation race (#7211) 2020-12-21 11:32:59 +01:00
Simon Frei
78a41828fc gui: Reflect change in untrusted in sharing tab (#7201) 2020-12-21 11:11:44 +01:00
Simon Frei
bd0c9913cf lib/db: Remove index ids when dropping folder (#7200) 2020-12-21 11:10:59 +01:00
Simon Frei
d904dfa191 lib/model: Fix flaky test and add some scanning debug (#7214) 2020-12-20 18:13:35 +01:00
Simon Frei
fa40ccece1 lib: Consistently set suture logging (#7202) 2020-12-18 19:44:00 +01:00
Simon Frei
7919310dc6 lib/model: Unflake TestIgnoreDeleteUnignore (#7208) 2020-12-18 18:42:09 +01:00
Simon Frei
7669af578a gui: Apply changes to untrusted (ref #6443) (#7206) 2020-12-17 23:13:28 +01:00
Simon Frei
739e99c4d9 lib/config: Remove deprecated pending entries from config (ref #6443) (#7204) 2020-12-17 22:49:29 +01:00
André Colomb
7502997e7e all: Store pending devices and folders in database (fixes #7178) (#6443) 2020-12-17 19:54:31 +01:00
Jakob Borg
4470cd5aaa gui, man, authors: Update docs, translations, and contributors 2020-12-16 07:45:23 +01:00
André Colomb
466e8a5cd0 gui: Sort folders and devices in advanced config modal (#7192) 2020-12-14 16:45:38 +01:00
Jakob Borg
4142a431b5 model: Actually print folder description in "Overriding" log message 2020-12-12 12:32:24 +01:00
Jakob Borg
5565afdd9f gui: version.tags is an array, which is truthy when empty 2020-12-12 10:34:26 +01:00
Simon Frei
0db3b7a530 build: Switch to gopsutil's v3 module (#7191) 2020-12-10 16:43:15 +01:00
Simon Frei
b37ecc3cf4 build: Update notify (fixes #7076) (#7189) 2020-12-10 15:43:05 +01:00
Jakob Borg
ec5a5d5218 lib/api: Returns tags in version as list (#7190) 2020-12-10 12:22:09 +01:00
Jakob Borg
7980c8cea2 gui: Harmonize architecture names 2020-12-10 11:23:47 +01:00
Jakob Borg
e9b68a224c lib/connections: Handle QUIC not being available (#7186)
This does two things:

- Exclude QUIC from go1.16 builds, automatically, for now, since it
  doesn't work and just panics.

- Provide some fake listeners and dialers when QUIC is disabled.

These fake listeners and dialers indicate that they are disabled and
unsupported, which silences "Dialing $address: unknown address scheme:
quic" type of stuff which is not super helpful to the user.
2020-12-09 19:23:50 +01:00
Simon Frei
8fd6b1d428 lib/protocol: Handle slashified paths in IsEncryptedParent (fixes #7184) (#7187) 2020-12-09 18:16:14 +01:00
André Colomb
4198b5061f gui: Split folders into two categories on the sharing tab for devices (#7162) 2020-12-09 14:54:51 +01:00
Jakob Borg
b0a525a504 gui, man, authors: Update docs, translations, and contributors 2020-12-09 07:45:25 +01:00
Eric Lesiuta
25d904dc37 gui: Fix blank device name under "Recent Changes" (#7185)
fixes the device showing up as blank if the friendly name is known
2020-12-07 22:19:28 +01:00
157 changed files with 4075 additions and 2016 deletions

View File

@@ -87,6 +87,7 @@ Dominik Heidler (asdil12) <dominik@heidler.eu>
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
Elliot Huffman <thelich2@gmail.com>
Emil Hessman (ceh) <emil@hessman.se>
Eric Lesiuta <elesiuta@gmail.com>
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
Evgeny Kuznetsov <evgeny@kuznetsov.md>
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
@@ -214,6 +215,7 @@ Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.
Rahmi Pruitt <rjpruitt16@gmail.com>
Richard Hartmann <RichiH@users.noreply.github.com>
Robert Carosi (nov1n) <robert@carosi.nl>
Roberto Santalla <roobre@users.noreply.github.com>
Robin Schoonover <robin@cornhooves.org>
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
Ross Smith II (rasa) <ross@smithii.com>

View File

@@ -28,16 +28,21 @@ func main() {
log.SetFlags(0)
target := flag.String("target", "localhost:8384", "Target Syncthing instance")
types := flag.String("types", "", "Filter for specific event types (comma-separated)")
apikey := flag.String("apikey", "", "Syncthing API key")
flag.Parse()
if *apikey == "" {
log.Fatal("Must give -apikey argument")
}
var eventsArg string
if len(*types) > 0 {
eventsArg = "&events=" + *types
}
since := 0
for {
req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/rest/events?since=%d", *target, since), nil)
req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/rest/events?since=%d%s", *target, since, eventsArg), nil)
if err != nil {
log.Fatal(err)
}

View File

@@ -131,6 +131,23 @@ func dump(ldb backend.Backend) {
fmt.Printf(" V:%v\n", v)
}
case db.KeyTypePendingFolder:
device := binary.BigEndian.Uint32(key[1:])
folder := string(key[5:])
var of db.ObservedFolder
of.Unmarshal(it.Value())
fmt.Printf("[pendingFolder] D:%d F:%s V:%v\n", device, folder, of)
case db.KeyTypePendingDevice:
device := "<invalid>"
dev, err := protocol.DeviceIDFromBytes(key[1:])
if err == nil {
device = dev.String()
}
var od db.ObservedDevice
od.Unmarshal(it.Value())
fmt.Printf("[pendingDevice] D:%v V:%v\n", device, od)
default:
fmt.Printf("[??? %d]\n %x\n %x\n", key[0], key, it.Value())
}

View File

@@ -6,9 +6,11 @@ import (
"compress/gzip"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"flag"
"fmt"
"github.com/syncthing/syncthing/lib/protocol"
"io"
"io/ioutil"
"log"
@@ -208,6 +210,7 @@ func main() {
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS10, // No SSLv3
ClientAuth: tls.RequestClientCert,
CipherSuites: []uint16{
// No RC4
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
@@ -338,6 +341,12 @@ func handleGetRequest(rw http.ResponseWriter, r *http.Request) {
}
func handlePostRequest(w http.ResponseWriter, r *http.Request) {
var relayCert *x509.Certificate
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
relayCert = r.TLS.PeerCertificates[0]
log.Printf("Got TLS cert from relay server")
}
var newRelay relay
err := json.NewDecoder(r.Body).Decode(&newRelay)
r.Body.Close()
@@ -359,6 +368,16 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
return
}
if relayCert != nil {
advertisedId := uri.Query().Get("id")
idFromCert := protocol.NewDeviceID(relayCert.Raw).String()
if advertisedId != idFromCert {
log.Println("Warning: Relay server requested to join with an ID different from the join request, rejecting")
http.Error(w, "mismatched advertised id and join request cert", http.StatusBadRequest)
return
}
}
host, port, err := net.SplitHostPort(uri.Host)
if err != nil {
if debug {
@@ -379,7 +398,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
if ip == nil || ip.IsUnspecified() {
uri.Host = net.JoinHostPort(rhost, port)
newRelay.URL = uri.String()
} else if host != rhost {
} else if host != rhost && relayCert == nil {
if debug {
log.Println("IP address advertised does not match client IP address", r.RemoteAddr, uri)
}

View File

@@ -247,7 +247,7 @@ func main() {
for _, pool := range pools {
pool = strings.TrimSpace(pool)
if len(pool) > 0 {
go poolHandler(pool, uri, mapping)
go poolHandler(pool, uri, mapping, cert)
}
}

View File

@@ -4,6 +4,7 @@ package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"io/ioutil"
"log"
@@ -16,7 +17,7 @@ const (
httpStatusEnhanceYourCalm = 429
)
func poolHandler(pool string, uri *url.URL, mapping mapping) {
func poolHandler(pool string, uri *url.URL, mapping mapping, ownCert tls.Certificate) {
if debug {
log.Println("Joining", pool)
}
@@ -31,7 +32,24 @@ func poolHandler(pool string, uri *url.URL, mapping mapping) {
uriCopy.String(),
})
resp, err := httpClient.Post(pool, "application/json", &b)
poolUrl, err := url.Parse(pool)
if err != nil {
log.Printf("Could not parse pool url '%s': %v", pool, err)
}
client := http.DefaultClient
if poolUrl.Scheme == "https" {
// Sent our certificate in join request
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{ownCert},
},
},
}
}
resp, err := client.Post(pool, "application/json", &b)
if err != nil {
log.Printf("Error joining pool %v: HTTP request: %v", pool, err)
time.Sleep(time.Minute)

View File

@@ -39,10 +39,10 @@ import (
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/syncthing"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/util"
"github.com/pkg/errors"
)
@@ -323,7 +323,7 @@ func main() {
}
if err != nil {
l.Warnln("Command line options:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if options.logFile == "default" || options.logFile == "" {
@@ -360,7 +360,7 @@ func main() {
)
if err != nil {
l.Warnln("Error reading device ID:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
@@ -370,7 +370,7 @@ func main() {
if options.browserOnly {
if err := openGUI(protocol.EmptyDeviceID); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
return
}
@@ -378,7 +378,7 @@ func main() {
if options.generateDir != "" {
if err := generate(options.generateDir); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
return
}
@@ -386,14 +386,14 @@ func main() {
// Ensure that our home directory exists.
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failure on home directory:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if err != nil {
l.Warnln("Error while Upgrading:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Upgraded from", options.upgradeTo)
return
@@ -424,13 +424,13 @@ func main() {
os.Exit(exitCodeForUpgrade(err))
}
l.Infof("Upgraded to %q", release.Tag)
os.Exit(util.ExitUpgrade.AsInt())
os.Exit(svcutil.ExitUpgrade.AsInt())
}
if options.resetDatabase {
if err := resetDB(); err != nil {
l.Warnln("Resetting database:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Successfully reset database - it will be rebuilt after next start.")
return
@@ -610,7 +610,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
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(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
// Candidate builds should auto upgrade. Make sure the option is set,
@@ -656,7 +656,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
} else {
l.Infof("Upgraded to %q, exiting now.", release.Tag)
os.Exit(util.ExitUpgrade.AsInt())
os.Exit(svcutil.ExitUpgrade.AsInt())
}
}
@@ -681,7 +681,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
appOpts.DBIndirectGCInterval = dur
}
app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
app, err := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
if err != nil {
l.Warnln("Failed to start Syncthing:", err)
os.Exit(svcutil.ExitError.AsInt())
}
if autoUpgradePossible {
go autoUpgrade(cfg, app, evLogger)
@@ -697,18 +701,18 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Warnln("Creating profile:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if err := pprof.StartCPUProfile(f); err != nil {
l.Warnln("Starting profile:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
}
go standbyMonitor(app, cfg)
if err := app.Start(); err != nil {
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
cleanConfigDirectory()
@@ -721,7 +725,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
status := app.Wait()
if status == util.ExitError {
if status == svcutil.ExitError {
l.Warnln("Syncthing stopped with error:", app.Error())
}
@@ -740,7 +744,7 @@ func setupSignalHandling(app *syncthing.App) {
signal.Notify(restartSign, sigHup)
go func() {
<-restartSign
app.Stop(util.ExitRestart)
app.Stop(svcutil.ExitRestart)
}()
// Exit with "success" code (no restart) on INT/TERM
@@ -749,7 +753,7 @@ func setupSignalHandling(app *syncthing.App) {
signal.Notify(stopSign, os.Interrupt, sigTerm)
go func() {
<-stopSign
app.Stop(util.ExitSuccess)
app.Stop(svcutil.ExitSuccess)
}()
}
@@ -786,7 +790,7 @@ func auditWriter(auditFile string) io.Writer {
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
if err != nil {
l.Warnln("Audit:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
auditDest = auditFile
}
@@ -836,7 +840,7 @@ func standbyMonitor(app *syncthing.App, cfg config.Wrapper) {
// things a moment to stabilize.
time.Sleep(restartDelay)
app.Stop(util.ExitRestart)
app.Stop(svcutil.ExitRestart)
return
}
now = time.Now()
@@ -906,7 +910,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger)
sub.Unsubscribe()
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
time.Sleep(time.Minute)
app.Stop(util.ExitUpgrade)
app.Stop(svcutil.ExitUpgrade)
return
}
}
@@ -994,13 +998,13 @@ func setPauseState(cfg config.Wrapper, paused bool) {
}
if _, err := cfg.Replace(raw); err != nil {
l.Warnln("Cannot adjust paused state:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
}
func exitCodeForUpgrade(err error) int {
if _, ok := err.(*errNoUpgrade); ok {
return util.ExitNoUpgradeAvailable.AsInt()
return svcutil.ExitNoUpgradeAvailable.AsInt()
}
return util.ExitError.AsInt()
return svcutil.ExitError.AsInt()
}

View File

@@ -25,8 +25,8 @@ import (
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
var (
@@ -99,7 +99,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if t := time.Since(restarts[0]); t < loopThreshold {
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
copy(restarts[0:], restarts[1:])
@@ -169,7 +169,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if err == nil {
// Successful exit indicates an intentional shutdown
os.Exit(util.ExitSuccess.AsInt())
os.Exit(svcutil.ExitSuccess.AsInt())
}
if exiterr, ok := err.(*exec.ExitError); ok {
@@ -177,7 +177,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if stopped || runtimeOptions.noRestart {
os.Exit(exitCode)
}
if exitCode == util.ExitUpgrade.AsInt() {
if exitCode == svcutil.ExitUpgrade.AsInt() {
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
l.Infoln("Restarting monitor...")
@@ -189,7 +189,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
}
if runtimeOptions.noRestart {
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Syncthing exited:", err)

8
go.mod
View File

@@ -3,7 +3,6 @@ module github.com/syncthing/syncthing
require (
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/xdr v1.1.0
github.com/ccding/go-stun v0.1.2
@@ -15,11 +14,9 @@ require (
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
github.com/getsentry/raven-go v0.2.0
github.com/go-ldap/ldap/v3 v3.2.4
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gobwas/glob v0.2.3
github.com/gogo/protobuf v1.3.1
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/golang/protobuf v1.4.3
github.com/greatroar/blobloom v0.5.0
github.com/jackpal/gateway v1.0.6
github.com/jackpal/go-nat-pmp v1.0.2
@@ -37,8 +34,8 @@ require (
github.com/prometheus/client_golang v1.8.0
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/sasha-s/go-deadlock v0.2.0
github.com/shirou/gopsutil v3.20.10+incompatible
github.com/syncthing/notify v0.0.0-20201109091751-9a0e44181151
github.com/shirou/gopsutil/v3 v3.20.11
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7
github.com/thejerf/suture/v4 v4.0.0
github.com/urfave/cli v1.22.4
@@ -48,6 +45,7 @@ require (
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1
golang.org/x/text v0.3.4
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/protobuf v1.23.0
)
go 1.14

9
go.sum
View File

@@ -399,8 +399,8 @@ github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.20.10+incompatible h1:kQuRhh6h6y4luXvnmtu/lJEGtdJ3q8lbu9NQY99GP+o=
github.com/shirou/gopsutil v3.20.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v3 v3.20.11 h1:NeVf1K0cgxsWz+N3671ojRptdgzvp7BXL3KV21R0JnA=
github.com/shirou/gopsutil/v3 v3.20.11/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
@@ -455,8 +455,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syncthing/notify v0.0.0-20201109091751-9a0e44181151 h1:aKnLuEFWn/7u42UR82PxsPOMkoBAhq+06oRtUnK3Z1o=
github.com/syncthing/notify v0.0.0-20201109091751-9a0e44181151/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc h1:b6b5XVKqpwxC6keIYThA5/XhFue4zNWRwv8FfqlKoxA=
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 h1:udtnv1cokhJYqnUfCMCppJ71bFN9VKfG1BQ6UsYZnx8=
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
@@ -582,6 +582,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -66,6 +66,7 @@
"Deleted": "Изтрито",
"Deselect All": "Никое",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Устройство",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) с адрес {{address}} желае да се свърже. Да бъде ли добавено?",
"Device ID": "Идентификатор на устройство",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Ограничението на скоростта трябва да бъде положително число (0: неограничено)",
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Ще бъдат спрени и автоматично синхронизирани, когато грешката бъде оправена.",
"This Device": "Вашето устройство",
"This can easily give hackers access to read and change any files on your computer.": "Така се предоставя изключително лесен достъп (четене, редактиране и изтриване) до всеки файл, на компютъра Ви.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Esborrat",
"Deselect All": "Anul·lar tota la selecció",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Dispositiu",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositiu \"{{name}}\" ({{device}} a l'adreça {{address}}) vol connectar. Afegir nou dispositiu?",
"Device ID": "ID del dispositiu",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "El llímit del ritme deu ser un nombre no negatiu (0: sense llímit)",
"The rescan interval must be a non-negative number of seconds.": "L'interval de reescaneig deu ser un nombre positiu de segons.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Es reintenta automàticament i es sincronitzaràn quant el resolga l'error.",
"This Device": "Aquest Dispositiu",
"This can easily give hackers access to read and change any files on your computer.": "Açò pot donar accés fàcilment als hackers per a llegir i canviar qualsevol fitxer al teu ordinador.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Smazáno",
"Deselect All": "Zrušit výběr všeho",
"Deselect devices to stop sharing this folder with.": "Zrušte výběr zařízení, se kterými již nemá být tato složka sdílena.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Zařízení",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Zařízení „{{name}}“ ({{device}} na {{address}}) se chce připojit. Přidat nové zařízení?",
"Device ID": "Identifikátor zařízení",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Je třeba, aby limit rychlosti bylo kladné číslo (0: bez limitu)",
"The rescan interval must be a non-negative number of seconds.": "Je třeba, aby interval opakování skenování bylo kladné číslo.",
"There are no devices to share this folder with.": "Nejsou žádná zařízení, se kterými lze sdílet tuto složku.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Nové pokusy o synchronizaci budou probíhat automaticky a položky budou synchronizovány jakmile bude chyba odstraněna.",
"This Device": "Toto zařízení",
"This can easily give hackers access to read and change any files on your computer.": "Toto může útočníkům jednoduše umožnit čtení a úpravy souborů na vašem počítači. ",

View File

@@ -25,7 +25,7 @@
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "En ekstern kommando styrer versioneringen. Den skal fjerne filen fra den delte mappe. Hvis stien til programmet indeholder mellemrum, bør den sættes i anførselstegn.",
"Anonymous Usage Reporting": "Anonym brugerstatistik",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formatet for anonym brugerstatistik er ændret. Vil du flytte til det nye format?",
"Are you sure you want to permanently delete all these files?": "Are you sure you want to permanently delete all these files?",
"Are you sure you want to permanently delete all these files?": "Slette valgte filer permanent?",
"Are you sure you want to remove device {%name%}?": "Er du sikker på, at du vil fjerne enheden {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Er du sikker på, at du vil fjerne mappen {{label}}?",
"Are you sure you want to restore {%count%} files?": "Er du sikker på, at du vil genskabe {{count}} filer?",
@@ -66,12 +66,13 @@
"Deleted": "Slettet",
"Deselect All": "Fravælg alle",
"Deselect devices to stop sharing this folder with.": "Fravælg enheder for at stoppe mappe deling.",
"Deselect folders to stop sharing with this device.": "Fravælg mapper for at stoppe deling med denne enhed.",
"Device": "Enhed",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enheden “{{name}}” ({{device}} på {{address}}) vil gerne forbinde. Tilføj denne enhed?",
"Device ID": "Enheds-ID",
"Device Identification": "Enhedsidentifikation",
"Device Name": "Enhedsnavn",
"Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password",
"Device is untrusted, enter encryption password": "Enhed er ikke-troværdig, indtast krypteringsadgangskode",
"Device rate limits": "Enhedens hastighedsbegrænsning",
"Device that last modified the item": "Enhed, som sidst ændrede filen",
"Devices": "Enheder",
@@ -235,7 +236,7 @@
"Release Notes": "Udgivelsesnoter",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Udgivelseskandidater indeholder alle de nyeste funktioner og rettelser. De er det samme som de traditionelle tougers-udgivelser af Syncthing.",
"Remote Devices": "Fjernenheder ",
"Remote GUI": "Remote GUI",
"Remote GUI": "Ekstern grafisk brugerflade",
"Remove": "Fjern",
"Remove Device": "Fjern enhed",
"Remove Folder": "Fjern mappe",
@@ -323,7 +324,7 @@
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De følgende intervaller er brugt: Inden for den første time bliver en version gemt hvert 30. sekund, inden for den første dag bliver en version gemt hver time, inden for de første 30 dage bliver en version gemt hver dag, og indtil den maksimale alder bliver en version gemt hver uge.",
"The following items could not be synchronized.": "Følgende filer kunne ikke synkroniseres.",
"The following items were changed locally.": "De følgende filer er ændret lokalt.",
"The following unexpected items were found.": "The following unexpected items were found.",
"The following unexpected items were found.": "Følgende ikke-forventet emner blev fundet.",
"The interval must be a positive number of seconds.": "Intervallet skal være et positivt antal sekunder.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.",
"The maximum age must be a number and cannot be blank.": "Maksimal alder skal være et tal og feltet må ikke være tomt.",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Hastighedsbegrænsningen skal være et ikke-negativt tal (0: ingen begrænsning)",
"The rescan interval must be a non-negative number of seconds.": "Genskanningsintervallet skal være et ikke-negativt antal sekunder.",
"There are no devices to share this folder with.": "Der er ingen enheder at dele denne mappe med.",
"There are no folders to share with this device.": "Der er ingen mapper at dele med denne enhed.",
"They are retried automatically and will be synced when the error is resolved.": "De prøves igen automatisk og vil blive synkroniseret, når fejlen er løst.",
"This Device": "Denne enhed",
"This can easily give hackers access to read and change any files on your computer.": "Dette gør det nemt for hackere at få adgang til at læse og ændre filer på din computer.",
@@ -350,7 +352,7 @@
"Unavailable/Disabled by administrator or maintainer": "Ikke tilgængelig / deaktiveret af administrator eller vedligeholder",
"Undecided (will prompt)": "Ubestemt (du bliver spurgt)",
"Unexpected Items": "Ikke forventede elementer",
"Unexpected items have been found in this folder.": "Unexpected items have been found in this folder.",
"Unexpected items have been found in this folder.": "Ikke-forventet emner blev fundet i denne mappe.",
"Unignore": "Fjern ignorering",
"Unknown": "Ukendt",
"Unshared": "Ikke delt",

View File

@@ -66,6 +66,7 @@
"Deleted": "Gelöscht",
"Deselect All": "Alle abwählen",
"Deselect devices to stop sharing this folder with.": "Die Geräte abwählen, für die dieser Ordner nicht mehr freigegeben werden soll.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Gerät",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät \"{{name}}\" ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Device ID": "Gerätekennung",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Das Datenratelimit muss eine nicht negative Zahl sein (0 = kein Limit).",
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl (in Sekunden) sein.",
"There are no devices to share this folder with.": "Es gibt keine Geräte, mit denen dieser Ordner geteilt werden kann.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Sie werden automatisch heruntergeladen und werden synchronisiert, wenn der Fehler behoben wurde.",
"This Device": "Dieses Gerät",
"This can easily give hackers access to read and change any files on your computer.": "Dies kann dazu führen, dass Unberechtigte relativ einfach auf Ihre Dateien zugreifen und diese ändern können.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Διαγραμμένα",
"Deselect All": "Αποεπιλογή όλων",
"Deselect devices to stop sharing this folder with.": "Αποεπιλέξτε συσκευές για να σταματήσει ο διαμοιρασμός του φακέλου με αυτές.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Συσκευή",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Η συσκευή \"{{name}}\" ({{device}} στη διεύθυνση {{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής;",
"Device ID": "Ταυτότητα συσκευής",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Το όριο ταχύτητας πρέπει να είναι ένας μη-αρνητικός αριθμός (0: χωρίς όριο)",
"The rescan interval must be a non-negative number of seconds.": "Ο χρόνος επανελέγχου για αλλαγές είναι σε δευτερόλεπτα (δηλ. θετικός αριθμός).",
"There are no devices to share this folder with.": "Δεν υπάρχουν συσκευές με τις οποίες διαμοιράζεται αυτός ο φάκελος.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Όταν επιλυθεί το σφάλμα θα κατεβούν και θα συχρονιστούν αυτόματα.",
"This Device": "Αυτή η συσκευή",
"This can easily give hackers access to read and change any files on your computer.": "Αυτό μπορεί εύκολα να δώσει πρόσβαση ανάγνωσης και επεξεργασίας αρχείων του υπολογιστή σας σε χάκερς.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Deleted",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Device",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Device ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This Device": "This Device",
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Deleted",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Device",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Device ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This Device": "This Device",
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Deleted",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Device",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Device ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This Device": "This Device",
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Forigita",
"Deselect All": "Malelekti Ĉiujn",
"Deselect devices to stop sharing this folder with.": "Malelekti aparatojn por ĉesi komunigi tiun ĉi dosierujon kun ili.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Aparato",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Aparato \"{{name}}\" ({{device}} ĉe {{address}}) volas konekti. Aldoni la novan aparaton?",
"Device ID": "Aparato ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "La rapideca limo devas esti pozitiva nombro (0: senlimo)",
"The rescan interval must be a non-negative number of seconds.": "La intervalo de reskano devas esti pozitiva nombro da sekundoj.",
"There are no devices to share this folder with.": "Estas neniu aparato kun kiu komunigi tiun ĉi dosierujon.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Ili estas reprovitaj aŭtomate kaj estos sinkronigitaj kiam la eraro estas solvita.",
"This Device": "Ĉi Tiu Aparato",
"This can easily give hackers access to read and change any files on your computer.": "Ĉi tio povas facile doni al kodumuloj atingon por legi kaj ŝanĝi ajnajn dosierojn en via komputilo.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Eliminado",
"Deselect All": "Deseleccionar Todo",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "El dispositivo \"{{name}}\" ({{device}} en la dirección {{address}}) quiere conectarse. Añadir nuevo dispositivo?",
"Device ID": "ID del Dispositivo",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "El límite de velocidad debe ser un número no negativo (0: sin límite)",
"The rescan interval must be a non-negative number of seconds.": "El intervalo de actualización debe ser un número positivo de segundos.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Se reintentarán de forma automática y se sincronizarán cuando se resuelva el error.",
"This Device": "Este Dispositivo",
"This can easily give hackers access to read and change any files on your computer.": "Esto podría permitir fácilmente el acceso a hackers para leer y modificar cualquier fichero de tu equipo.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Eliminado",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "El dispositivo \"{{name}}\" ({{device}} en la dirección {{address}}) quiere conectarse. Añadir nuevo dispositivo?",
"Device ID": "ID del Dispositivo",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "El límite de velocidad debe ser un número no negativo (0: sin límite)",
"The rescan interval must be a non-negative number of seconds.": "El intervalo de actualización debe ser un número positivo de segundos.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Se reintentarán de forma automática y se sincronizarán cuando se resuelva el error.",
"This Device": "Este Dispositivo",
"This can easily give hackers access to read and change any files on your computer.": "Esto podría permitir fácilmente el acceso a hackers para leer y modificar cualquier fichero de tu equipo.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Kendua",
"Deselect All": "Hautaketa guztia kendu",
"Deselect devices to stop sharing this folder with.": "Desautatu karpeta honekin partekatu nahi ez dituzun gailuak.",
"Deselect folders to stop sharing with this device.": "Desautatu karpetak gailu honekin partekatzeari uzteko.",
"Device": "Tresna",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Tresna \"{{name}}\" ({{device}} {{address}} era) konektatu nahi du. Onhartzen duzu ?",
"Device ID": "Tresnaren ID-a",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Ixuriaren emaria ez da negatiboa izaiten ahal (0 = mugarik gabekoa)",
"The rescan interval must be a non-negative number of seconds.": "Ikerketaren tartea ez da segundo kopuru negatiboa izaiten ahal",
"There are no devices to share this folder with.": "Ez dago partekatutako erabilera horri gehitzeko gailurik.",
"There are no folders to share with this device.": "Ez dago gailu honekin partekatzeko karpetarik.",
"They are retried automatically and will be synced when the error is resolved.": "Errorea zuzendua izanen delarik, automatikoki berriz entseatuak et sinkronizatuak izanen dira",
"This Device": "Tresna hau",
"This can easily give hackers access to read and change any files on your computer.": "Hunek errexki irakurtzen eta aldatzen uzten ahal du zure ordenagailuko edozein fitxero, nahiz eta sartu denak ez haizu izan!",

View File

@@ -66,6 +66,7 @@
"Deleted": "Poistettu",
"Deselect All": "Poista valinnat",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Laite",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Laite \"{{name}}\" {{device}} osoitteessa ({{address}}) haluaa yhdistää. Lisää uusi laite?",
"Device ID": "Laitteen ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Nopeusrajan tulee olla positiivinen luku tai nolla. (0: ei rajaa)",
"The rescan interval must be a non-negative number of seconds.": "Uudelleenskannauksen aikavälin tulee olla ei-negatiivinen numero sekunteja.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Niiden synkronointia yritetään uudelleen automaattisesti.",
"This Device": "Tämä laite",
"This can easily give hackers access to read and change any files on your computer.": "Tämä voi helposti sallia vihamielisille tahoille pääsyn lukea ja muokata kaikkia tiedostojasi",

View File

@@ -66,6 +66,7 @@
"Deleted": "Supprimé",
"Deselect All": "Tout déselectionner",
"Deselect devices to stop sharing this folder with.": "Désélectionnez les appareils avec lesquels vous ne souhaitez plus partager ces données.",
"Deselect folders to stop sharing with this device.": "Désélectionnez les partages auxquels cet appareil doit plus participer.",
"Device": "Appareil",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{device}}), appareil actuellement à {{address}}, demande à se connecter.\nAcceptez-vous de l'ajouter à votre liste d'appareils connus ?",
"Device ID": "ID de l'appareil",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "La limite de débit ne doit pas être négative (0 = pas de limite)",
"The rescan interval must be a non-negative number of seconds.": "L'intervalle d'analyse ne doit pas être un nombre négatif de secondes.",
"There are no devices to share this folder with.": "Il n'y a aucun appareil à ajouter à ce partage.",
"There are no folders to share with this device.": "Il n'y a aucun partage disponible.",
"They are retried automatically and will be synced when the error is resolved.": "Ils seront automatiquement retentés et synchronisés quand l'erreur sera résolue.",
"This Device": "Cet appareil",
"This can easily give hackers access to read and change any files on your computer.": "Ceci peut aisément permettre à un intrus de lire et modifier n'importe quel fichier de votre ordinateur.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Fuortsmiten",
"Deselect All": "Alles Deselektearje",
"Deselect devices to stop sharing this folder with.": "Kies de apparaten om dizze map net langer mei te dielen.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Apparaat",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Apparaat \"{{name}}\" {{device}} op ({{address}}) wol ferbining meitsje. Nij apparaat taheakje?",
"Device ID": "Apparaat-ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "It fluggenslimyt moat in posityf nûmer wêze (0: gjin limyt)",
"The rescan interval must be a non-negative number of seconds.": "It wersken-ynterfal moat in posityf tal fan sekonden wêze.",
"There are no devices to share this folder with.": "Der binne gjin apparaten om dizze map mei te dielen.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Sy wurde automatysk opnij probearre en sille syngronisearre wurde wannear at de flater oplost is.",
"This Device": "Dit Apparaat",
"This can easily give hackers access to read and change any files on your computer.": "Dit kin samar ynkringers (hackers) tagong jaan om elke triem op jo kompjûter te besjen en te feroarjen.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Törölve",
"Deselect All": "Kijelölés megszüntetése",
"Deselect devices to stop sharing this folder with.": "Azon eszközök kijelölésének törlése, amelyekkel e mappa megosztása leállítandó.",
"Deselect folders to stop sharing with this device.": "Szüntesse meg a mappák kijelölését a mappák megosztásának leállításához ezzel az eszközzel.",
"Device": "Eszköz",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" eszköz ({{device}} @ {{address}}) szeretne csatlakozni. Hozzáadható az új eszköz?",
"Device ID": "Eszközazonosító",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "A sebességlimitnek pozitív számnak kell lennie (0: nincs limit)",
"The rescan interval must be a non-negative number of seconds.": "Az átnézési intervallum nullánál nagyobb másodperc érték kell legyen.",
"There are no devices to share this folder with.": "Nincsenek eszközök, amelyekkel megosztható lenne a mappa.",
"There are no folders to share with this device.": "Nincsenek mappák, amelyek megoszthatók ezzel az eszközzel.",
"They are retried automatically and will be synced when the error is resolved.": "A hiba javítása után automatikusan újra megpróbálja a szinkronizálást.",
"This Device": "Ez az eszköz",
"This can easily give hackers access to read and change any files on your computer.": "Így a hekkerek könnyedén hozzáférést szerezhetnek a gépen tárolt fájlok olvasásához és módosításához.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Cancellato",
"Deselect All": "Deseleziona tutto",
"Deselect devices to stop sharing this folder with.": "Deseleziona i dispositivi con cui interrompere la condivisione di questa cartella.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Il dispositivo \"{{name}}\" ({{device}} - {{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
"Device ID": "ID Dispositivo",
@@ -193,7 +194,7 @@
"No File Versioning": "Nessun Controllo Versione",
"No files will be deleted as a result of this operation.": "Nessun file verrà eliminato come risultato di questa operazione.",
"No upgrades": "Nessun aggiornamento",
"Not shared": "Not shared",
"Not shared": "Non condiviso",
"Notice": "Avviso",
"OK": "OK",
"Off": "Disattiva",
@@ -235,7 +236,7 @@
"Release Notes": "Note di Rilascio",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Le versioni candidate al rilascio contengono le ultime funzionalità e aggiustamenti. Sono simili ai rilasci bisettimanali di Syncthing.",
"Remote Devices": "Dispositivi Remoti",
"Remote GUI": "Remote GUI",
"Remote GUI": "Interfaccia remota",
"Remove": "Rimuovi",
"Remove Device": "Rimuovi Dispositivo",
"Remove Folder": "Rimuovi Cartella",
@@ -270,7 +271,7 @@
"Share Folder": "Condividi la Cartella",
"Share Folders With Device": "Condividi Cartelle con il Dispositivo",
"Share this folder?": "Vuoi condividere questa cartella?",
"Shared Folders": "Shared Folders",
"Shared Folders": "Cartelle condivise",
"Shared With": "Condiviso Con",
"Sharing": "Condivisione",
"Show ID": "Mostra ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Il limite di banda deve essere un numero non negativo (0: nessun limite)",
"The rescan interval must be a non-negative number of seconds.": "L'intervallo di scansione deve essere un numero non negativo secondi.",
"There are no devices to share this folder with.": "Non ci sono dispositivi con cui condividere questa cartella.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Verranno effettuati tentativi in automatico e verranno sincronizzati quando l'errore sarà risolto.",
"This Device": "Questo Dispositivo",
"This can easily give hackers access to read and change any files on your computer.": "Ciò potrebbe facilmente permettere agli hackers accesso alla lettura e modifica di qualunque file del tuo computer.",
@@ -356,7 +358,7 @@
"Unshared": "Non Condiviso",
"Unshared Devices": "Dispositivi non condivisi",
"Unshared Folders": "Unshared Folders",
"Untrusted": "Untrusted",
"Untrusted": "Non attendibile",
"Up to Date": "Sincronizzato",
"Updated": "Aggiornato",
"Upgrade": "Aggiornamento",

View File

@@ -66,6 +66,7 @@
"Deleted": "削除",
"Deselect All": "すべて選択解除",
"Deselect devices to stop sharing this folder with.": "このフォルダの共有を停止したいデバイスがある場合は、当該デバイスの選択を解除してください。",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "デバイス",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "デバイス「{{name}}」 ({{address}} の {{device}}) が接続を求めています。新しいデバイスとして追加しますか?",
"Device ID": "デバイスID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "帯域制限値は0以上で指定して下さい。 (0で無制限)",
"The rescan interval must be a non-negative number of seconds.": "再スキャン間隔は0秒以上で指定してください。",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "エラーが解決すると、自動的に再試行され同期されます。",
"This Device": "このデバイス",
"This can easily give hackers access to read and change any files on your computer.": "この設定のままでは、あなたのコンピューターにある任意のファイルを、他者が簡単に盗み見たり書き換えたりすることができます。",

View File

@@ -66,6 +66,7 @@
"Deleted": "삭제됨",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "기기",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
"Device ID": "기기 ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "대역폭 제한 설정은 반드시 양수로 입력해야 합니다 (0: 무제한)",
"The rescan interval must be a non-negative number of seconds.": "재검색 간격은 초단위이며 양수로 입력해야 합니다.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "오류가 해결되면 자동적으로 동기화 됩니다.",
"This Device": "현재 기기",
"This can easily give hackers access to read and change any files on your computer.": "이 설정은 해커가 손쉽게 사용자 컴퓨터의 모든 파일을 읽고 변경할 수 있도록 할 수 있습니다.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Ištrinta",
"Deselect All": "Nuimti žymėjimą nuo visų",
"Deselect devices to stop sharing this folder with.": "Panaikinti įrenginių pasirinkimą, kad su jais būtų nustota bendrinti šį aplanką. ",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Įrenginys",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Įrenginys \"{{name}}\" ({{device}} {{address}}) nori prisijungti. Pridėti naują įrenginį?",
"Device ID": "Įrenginio ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Srauto maksimalus greitis privalo būti ne neigiamas skaičius (0: nėra apribojimo)",
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
"There are no devices to share this folder with.": "Nėra įrenginių su kuriais bendrinti šį aplanką.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Failus bus automatiškai bandoma parsiųsti dar kartą kai išspręsite klaidas.",
"This Device": "Šis įrenginys",
"This can easily give hackers access to read and change any files on your computer.": "Tai gali suteikti programišiams lengvą prieigą skaityti ir keisti bet kokius failus jūsų kompiuteryje.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Slettet",
"Deselect All": "Fjern alle markeringer",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Enhet",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) ønsker å koble til. Legge til ny enhet?",
"Device ID": "Enhets-ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Hastighetsbegrensningen kan ikke være et negativt tall (0: ingen begrensing)",
"The rescan interval must be a non-negative number of seconds.": "Antall sekund for intervallet kan ikke være negativt.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Disse hentes automatisk og vil synkroniseres når feilen er blitt utbedret.",
"This Device": "Denne enheten",
"This can easily give hackers access to read and change any files on your computer.": "Dette kan lett gi hackere tilgang til å lese og endre alle filer på datamaskinen din.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Verwijderd",
"Deselect All": "Alles deselecteren",
"Deselect devices to stop sharing this folder with.": "Deselecteer apparaten om er deze map niet meer mee te delen.",
"Deselect folders to stop sharing with this device.": "Deselecteer mappen om te stoppen met delen met dit apparaat.",
"Device": "Apparaat",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Apparaat \"{{name}}\" ({{device}} op {{address}}) wil verbinden. Nieuw apparaat toevoegen?",
"Device ID": "Apparaat-ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "De snelheidsbegrenzing moet een positief getal zijn (0: geen begrenzing)",
"The rescan interval must be a non-negative number of seconds.": "Het interval voor opnieuw scannen moet een positief aantal seconden zijn.",
"There are no devices to share this folder with.": "Er zijn geen apparaten om deze map mee te delen.",
"There are no folders to share with this device.": "Er zijn geen mappen om te delen met dit apparaat.",
"They are retried automatically and will be synced when the error is resolved.": "Ze worden automatisch opnieuw geprobeerd en zullen gesynchroniseerd worden wanneer de fout opgelost is.",
"This Device": "Dit apparaat",
"This can easily give hackers access to read and change any files on your computer.": "Dit kan hackers eenvoudig toegang geven om bestanden op uw computer te lezen en te wijzigen.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Usunięto",
"Deselect All": "Odznacz wszystko",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Urządzenie",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie \"{{name}}\" {{device}} ({{address}}) chce się połączyć. Dodać nowe urządzenie?",
"Device ID": "ID urządzenia",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Ograniczenie prędkości powinno być nieujemną liczbą całkowitą (0: brak ograniczeń)",
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie, synchronizacja nastąpi po usunięciu usterki.",
"This Device": "To urządzenie",
"This can easily give hackers access to read and change any files on your computer.": "Może to umożliwić osobom trzecim dostęp do odczytu i zmian dowolnych plików na urządzeniu.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Apagado",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositivo \"{{name}}\" ({{device}} em {{address}}) quer se conectar. Adicionar novo dispositivo?",
"Device ID": "ID do dispositivo",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "O limite de velocidade deve ser um número positivo (0: sem limite)",
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações deve ser um número positivo de segundos.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Serão tentadas automaticamente e sincronizadas após o erro ter sido resolvido.",
"This Device": "Este dispositivo",
"This can easily give hackers access to read and change any files on your computer.": "Isto pode dar a hackers poder de leitura e escrita de qualquer arquivo em seu dispositivo.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Eliminado",
"Deselect All": "Retirar todas as selecções",
"Deselect devices to stop sharing this folder with.": "Retire a selecção para deixar de partilhar a pasta com esses dispositivos.",
"Deselect folders to stop sharing with this device.": "Retire a selecção das pastas para terminar a partilha com este dispositivo.",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "O dispositivo \"{{name}}\" ({{device}} em {{address}}) quer conectar-se. Adiciono este novo dispositivo?",
"Device ID": "ID do dispositivo",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "O limite de velocidade tem que ser um número que não seja negativo (0: sem limite)",
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações tem que ser um valor não negativo de segundos.",
"There are no devices to share this folder with.": "Não existem quaisquer dispositivos com os quais se possa partilhar esta pasta.",
"There are no folders to share with this device.": "Não existem pastas para partilhar com este dispositivo.",
"They are retried automatically and will be synced when the error is resolved.": "Será tentado automaticamente e os itens serão sincronizados assim que o erro seja resolvido.",
"This Device": "Este dispositivo",
"This can easily give hackers access to read and change any files on your computer.": "Isso facilmente dará acesso aos piratas informáticos para lerem e modificarem quaisquer ficheiros no seu computador.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Удалено",
"Deselect All": "Снять выделение",
"Deselect devices to stop sharing this folder with.": "Отмените выбор устройств, чтобы прекратить совместное использование этой папки.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Устройство",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство «{{name}}» ({{device}} на {{address}}) хочет подключиться. Добавить новое устройство?",
"Device ID": "ID устройства",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Скорость должна быть неотрицательным числом (0: нет ограничения)",
"The rescan interval must be a non-negative number of seconds.": "Интервал пересканирования должен быть неотрицательным количеством секунд.",
"There are no devices to share this folder with.": "Нет устройств, для которых будет доступна эта папка.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Будут синхронизированы автоматически когда ошибка будет исправлена.",
"This Device": "Это устройство",
"This can easily give hackers access to read and change any files on your computer.": "Это может дать доступ хакерам для чтения и изменения любых файлов на вашем компьютере.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Zmazané",
"Deselect All": "Odznačiť všetko",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Zariadenie",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Zariadenie \"{{name}}\" ({{device}} na {{address}}) sa chce pripojiť. Pridať nové zariadenie?",
"Device ID": "ID zariadenia",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Limit rýchlosti musí byť kladné číslo (0: bez limitu)",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This Device": "Toto zariadenie",
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",

View File

@@ -18,7 +18,7 @@
"Advanced": "Avancerat",
"Advanced Configuration": "Avancerad konfiguration",
"All Data": "Alla data",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alla mappar som delas med denna enhet måste skyddas av ett lösenord, så att all skickad data är oläslig utan det angivna lösenordet.",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alla mappar som delas med denna enhet måste skyddas av ett lösenord, så att alla data som skickas är oläsliga utan det angivna lösenordet.",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistiksrapportering?",
"Allowed Networks": "Tillåtna nätverk",
"Alphabetic": "Alfabetisk",
@@ -66,6 +66,7 @@
"Deleted": "Borttagna",
"Deselect All": "Avmarkera alla",
"Deselect devices to stop sharing this folder with.": "Avmarkera enheter för att sluta dela denna mapp med.",
"Deselect folders to stop sharing with this device.": "Avmarkera mappar för att sluta dela med denna enhet.",
"Device": "Enhet",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
"Device ID": "Enhets-ID",
@@ -229,7 +230,7 @@
"Random": "Slumpmässig",
"Receive Encrypted": "Ta emot krypterade",
"Receive Only": "Ta endast emot",
"Received data is already encrypted": "Mottagna data är redan krypterad",
"Received data is already encrypted": "Mottagna data är redan krypterade",
"Recent Changes": "Senaste ändringar",
"Reduced by ignore patterns": "Minskas med ignorera mönster",
"Release Notes": "Versionsanteckningar",
@@ -293,7 +294,7 @@
"Start Browser": "Starta webbläsaren",
"Statistics": "Statistik",
"Stopped": "Stoppad",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Lagrar och synkroniserar endast krypterad data. Mappar på alla anslutna enheter måste ställas in med samma lösenord eller vara av typen \"{{receiveEncrypted}}\".",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Lagrar och synkroniserar endast krypterade data. Mappar på alla anslutna enheter måste ställas in med samma lösenord eller vara av typen \"{{receiveEncrypted}}\".",
"Support": "Support",
"Support Bundle": "Support Bundle",
"Sync Protocol Listen Addresses": "Lyssnaradresser för synkroniseringsprotokollet",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Frekvensgränsen måste vara ett icke-negativt tal (0: ingen gräns)",
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
"There are no devices to share this folder with.": "Det finns inga enheter att dela denna mapp med.",
"There are no folders to share with this device.": "Det finns inga mappar att dela med denna enhet.",
"They are retried automatically and will be synced when the error is resolved.": "De omprövas automatiskt och kommer att synkroniseras när felet är löst.",
"This Device": "Denna enhet",
"This can easily give hackers access to read and change any files on your computer.": "Detta kan lätt ge hackare tillgång till att läsa och ändra några filer på datorn.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Silinen",
"Deselect All": "Tüm Seçimi Kaldır",
"Deselect devices to stop sharing this folder with.": "Bu klasörün paylaşımının durdurulacağı cihazların seçimini kaldırın.",
"Deselect folders to stop sharing with this device.": "Bu cihazla paylaşımı durdurulacak klasörlerin seçimini kaldırın.",
"Device": "Cihaz",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{address}} adresindeki {{device}}) cihazı bağlanmak istiyor. Yeni cihaz eklensin mi?",
"Device ID": "Cihaz Kimliği",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Hız sınırı negatif olmayan bir sayı olmak zorundadır (0: sınır yok)",
"The rescan interval must be a non-negative number of seconds.": "Yeniden tarama aralığı negatif olmayan bir saniye sayısı olmak zorundadır.",
"There are no devices to share this folder with.": "Bu klasörün paylaşılacağı cihazlar yok.",
"There are no folders to share with this device.": "Bu cihazla paylaşılacak klasörler yok.",
"They are retried automatically and will be synced when the error is resolved.": "Otomatik olarak yeniden denenirler ve hata çözüldüğünde eşitleneceklerdir.",
"This Device": "Bu Cihaz",
"This can easily give hackers access to read and change any files on your computer.": "Bu, bilgisayar korsanlarının bilgisayarınızdaki herhangi bir dosyayı okumasına ve değiştirmesine kolayca erişim sağlayabilir.",

View File

@@ -66,6 +66,7 @@
"Deleted": "Видалене",
"Deselect All": "Зняти вибір з усіх",
"Deselect devices to stop sharing this folder with.": "Зніміть вибір з пристроїв, які не матимуть доступу до цієї директорії.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "Пристрій",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Пристрій \"{{name}}\" ({{device}} за адресою {{address}}) намагається під’єднатися. Додати новий пристрій?",
"Device ID": "ID пристрою",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "Швидкість має бути додатнім числом.",
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
"There are no devices to share this folder with.": "Відсутні пристрої, які мають доступ до цієї директорії.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "Вони будуть автоматично повторно синхронізовані, коли помилку буде усунено. ",
"This Device": "Локальний пристрій",
"This can easily give hackers access to read and change any files on your computer.": "Це легко може дати хакерам доступ до читання та зміни будь-яких файлів на вашому комп'ютері.",

View File

@@ -66,6 +66,7 @@
"Deleted": "已删除",
"Deselect All": "取消全选",
"Deselect devices to stop sharing this folder with.": "反选设备以停止共享此文件夹",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "设备",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "设备 \"{{name}}\"(位于 {{address}} 的 {{device}})请求连接。是否添加新设备?",
"Device ID": "设备 ID",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "传输速度限制为非负整数0 表示不限制)",
"The rescan interval must be a non-negative number of seconds.": "扫描间隔单位为秒,且不能为负数。",
"There are no devices to share this folder with.": "没有设备共享此文件夹",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "系统将会自动重试,当错误被解决时,它们将会被同步。",
"This Device": "当前设备",
"This can easily give hackers access to read and change any files on your computer.": "这会让骇客能够轻而易举地访问及修改您的文件。",

View File

@@ -66,6 +66,7 @@
"Deleted": "已刪除",
"Deselect All": "取消選取全部",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "裝置",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "裝置 \"{{name}}\" ({{device}} 位於 {{address}}) 想要連線。要增加新裝置嗎?",
"Device ID": "裝置識別碼",
@@ -336,6 +337,7 @@
"The rate limit must be a non-negative number (0: no limit)": "限制速率必須為非負的數字 (0: 不設限制)",
"The rescan interval must be a non-negative number of seconds.": "重新掃描間隔必須為一個非負數的秒數。",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "解決問題後,將會自動重試和同步。",
"This Device": "本機",
"This can easily give hackers access to read and change any files on your computer.": "這能給駭客輕易的來讀取、變更電腦中的任何檔案。",

View File

@@ -190,7 +190,7 @@
<!-- Panel: New Device -->
<div ng-repeat="pendingDevice in config.pendingDevices" class="row">
<div ng-repeat="(deviceID, pendingDevice) in pendingDevices" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
@@ -202,17 +202,17 @@
</div>
<div class="panel-body">
<p>
<span translate translate-value-device="{{ pendingDevice.deviceID }}" translate-value-address="{{ pendingDevice.address }}" translate-value-name="{{ pendingDevice.name }}">
<span translate translate-value-device="{{ deviceID }}" translate-value-address="{{ pendingDevice.address }}" translate-value-name="{{ pendingDevice.name }}">
Device "{%name%}" ({%device%} at {%address%}) wants to connect. Add new device?
</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(pendingDevice.deviceID, pendingDevice.name)">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(deviceID, pendingDevice.name)">
<span class="fas fa-plus"></span>&nbsp;<span translate>Add Device</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreDevice(pendingDevice)">
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreDevice(deviceID, pendingDevice)">
<span class="fas fa-times"></span>&nbsp;<span translate>Ignore</span>
</button>
</div>
@@ -222,8 +222,8 @@
</div>
<!-- Panel: New Folder -->
<div ng-repeat="device in config.devices">
<div ng-repeat="pendingFolder in device.pendingFolders" class="row reject">
<div ng-repeat="(folderID, pendingFolder) in pendingFolders">
<div ng-repeat="(deviceID, offeringDevice) in pendingFolder.offeredBy" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
@@ -231,32 +231,32 @@
<div class="panel-icon">
<span class="fas fa-folder"></span>
</div>
<span translate ng-if="!folders[pendingFolder.id]">New Folder</span>
<span translate ng-if="folders[pendingFolder.id]">Share Folder</span>
<span class="pull-right">{{ pendingFolder.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
<span translate ng-if="!folders[folderID]">New Folder</span>
<span translate ng-if="folders[folderID]">Share Folder</span>
<span class="pull-right">{{ offeringDevice.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
</h3>
</div>
<div class="panel-body">
<p>
<span ng-if="pendingFolder.label.length == 0" translate translate-value-device="{{ deviceName(devices[device.deviceID]) }}" translate-value-folder="{{ pendingFolder.id }}">
<span ng-if="offeringDevice.label.length == 0" translate translate-value-device="{{ deviceName(devices[deviceID]) }}" translate-value-folder="{{ folderID }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span ng-if="pendingFolder.label.length != 0" translate translate-value-device="{{ deviceName(devices[device.deviceID]) }}" translate-value-folder="{{ pendingFolder.id }}" translate-value-folderlabel="{{ pendingFolder.label }}">
<span ng-if="offeringDevice.label.length != 0" translate translate-value-device="{{ deviceName(devices[deviceID]) }}" translate-value-folder="{{ folderID }}" translate-value-folderlabel="{{ offeringDevice.label }}">
{%device%} wants to share folder "{%folderlabel%}" ({%folder%}).
</span>
<span translate ng-if="folders[pendingFolder.id]">Share this folder?</span>
<span translate ng-if="!folders[pendingFolder.id]">Add new folder?</span>
<span translate ng-if="folders[folderID]">Share this folder?</span>
<span translate ng-if="!folders[folderID]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(pendingFolder.id, pendingFolder.label, device.deviceID)" ng-if="!folders[pendingFolder.id]">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, offeringDevice.label, deviceID)" ng-if="!folders[folderID]">
<span class="fas fa-check"></span>&nbsp;<span translate>Add</span>
</button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(pendingFolder.id, device.deviceID)" ng-if="folders[pendingFolder.id]">
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(folderID, deviceID)" ng-if="folders[folderID]">
<span class="fas fa-check"></span>&nbsp;<span translate>Share</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreFolder(device.deviceID, pendingFolder)">
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreFolder(deviceID, folderID, offeringDevice)">
<span class="fas fa-times"></span>&nbsp;<span translate>Ignore</span>
</button>
</div>

View File

@@ -9,7 +9,7 @@
</h1>
<p class="text-center">
Build {{version.date | date:"yyyy-MM-dd"}}
<span ng-if="version.tags">({{version.tags.join(", ")}})</span>
<span ng-if="version.tags.length">({{version.tags.join(", ")}})</span>
<br />
Copyright &copy; 2014-{{version.date | date:"yyyy"}} the Syncthing Authors.
</p>
@@ -19,7 +19,7 @@
<h4 class="text-center" translate>The Syncthing Authors</h4>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tomasz Wilczyński, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, mv1005, otbutz, perewa, rubenbe, wangguoliang, xarx00, xjtdy888, 佛跳墙
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eric Lesiuta, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tomasz Wilczyński, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, mv1005, otbutz, perewa, rubenbe, wangguoliang, xarx00, xjtdy888, 佛跳墙
</div>
</div>
<hr />

View File

@@ -38,6 +38,8 @@ angular.module('syncthing.core')
$scope.upgradeInfo = null;
$scope.deviceStats = {};
$scope.folderStats = {};
$scope.pendingDevices = {};
$scope.pendingFolders = {};
$scope.progress = {};
$scope.version = {};
$scope.needed = {}
@@ -242,6 +244,34 @@ angular.module('syncthing.core')
}
});
$scope.$on(Events.DEVICE_REJECTED, function (event, arg) {
var pendingDevice = {
time: arg.time,
name: arg.data.name,
address: arg.data.address
};
console.log("rejected device:", arg.data.device, pendingDevice);
$scope.pendingDevices[arg.data.device] = pendingDevice;
});
$scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
var offeringDevice = {
time: arg.time,
label: arg.data.folderLabel
};
console.log("rejected folder", arg.data.folder, "from device:", arg.data.device, offeringDevice);
var pendingFolder = $scope.pendingFolders[arg.data.folder];
if (pendingFolder === undefined) {
pendingFolder = {
offeredBy: {}
};
}
pendingFolder.offeredBy[arg.data.device] = offeringDevice;
$scope.pendingFolders[arg.data.folder] = pendingFolder;
});
$scope.$on('ConfigLoaded', function () {
if ($scope.config.options.urAccepted === 0) {
// If usage reporting has been neither accepted nor declined,
@@ -391,6 +421,7 @@ angular.module('syncthing.core')
});
});
refreshCluster();
refreshNoAuthWarning();
setDefaultTheme();
@@ -455,6 +486,16 @@ angular.module('syncthing.core')
}
}
function refreshCluster() {
$http.get(urlbase + '/cluster/pending/devices').success(function (data) {
$scope.pendingDevices = data;
console.log("refreshCluster devices", data);
}).error($scope.emitHTTPError);
$http.get(urlbase + '/cluster/pending/folders').success(function (data) {
$scope.pendingFolders = data;
console.log("refreshCluster folders", data);
}).error($scope.emitHTTPError);
}
function refreshDiscoveryCache() {
$http.get(urlbase + '/system/discovery').success(function (data) {
@@ -1012,7 +1053,6 @@ angular.module('syncthing.core')
// loop through all devices
var deviceCount = 0;
var pendingFolders = 0;
for (var id in $scope.devices) {
var status = $scope.deviceStatus({
deviceID: id
@@ -1028,14 +1068,11 @@ angular.module('syncthing.core')
deviceCount--;
break;
}
pendingFolders += $scope.devices[id].pendingFolders.length;
deviceCount++;
}
// enumerate notifications
if ($scope.openNoAuth || !$scope.configInSync || $scope.errorList().length > 0 || !online || (
!isEmptyObject($scope.config) && ($scope.config.pendingDevices.length > 0 || pendingFolders > 0)
)) {
if ($scope.openNoAuth || !$scope.configInSync || $scope.errorList().length > 0 || !online || Object.keys($scope.pendingDevices).length > 0 || Object.keys($scope.pendingFolders).length > 0) {
notifyCount++;
}
@@ -1094,7 +1131,7 @@ angular.module('syncthing.core')
if (matches.length !== 1) {
return shortID;
}
return matches[0].name;
return $scope.friendlyNameFromID(matches[0]);
};
$scope.friendlyNameFromID = function (deviceID) {
@@ -1430,32 +1467,28 @@ angular.module('syncthing.core')
}
$scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
initShareEditing('device');
$scope.currentSharing.selected = {};
$scope.deviceFolders($scope.currentDevice).forEach(function (folder) {
$scope.currentSharing.selected[folder] = true;
$scope.deviceFolders($scope.currentDevice).forEach(function (folderID) {
$scope.currentSharing.shared.push($scope.folders[folderID]);
$scope.currentSharing.selected[folderID] = true;
});
$scope.currentSharing.unrelated = $scope.folderList().filter(function (n) {
return !$scope.currentSharing.selected[n.id];
});
$scope.deviceEditor.$setPristine();
$('#editDevice').modal();
};
$scope.selectAllFolders = function (state) {
var folders = $scope.folders;
for (var id in folders) {
$scope.currentSharing.selected[id] = !!state;
};
};
$scope.selectAllSharedFolders = function (state) {
var devices = $scope.currentSharing.shared;
for (var i = 0; i < devices.length; i++) {
$scope.currentSharing.selected[devices[i].deviceID] = !!state;
var folders = $scope.currentSharing.shared;
for (var i = 0; i < folders.length; i++) {
$scope.currentSharing.selected[folders[i].id] = !!state;
}
};
$scope.selectAllUnrelatedFolders = function (state) {
var devices = $scope.currentSharing.unrelated;
for (var i = 0; i < devices.length; i++) {
$scope.currentSharing.selected[devices[i].deviceID] = !!state;
var folders = $scope.currentSharing.unrelated;
for (var i = 0; i < folders.length; i++) {
$scope.currentSharing.selected[folders[i].id] = !!state;
}
};
@@ -1480,11 +1513,11 @@ angular.module('syncthing.core')
_addressesStr: 'dynamic',
compression: 'metadata',
introducer: false,
pendingFolders: [],
ignoredFolders: []
};
$scope.editingExisting = false;
initShareEditing('device');
$scope.currentSharing.unrelated = $scope.folderList();
$scope.deviceEditor.$setPristine();
$('#editDevice').modal();
});
@@ -1533,25 +1566,31 @@ angular.module('syncthing.core')
}
if (!found) {
// Add device to folder
$scope.folders[id].devices.push({
deviceID: deviceCfg.deviceID
});
}
} else {
// Remove device from folder
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
return n.deviceID !== deviceCfg.deviceID;
});
}
}
delete $scope.currentSharing;
$scope.config.folders = folderList($scope.folders);
$scope.saveConfig();
};
$scope.ignoreDevice = function (pendingDevice) {
pendingDevice = angular.copy(pendingDevice);
$scope.ignoreDevice = function (deviceID, pendingDevice) {
var ignoredDevice = angular.copy(pendingDevice);
ignoredDevice.deviceID = deviceID;
// Bump time
pendingDevice.time = (new Date()).toISOString();
$scope.config.remoteIgnoredDevices.push(pendingDevice);
ignoredDevice.time = (new Date()).toISOString();
$scope.config.remoteIgnoredDevices.push(ignoredDevice);
$scope.saveConfig();
};
@@ -1742,7 +1781,7 @@ angular.module('syncthing.core')
$scope.currentSharing.selected[n.deviceID] = true;
});
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
});
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
$scope.currentFolder.trashcanFileVersioning = true;
@@ -1773,7 +1812,10 @@ angular.module('syncthing.core')
$scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
$scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
$scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
// Zero is a valid, non-default value (disabled)
if ($scope.currentFolder.versioningCleanupIntervalS !== 0) {
$scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
}
// staggeredMaxAge can validly be zero, which we should not replace
// with the default value of 365. So only set the default if it's
@@ -1840,7 +1882,7 @@ angular.module('syncthing.core')
initShareEditing('folder');
$scope.currentSharing.selected[device] = true;
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
return n.deviceID !== $scope.myID;
});
$scope.ignores.text = '';
$scope.ignores.error = null;
@@ -1952,13 +1994,16 @@ angular.module('syncthing.core')
});
};
$scope.ignoreFolder = function (device, pendingFolder) {
pendingFolder = angular.copy(pendingFolder);
// Bump time
pendingFolder.time = (new Date()).toISOString();
$scope.ignoreFolder = function (device, folderID, offeringDevice) {
var ignoredFolder = {
id: folderID,
label: offeringDevice.label,
// Bump time
time: (new Date()).toISOString()
}
if (device in $scope.devices) {
$scope.devices[device].ignoredFolders.push(pendingFolder);
$scope.devices[device].ignoredFolders.push(ignoredFolder);
$scope.saveConfig();
}
};
@@ -2330,6 +2375,8 @@ angular.module('syncthing.core')
$scope.advanced = function () {
$scope.advancedConfig = angular.copy($scope.config);
$scope.advancedConfig.devices.sort(deviceCompare);
$scope.advancedConfig.folders.sort(folderCompare);
$('#advanced').modal('show');
};
@@ -2445,12 +2492,18 @@ angular.module('syncthing.core')
}[$scope.version.os] || $scope.version.os;
var arch = {
'386': '32 bit',
'amd64': '64 bit',
'arm': 'ARM',
'arm64': 'AArch64',
'ppc64': 'PowerPC',
'ppc64le': 'PowerPC (LE)'
'386': '32-bit Intel/AMD',
'amd64': '64-bit Intel/AMD',
'arm': '32-bit ARM',
'arm64': '64-bit ARM',
'ppc64': '64-bit PowerPC',
'ppc64le': '64-bit PowerPC (LE)',
'mips': '32-bit MIPS',
'mipsle': '32-bit MIPS (LE)',
'mips64': '64-bit MIPS',
'mips64le': '64-bit MIPS (LE)',
'riscv64': '64-bit RISC-V',
's390x': '64-bit z/Architecture',
}[$scope.version.arch] || $scope.version.arch;
return $scope.version.version + ', ' + os + ' (' + arch + ')';

View File

@@ -65,15 +65,38 @@
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label translate for="folders">Share Folders With Device</label>
<div class="form-group" ng-if="currentSharing.shared.length">
<label translate for="folders">Shared Folders</label>
<p class="help-block">
<span translate>Select the folders to share with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllFolders(false)" translate>Deselect All</a></small>
<span translate>Deselect folders to stop sharing with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllSharedFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllSharedFolders(false)" translate>Deselect All</a></small>
</p>
<div class="row">
<div class="col-md-4" ng-repeat="folder in folderList()">
<div class="col-md-4" ng-repeat="folder in currentSharing.shared">
<div class="checkbox">
<label ng-if="folder.label.length == 0">
<input type="checkbox" ng-model="currentSharing.selected[folder.id]" />&nbsp;{{folder.id}}
</label>
<label ng-if="folder.label.length != 0">
<input type="checkbox" ng-model="currentSharing.selected[folder.id]" />&nbsp; <span tooltip data-original-title="{{folder.id}}">{{folder.label}}</span>
</label>
</div>
</div>
</div>
</div>
<div class="form-group" ng-if="currentSharing.unrelated.length || folderList().length == 0">
<label translate for="folders">Unshared Folders</label>
<p class="help-block" ng-if="folderList().length > 0">
<span translate>Select additional folders to share with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllUnrelatedFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllUnrelatedFolders(false)" translate>Deselect All</a></small>
</p>
<p class="help-block" ng-if="folderList().length == 0">
<span translate>There are no folders to share with this device.</span>
</p>
<div class="row">
<div class="col-md-4" ng-repeat="folder in currentSharing.unrelated">
<div class="checkbox">
<label ng-if="folder.label.length == 0">
<input type="checkbox" ng-model="currentSharing.selected[folder.id]">&nbsp;{{folder.id}}

View File

@@ -190,7 +190,7 @@
<!-- Panel: New Device -->
<div ng-repeat="pendingDevice in config.pendingDevices" class="row">
<div ng-repeat="(deviceID, pendingDevice) in pendingDevices" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
@@ -202,17 +202,17 @@
</div>
<div class="panel-body">
<p>
<span translate translate-value-device="{{ pendingDevice.deviceID }}" translate-value-address="{{ pendingDevice.address }}" translate-value-name="{{ pendingDevice.name }}">
<span translate translate-value-device="{{ deviceID }}" translate-value-address="{{ pendingDevice.address }}" translate-value-name="{{ pendingDevice.name }}">
Device "{%name%}" ({%device%} at {%address%}) wants to connect. Add new device?
</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(pendingDevice.deviceID, pendingDevice.name)">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(deviceID, pendingDevice.name)">
<span class="fas fa-plus"></span>&nbsp;<span translate>Add Device</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreDevice(pendingDevice)">
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreDevice(deviceID, pendingDevice)">
<span class="fas fa-times"></span>&nbsp;<span translate>Ignore</span>
</button>
</div>
@@ -222,8 +222,8 @@
</div>
<!-- Panel: New Folder -->
<div ng-repeat="device in config.devices">
<div ng-repeat="pendingFolder in device.pendingFolders" class="row reject">
<div ng-repeat="(folderID, pendingFolder) in pendingFolders">
<div ng-repeat="(deviceID, offeringDevice) in pendingFolder.offeredBy" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
@@ -231,32 +231,32 @@
<div class="panel-icon">
<span class="fas fa-folder"></span>
</div>
<span translate ng-if="!folders[pendingFolder.id]">New Folder</span>
<span translate ng-if="folders[pendingFolder.id]">Share Folder</span>
<span class="pull-right">{{ pendingFolder.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
<span translate ng-if="!folders[folderID]">New Folder</span>
<span translate ng-if="folders[folderID]">Share Folder</span>
<span class="pull-right">{{ offeringDevice.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
</h3>
</div>
<div class="panel-body">
<p>
<span ng-if="pendingFolder.label.length == 0" translate translate-value-device="{{ deviceName(devices[device.deviceID]) }}" translate-value-folder="{{ pendingFolder.id }}">
<span ng-if="offeringDevice.label.length == 0" translate translate-value-device="{{ deviceName(devices[deviceID]) }}" translate-value-folder="{{ folderID }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span ng-if="pendingFolder.label.length != 0" translate translate-value-device="{{ deviceName(devices[device.deviceID]) }}" translate-value-folder="{{ pendingFolder.id }}" translate-value-folderlabel="{{ pendingFolder.label }}">
<span ng-if="offeringDevice.label.length != 0" translate translate-value-device="{{ deviceName(devices[deviceID]) }}" translate-value-folder="{{ folderID }}" translate-value-folderlabel="{{ offeringDevice.label }}">
{%device%} wants to share folder "{%folderlabel%}" ({%folder%}).
</span>
<span translate ng-if="folders[pendingFolder.id]">Share this folder?</span>
<span translate ng-if="!folders[pendingFolder.id]">Add new folder?</span>
<span translate ng-if="folders[folderID]">Share this folder?</span>
<span translate ng-if="!folders[folderID]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(pendingFolder.id, pendingFolder.label, device.deviceID)" ng-if="!folders[pendingFolder.id]">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, offeringDevice.label, deviceID)" ng-if="!folders[folderID]">
<span class="fas fa-check"></span>&nbsp;<span translate>Add</span>
</button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(pendingFolder.id, device.deviceID)" ng-if="folders[pendingFolder.id]">
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(folderID, deviceID)" ng-if="folders[folderID]">
<span class="fas fa-check"></span>&nbsp;<span translate>Share</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreFolder(device.deviceID, pendingFolder)">
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreFolder(deviceID, folderID, offeringDevice)">
<span class="fas fa-times"></span>&nbsp;<span translate>Ignore</span>
</button>
</div>

View File

@@ -38,6 +38,8 @@ angular.module('syncthing.core')
$scope.upgradeInfo = null;
$scope.deviceStats = {};
$scope.folderStats = {};
$scope.pendingDevices = {};
$scope.pendingFolders = {};
$scope.progress = {};
$scope.version = {};
$scope.needed = {}
@@ -242,6 +244,34 @@ angular.module('syncthing.core')
}
});
$scope.$on(Events.DEVICE_REJECTED, function (event, arg) {
var pendingDevice = {
time: arg.time,
name: arg.data.name,
address: arg.data.address
};
console.log("rejected device:", arg.data.device, pendingDevice);
$scope.pendingDevices[arg.data.device] = pendingDevice;
});
$scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
var offeringDevice = {
time: arg.time,
label: arg.data.folderLabel
};
console.log("rejected folder", arg.data.folder, "from device:", arg.data.device, offeringDevice);
var pendingFolder = $scope.pendingFolders[arg.data.folder];
if (pendingFolder === undefined) {
pendingFolder = {
offeredBy: {}
};
}
pendingFolder.offeredBy[arg.data.device] = offeringDevice;
$scope.pendingFolders[arg.data.folder] = pendingFolder;
});
$scope.$on('ConfigLoaded', function () {
if ($scope.config.options.urAccepted === 0) {
// If usage reporting has been neither accepted nor declined,
@@ -391,6 +421,7 @@ angular.module('syncthing.core')
});
});
refreshCluster();
refreshNoAuthWarning();
setDefaultTheme();
@@ -455,6 +486,16 @@ angular.module('syncthing.core')
}
}
function refreshCluster() {
$http.get(urlbase + '/cluster/pending/devices').success(function (data) {
$scope.pendingDevices = data;
console.log("refreshCluster devices", data);
}).error($scope.emitHTTPError);
$http.get(urlbase + '/cluster/pending/folders').success(function (data) {
$scope.pendingFolders = data;
console.log("refreshCluster folders", data);
}).error($scope.emitHTTPError);
}
function refreshDiscoveryCache() {
$http.get(urlbase + '/system/discovery').success(function (data) {
@@ -1016,7 +1057,6 @@ angular.module('syncthing.core')
// loop through all devices
var deviceCount = 0;
var pendingFolders = 0;
for (var id in $scope.devices) {
var status = $scope.deviceStatus({
deviceID: id
@@ -1032,14 +1072,11 @@ angular.module('syncthing.core')
deviceCount--;
break;
}
pendingFolders += $scope.devices[id].pendingFolders.length;
deviceCount++;
}
// enumerate notifications
if ($scope.openNoAuth || !$scope.configInSync || $scope.errorList().length > 0 || !online || (
!isEmptyObject($scope.config) && ($scope.config.pendingDevices.length > 0 || pendingFolders > 0)
)) {
if ($scope.openNoAuth || !$scope.configInSync || $scope.errorList().length > 0 || !online || Object.keys($scope.pendingDevices).length > 0 || Object.keys($scope.pendingFolders).length > 0) {
notifyCount++;
}
@@ -1098,7 +1135,7 @@ angular.module('syncthing.core')
if (matches.length !== 1) {
return shortID;
}
return matches[0].name;
return $scope.friendlyNameFromID(matches[0]);
};
$scope.friendlyNameFromID = function (deviceID) {
@@ -1433,40 +1470,36 @@ angular.module('syncthing.core')
}
}
$scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
initShareEditing('device');
for (var folderID in $scope.folders) {
var found = false;
for (var i = 0; i < $scope.folders[folderID].devices.length; i++) {
if ($scope.folders[folderID].devices[i].deviceID === deviceCfg.deviceID) {
found = true;
$scope.deviceFolders($scope.currentDevice).forEach(function (folderID) {
$scope.currentSharing.shared.push($scope.folders[folderID]);
$scope.currentSharing.selected[folderID] = true;
var folderdevices = $scope.folders[folderID].devices;
for (var i = 0; i < folderdevices.length; i++) {
if (folderdevices[i].deviceID === deviceCfg.deviceID) {
$scope.currentSharing.encryptionPasswords[folderID] = folderdevices[i].encryptionPassword;
break;
}
}
if (found) {
$scope.currentSharing.encryptionPasswords[folderID] = $scope.folders[folderID].devices[i].encryptionPassword;
$scope.currentSharing.shared.push($scope.folders[folderID]);
} else {
$scope.currentSharing.unrelated.push($scope.folders[folderID]);
}
$scope.currentSharing.selected[folderID] = found;
}
});
$scope.currentSharing.unrelated = $scope.folderList().filter(function (n) {
return !$scope.currentSharing.selected[n.id];
});
$scope.deviceEditor.$setPristine();
$('#editDevice').modal();
};
$scope.selectAllSharedFolders = function (state) {
var devices = $scope.currentSharing.shared;
for (var i = 0; i < devices.length; i++) {
$scope.currentSharing.selected[devices[i].deviceID] = !!state;
var folders = $scope.currentSharing.shared;
for (var i = 0; i < folders.length; i++) {
$scope.currentSharing.selected[folders[i].id] = !!state;
}
};
$scope.selectAllUnrelatedFolders = function (state) {
var devices = $scope.currentSharing.unrelated;
for (var i = 0; i < devices.length; i++) {
$scope.currentSharing.selected[devices[i].deviceID] = !!state;
var folders = $scope.currentSharing.unrelated;
for (var i = 0; i < folders.length; i++) {
$scope.currentSharing.selected[folders[i].id] = !!state;
}
};
@@ -1491,11 +1524,11 @@ angular.module('syncthing.core')
_addressesStr: 'dynamic',
compression: 'metadata',
introducer: false,
pendingFolders: [],
ignoredFolders: []
};
$scope.editingExisting = false;
initShareEditing('device');
$scope.currentSharing.unrelated = $scope.folderList();
$scope.deviceEditor.$setPristine();
$('#editDevice').modal();
});
@@ -1533,44 +1566,45 @@ angular.module('syncthing.core')
$scope.devices[deviceCfg.deviceID] = deviceCfg;
$scope.config.devices = deviceList($scope.devices);
$scope.currentSharing.shared.forEach(function (folder) {
var id = folder.id;
if ($scope.currentSharing.selected[id] !== true) {
for (var id in $scope.currentSharing.selected) {
if ($scope.currentSharing.selected[id]) {
var found = false;
for (i = 0; i < $scope.folders[id].devices.length; i++) {
if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
found = true;
// Update encryption pw
$scope.folders[id].devices[i].encryptionPassword = $scope.currentSharing.encryptionPasswords[id];
break;
}
}
if (!found) {
// Add device to folder
$scope.folders[id].devices.push({
deviceID: deviceCfg.deviceID,
encryptionPassword: $scope.currentSharing.encryptionPasswords[id]
});
}
} else {
// Remove device from folder
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
return n.deviceID !== deviceCfg.deviceID;
});
return;
}
// Update encryption pw
for (i = 0; i < $scope.folders[id].devices.length; i++) {
if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
$scope.folders[id].devices[i].encryptionPassword = $scope.currentSharing.encryptionPasswords[id];
break;
}
}
});
$scope.currentSharing.unrelated.forEach(function (folder) {
if ($scope.currentSharing.selected[folder.id] === true) {
$scope.folders[folder.id].devices.push({
deviceID: deviceCfg.deviceID,
encryptionPassword: $scope.currentSharing.encryptionPasswords[folder.id],
});
}
});
}
delete $scope.currentSharing;
$scope.config.folders = folderList($scope.folders);
$scope.saveConfig();
};
$scope.ignoreDevice = function (pendingDevice) {
pendingDevice = angular.copy(pendingDevice);
$scope.ignoreDevice = function (deviceID, pendingDevice) {
var ignoredDevice = angular.copy(pendingDevice);
ignoredDevice.deviceID = deviceID;
// Bump time
pendingDevice.time = (new Date()).toISOString();
$scope.config.remoteIgnoredDevices.push(pendingDevice);
ignoredDevice.time = (new Date()).toISOString();
$scope.config.remoteIgnoredDevices.push(ignoredDevice);
$scope.saveConfig();
};
@@ -1764,7 +1798,7 @@ angular.module('syncthing.core')
$scope.currentSharing.selected[n.deviceID] = true;
});
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
});
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
$scope.currentFolder.trashcanFileVersioning = true;
@@ -1795,7 +1829,10 @@ angular.module('syncthing.core')
$scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
$scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
$scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
// Zero is a valid, non-default value (disabled)
if ($scope.currentFolder.versioningCleanupIntervalS !== 0) {
$scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
}
// staggeredMaxAge can validly be zero, which we should not replace
// with the default value of 365. So only set the default if it's
@@ -1862,7 +1899,7 @@ angular.module('syncthing.core')
initShareEditing('folder');
$scope.currentSharing.selected[device] = true;
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
return n.deviceID !== $scope.myID;
});
$scope.ignores.text = '';
$scope.ignores.error = null;
@@ -1976,13 +2013,16 @@ angular.module('syncthing.core')
});
};
$scope.ignoreFolder = function (device, pendingFolder) {
pendingFolder = angular.copy(pendingFolder);
// Bump time
pendingFolder.time = (new Date()).toISOString();
$scope.ignoreFolder = function (device, folderID, offeringDevice) {
var ignoredFolder = {
id: folderID,
label: offeringDevice.label,
// Bump time
time: (new Date()).toISOString()
}
if (id in $scope.devices) {
$scope.devices[id].ignoredFolders.push(pendingFolder);
if (device in $scope.devices) {
$scope.devices[device].ignoredFolders.push(ignoredFolder);
$scope.saveConfig();
}
};
@@ -2493,12 +2533,18 @@ angular.module('syncthing.core')
}[$scope.version.os] || $scope.version.os;
var arch = {
'386': '32 bit',
'amd64': '64 bit',
'arm': 'ARM',
'arm64': 'AArch64',
'ppc64': 'PowerPC',
'ppc64le': 'PowerPC (LE)'
'386': '32-bit Intel/AMD',
'amd64': '64-bit Intel/AMD',
'arm': '32-bit ARM',
'arm64': '64-bit ARM',
'ppc64': '64-bit PowerPC',
'ppc64le': '64-bit PowerPC (LE)',
'mips': '32-bit MIPS',
'mipsle': '32-bit MIPS (LE)',
'mips64': '64-bit MIPS',
'mips64le': '64-bit MIPS (LE)',
'riscv64': '64-bit RISC-V',
's390x': '64-bit z/Architecture',
}[$scope.version.arch] || $scope.version.arch;
return $scope.version.version + ', ' + os + ' (' + arch + ')';
@@ -2594,9 +2640,9 @@ angular.module('syncthing.core')
id: '@',
label: '@',
folderType: '@',
untrusted: '=',
},
link: function(scope, elem, attrs) {
scope.untrusted = attrs.untrusted === 'true';
var plain = false;
scope.togglePasswordVisibility = function() {
scope.plain = !scope.plain;

View File

@@ -67,23 +67,26 @@
<div class="form-horizontal" ng-if="currentSharing.shared.length">
<label translate for="folders">Shared Folders</label>
<p class="help-block">
<span translate>Select the folders to share with this device.</span>&emsp;
<span translate>Deselect folders to stop sharing with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllSharedFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllSharedFolders(false)" translate>Deselect All</a></small>
</p>
<div class="form-group" ng-repeat="folder in currentSharing.shared">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{folder.id}}" label="{{folderLabel(folder.id)}}" folder-type="{{folder.type}}" untrusted="{{currentDevice.untrusted}}" />
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{folder.id}}" label="{{folderLabel(folder.id)}}" folder-type="{{folder.type}}" untrusted="currentDevice.untrusted" />
</div>
</div>
<div class="form-horizontal" ng-if="currentSharing.unrelated.length">
<label translate>Unshared Folders</label>
<p class="help-block">
<label translate for="folders">Unshared Folders</label>
<p class="help-block" ng-if="folderList().length > 0">
<span translate>Select additional folders to share with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllUnrelatedFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllUnrelatedFolders(false)" translate>Deselect All</a></small>
</p>
<p class="help-block" ng-if="folderList().length == 0">
<span translate>There are no folders to share with this device.</span>
</p>
<div class="form-group" ng-repeat="folder in currentSharing.unrelated">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{folder.id}}" label="{{folderLabel(folder.id)}}" folder-type="{{folder.type}}" untrusted="{{currentDevice.untrusted}}" />
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{folder.id}}" label="{{folderLabel(folder.id)}}" folder-type="{{folder.type}}" untrusted="currentDevice.untrusted" />
</div>
</div>
</div>

View File

@@ -54,7 +54,7 @@
<a href="#" ng-click="selectAllSharedDevices(false)" translate>Deselect All</a></small>
</p>
<div class="form-group" ng-repeat="device in currentSharing.shared">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{device.deviceID}}" label="{{deviceName(device)}}" folder-type="{{currentFolder.type}}" untrusted="{{device.untrusted}}" />
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{device.deviceID}}" label="{{deviceName(device)}}" folder-type="{{currentFolder.type}}" untrusted="device.untrusted" />
</div>
</div>
<div class="form-horizontal" ng-if="currentSharing.unrelated.length || otherDevices().length <= 0">
@@ -68,7 +68,7 @@
<span translate>There are no devices to share this folder with.</span>
</p>
<div class="form-group" ng-repeat="device in currentSharing.unrelated" ng-init="id = device.deviceID; folder = currentFolder">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{device.deviceID}}" label="{{deviceName(device)}}" folder-type="{{currentFolder.type}}" untrusted="{{device.untrusted}}" />
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{device.deviceID}}" label="{{deviceName(device)}}" folder-type="{{currentFolder.type}}" untrusted="device.untrusted" />
</div>
</div>
</div>

View File

@@ -49,11 +49,11 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/util"
)
// matches a bcrypt hash and not too much else
@@ -89,7 +89,7 @@ type service struct {
startedOnce chan struct{} // the service has started successfully at least once
startupErr error
listenerAddr net.Addr
exitChan chan *util.FatalErr
exitChan chan *svcutil.FatalErr
guiErrors logger.Recorder
systemLog logger.Recorder
@@ -123,7 +123,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
tlsDefaultCommonName: tlsDefaultCommonName,
configChanged: make(chan struct{}),
startedOnce: make(chan struct{}),
exitChan: make(chan *util.FatalErr, 1),
exitChan: make(chan *svcutil.FatalErr, 1),
}
}
@@ -237,36 +237,38 @@ func (s *service) Serve(ctx context.Context) error {
restMux := httprouter.New()
// The GET handlers
restMux.HandlerFunc(http.MethodGet, "/rest/db/completion", s.getDBCompletion) // [device] [folder]
restMux.HandlerFunc(http.MethodGet, "/rest/db/file", s.getDBFile) // folder file
restMux.HandlerFunc(http.MethodGet, "/rest/db/ignores", s.getDBIgnores) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/need", s.getDBNeed) // folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/localchanged", s.getDBLocalChanged) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/status", s.getDBStatus) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
restMux.HandlerFunc(http.MethodGet, "/rest/folder/versions", s.getFolderVersions) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/folder/errors", s.getFolderErrors) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/folder/pullerrors", s.getFolderErrors) // folder (deprecated)
restMux.HandlerFunc(http.MethodGet, "/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
restMux.HandlerFunc(http.MethodGet, "/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout]
restMux.HandlerFunc(http.MethodGet, "/rest/stats/device", s.getDeviceStats) // -
restMux.HandlerFunc(http.MethodGet, "/rest/stats/folder", s.getFolderStats) // -
restMux.HandlerFunc(http.MethodGet, "/rest/svc/deviceid", s.getDeviceID) // id
restMux.HandlerFunc(http.MethodGet, "/rest/svc/lang", s.getLang) // -
restMux.HandlerFunc(http.MethodGet, "/rest/svc/report", s.getReport) // -
restMux.HandlerFunc(http.MethodGet, "/rest/svc/random/string", s.getRandomString) // [length]
restMux.HandlerFunc(http.MethodGet, "/rest/system/browse", s.getSystemBrowse) // current
restMux.HandlerFunc(http.MethodGet, "/rest/system/connections", s.getSystemConnections) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/discovery", s.getSystemDiscovery) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/error", s.getSystemError) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/ping", s.restPing) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/status", s.getSystemStatus) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/upgrade", s.getSystemUpgrade) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/version", s.getSystemVersion) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/debug", s.getSystemDebug) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/log", s.getSystemLog) // [since]
restMux.HandlerFunc(http.MethodGet, "/rest/system/log.txt", s.getSystemLogTxt) // [since]
restMux.HandlerFunc(http.MethodGet, "/rest/cluster/pending/devices", s.getPendingDevices) // -
restMux.HandlerFunc(http.MethodGet, "/rest/cluster/pending/folders", s.getPendingFolders) // [device]
restMux.HandlerFunc(http.MethodGet, "/rest/db/completion", s.getDBCompletion) // [device] [folder]
restMux.HandlerFunc(http.MethodGet, "/rest/db/file", s.getDBFile) // folder file
restMux.HandlerFunc(http.MethodGet, "/rest/db/ignores", s.getDBIgnores) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/need", s.getDBNeed) // folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/localchanged", s.getDBLocalChanged) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/status", s.getDBStatus) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
restMux.HandlerFunc(http.MethodGet, "/rest/folder/versions", s.getFolderVersions) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/folder/errors", s.getFolderErrors) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/folder/pullerrors", s.getFolderErrors) // folder (deprecated)
restMux.HandlerFunc(http.MethodGet, "/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
restMux.HandlerFunc(http.MethodGet, "/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout]
restMux.HandlerFunc(http.MethodGet, "/rest/stats/device", s.getDeviceStats) // -
restMux.HandlerFunc(http.MethodGet, "/rest/stats/folder", s.getFolderStats) // -
restMux.HandlerFunc(http.MethodGet, "/rest/svc/deviceid", s.getDeviceID) // id
restMux.HandlerFunc(http.MethodGet, "/rest/svc/lang", s.getLang) // -
restMux.HandlerFunc(http.MethodGet, "/rest/svc/report", s.getReport) // -
restMux.HandlerFunc(http.MethodGet, "/rest/svc/random/string", s.getRandomString) // [length]
restMux.HandlerFunc(http.MethodGet, "/rest/system/browse", s.getSystemBrowse) // current
restMux.HandlerFunc(http.MethodGet, "/rest/system/connections", s.getSystemConnections) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/discovery", s.getSystemDiscovery) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/error", s.getSystemError) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/ping", s.restPing) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/status", s.getSystemStatus) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/upgrade", s.getSystemUpgrade) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/version", s.getSystemVersion) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/debug", s.getSystemDebug) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/log", s.getSystemLog) // [since]
restMux.HandlerFunc(http.MethodGet, "/rest/system/log.txt", s.getSystemLogTxt) // [since]
// The POST handlers
restMux.HandlerFunc(http.MethodPost, "/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
@@ -472,7 +474,7 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
return true
}
func (s *service) fatal(err *util.FatalErr) {
func (s *service) fatal(err *svcutil.FatalErr) {
// s.exitChan is 1-buffered and whoever is first gets handled.
select {
case s.exitChan <- err:
@@ -620,6 +622,33 @@ func (s *service) whenDebugging(h http.Handler) http.Handler {
})
}
func (s *service) getPendingDevices(w http.ResponseWriter, r *http.Request) {
devices, err := s.model.PendingDevices()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sendJSON(w, devices)
}
func (s *service) getPendingFolders(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
device := qs.Get("device")
deviceID, err := protocol.DeviceIDFromString(device)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
folders, err := s.model.PendingFolders(deviceID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sendJSON(w, folders)
}
func (s *service) restPing(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string]string{"ping": "pong"})
}
@@ -643,7 +672,7 @@ func (s *service) getSystemVersion(w http.ResponseWriter, r *http.Request) {
"isCandidate": build.IsCandidate,
"isRelease": build.IsRelease,
"date": build.Date,
"tags": build.Tags,
"tags": build.TagsList(),
"stamp": build.Stamp,
"user": build.User,
})
@@ -886,9 +915,9 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
func (s *service) postSystemRestart(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "restarting"}`, w)
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("restart initiated by rest API"),
Status: util.ExitRestart,
Status: svcutil.ExitRestart,
})
}
@@ -915,17 +944,17 @@ func (s *service) postSystemReset(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
}
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("restart after db reset initiated by rest API"),
Status: util.ExitRestart,
Status: svcutil.ExitRestart,
})
}
func (s *service) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "shutting down"}`, w)
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("shutdown initiated by rest API"),
Status: util.ExitSuccess,
Status: svcutil.ExitSuccess,
})
}
@@ -1361,9 +1390,9 @@ func (s *service) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
}
s.flushResponse(`{"ok": "restarting"}`, w)
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("exit after upgrade initiated by rest API"),
Status: util.ExitUpgrade,
Status: svcutil.ExitUpgrade,
})
}
}

View File

@@ -32,10 +32,10 @@ import (
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture/v4"
)
@@ -119,7 +119,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
defer os.Remove(token)
srv.started = make(chan string)
sup := suture.New("test", util.Spec())
sup := suture.New("test", svcutil.SpecWithDebugLogger(l))
sup.Add(srv)
ctx, cancel := context.WithCancel(context.Background())
sup.ServeBackground(ctx)

View File

@@ -130,6 +130,10 @@ func (c *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
return false
}
func (c *mockedConfig) IgnoredDevices() []config.ObservedDevice {
return nil
}
func (c *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
return false
}

View File

@@ -11,7 +11,6 @@ import (
"net"
"time"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
@@ -50,11 +49,15 @@ func (m *mockedModel) FolderProgressBytesCompleted(_ string) int64 {
return 0
}
func (m *mockedModel) NumConnections() int {
return 0
}
func (m *mockedModel) ConnectionStats() map[string]interface{} {
return nil
}
func (m *mockedModel) DeviceStatistics() (map[string]stats.DeviceStatistics, error) {
func (m *mockedModel) DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error) {
return nil, nil
}
@@ -114,7 +117,7 @@ func (m *mockedModel) ScanFolderSubdirs(folder string, subs []string) error {
func (m *mockedModel) BringToFront(folder, file string) {}
func (m *mockedModel) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) {
func (m *mockedModel) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) {
return nil, false
}
@@ -125,6 +128,14 @@ func (m *mockedModel) State(folder string) (string, time.Time, error) {
func (m *mockedModel) UsageReportingStats(r *contract.Report, version int, preview bool) {
}
func (m *mockedModel) PendingDevices() (map[protocol.DeviceID]db.ObservedDevice, error) {
return nil, nil
}
func (m *mockedModel) PendingFolders(device protocol.DeviceID) (map[string]db.PendingFolder, error) {
return nil, nil
}
func (m *mockedModel) FolderErrors(folder string) ([]model.FileError, error) {
return nil, nil
}
@@ -157,7 +168,7 @@ func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string
return nil
}
func (m *mockedModel) AddConnection(conn connections.Connection, hello protocol.Hello) {}
func (m *mockedModel) AddConnection(conn protocol.Connection, hello protocol.Hello) {}
func (m *mockedModel) OnHello(protocol.DeviceID, net.Addr, protocol.Hello) error {
return nil

View File

@@ -14,7 +14,7 @@ import (
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
type recv struct {
@@ -33,8 +33,8 @@ type Interface interface {
type cast struct {
*suture.Supervisor
name string
reader util.ServiceWithError
writer util.ServiceWithError
reader svcutil.ServiceWithError
writer svcutil.ServiceWithError
outbox chan recv
inbox chan []byte
stopped chan struct{}
@@ -44,16 +44,13 @@ type cast struct {
// 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 {
spec := util.Spec()
// Only log restarts in debug mode.
spec := svcutil.SpecWithDebugLogger(l)
// 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...
spec.FailureThreshold = 2
spec.FailureBackoff = 60 * time.Second
// Only log restarts in debug mode.
spec.EventHook = func(e suture.Event) {
l.Debugln(e)
}
c := &cast{
Supervisor: suture.New(name, spec),
name: name,
@@ -61,7 +58,7 @@ func newCast(name string) *cast {
outbox: make(chan recv, 16),
stopped: make(chan struct{}),
}
util.OnSupervisorDone(c.Supervisor, func() { close(c.stopped) })
svcutil.OnSupervisorDone(c.Supervisor, func() { close(c.stopped) })
return c
}
@@ -75,8 +72,8 @@ func (c *cast) addWriter(svc func(ctx context.Context) error) {
c.Add(c.writer)
}
func (c *cast) createService(svc func(context.Context) error, suffix string) util.ServiceWithError {
return util.AsService(svc, fmt.Sprintf("%s/%s", c, suffix))
func (c *cast) createService(svc func(context.Context) error, suffix string) svcutil.ServiceWithError {
return svcutil.AsService(svc, fmt.Sprintf("%s/%s", c, suffix))
}
func (c *cast) String() string {

View File

@@ -87,6 +87,13 @@ func LongVersionFor(program string) string {
date := Date.UTC().Format("2006-01-02 15:04:05 MST")
v := fmt.Sprintf(`%s %s "%s" (%s %s-%s) %s@%s %s`, program, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
if tags := TagsList(); len(tags) > 0 {
v = fmt.Sprintf("%s [%s]", v, strings.Join(tags, ", "))
}
return v
}
func TagsList() []string {
tags := strings.Split(Tags, ",")
if len(tags) == 1 && tags[0] == "" {
tags = tags[:0]
@@ -96,9 +103,7 @@ func LongVersionFor(program string) string {
tags = append(tags, strings.ToLower(envVar))
}
}
if len(tags) > 0 {
sort.Strings(tags)
v = fmt.Sprintf("%s [%s]", v, strings.Join(tags, ", "))
}
return v
sort.Strings(tags)
return tags
}

View File

@@ -30,7 +30,7 @@ import (
const (
OldestHandledVersion = 10
CurrentVersion = 32
CurrentVersion = 33
MaxRescanIntervalS = 365 * 24 * 60 * 60
)
@@ -204,9 +204,6 @@ func (cfg Configuration) Copy() Configuration {
newCfg.IgnoredDevices = make([]ObservedDevice, len(cfg.IgnoredDevices))
copy(newCfg.IgnoredDevices, cfg.IgnoredDevices)
newCfg.PendingDevices = make([]ObservedDevice, len(cfg.PendingDevices))
copy(newCfg.PendingDevices, cfg.PendingDevices)
return newCfg
}
@@ -235,9 +232,7 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
guiPWIsSet := cfg.GUI.User != "" && cfg.GUI.Password != ""
cfg.Options.prepare(guiPWIsSet)
ignoredDevices := cfg.prepareIgnoredDevices(existingDevices)
cfg.preparePendingDevices(existingDevices, ignoredDevices)
cfg.prepareIgnoredDevices(existingDevices)
cfg.removeDeprecatedProtocols()
@@ -354,31 +349,6 @@ func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.Dev
return ignoredDevices
}
func (cfg *Configuration) preparePendingDevices(existingDevices, ignoredDevices map[protocol.DeviceID]bool) {
// The list of pending devices should not contain devices that were added manually, nor should it contain
// ignored devices.
// Sort by time, so that in case of duplicates latest "time" is used.
sort.Slice(cfg.PendingDevices, func(i, j int) bool {
return cfg.PendingDevices[i].Time.Before(cfg.PendingDevices[j].Time)
})
newPendingDevices := cfg.PendingDevices[:0]
nextPendingDevice:
for _, pendingDevice := range cfg.PendingDevices {
if !existingDevices[pendingDevice.ID] && !ignoredDevices[pendingDevice.ID] {
// Deduplicate
for _, existingPendingDevice := range newPendingDevices {
if existingPendingDevice.ID == pendingDevice.ID {
continue nextPendingDevice
}
}
newPendingDevices = append(newPendingDevices, pendingDevice)
}
}
cfg.PendingDevices = newPendingDevices
}
func (cfg *Configuration) removeDeprecatedProtocols() {
// Deprecated protocols are removed from the list of listeners and
// device addresses. So far just kcp*.

View File

@@ -24,14 +24,14 @@ var _ = math.Inf
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type Configuration struct {
Version int `protobuf:"varint,1,opt,name=version,proto3,casttype=int" json:"version" xml:"version,attr"`
Folders []FolderConfiguration `protobuf:"bytes,2,rep,name=folders,proto3" json:"folders" xml:"folder"`
Devices []DeviceConfiguration `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices" xml:"device"`
GUI GUIConfiguration `protobuf:"bytes,4,opt,name=gui,proto3" json:"gui" xml:"gui"`
LDAP LDAPConfiguration `protobuf:"bytes,5,opt,name=ldap,proto3" json:"ldap" xml:"ldap"`
Options OptionsConfiguration `protobuf:"bytes,6,opt,name=options,proto3" json:"options" xml:"options"`
IgnoredDevices []ObservedDevice `protobuf:"bytes,7,rep,name=ignored_devices,json=ignoredDevices,proto3" json:"remoteIgnoredDevices" xml:"remoteIgnoredDevice"`
PendingDevices []ObservedDevice `protobuf:"bytes,8,rep,name=pending_devices,json=pendingDevices,proto3" json:"pendingDevices" xml:"pendingDevice"`
Version int `protobuf:"varint,1,opt,name=version,proto3,casttype=int" json:"version" xml:"version,attr"`
Folders []FolderConfiguration `protobuf:"bytes,2,rep,name=folders,proto3" json:"folders" xml:"folder"`
Devices []DeviceConfiguration `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices" xml:"device"`
GUI GUIConfiguration `protobuf:"bytes,4,opt,name=gui,proto3" json:"gui" xml:"gui"`
LDAP LDAPConfiguration `protobuf:"bytes,5,opt,name=ldap,proto3" json:"ldap" xml:"ldap"`
Options OptionsConfiguration `protobuf:"bytes,6,opt,name=options,proto3" json:"options" xml:"options"`
IgnoredDevices []ObservedDevice `protobuf:"bytes,7,rep,name=ignored_devices,json=ignoredDevices,proto3" json:"remoteIgnoredDevices" xml:"remoteIgnoredDevice"`
DeprecatedPendingDevices []ObservedDevice `protobuf:"bytes,8,rep,name=pending_devices,json=pendingDevices,proto3" json:"-" xml:"pendingDevice,omitempty"` // Deprecated: Do not use.
}
func (m *Configuration) Reset() { *m = Configuration{} }
@@ -74,42 +74,43 @@ func init() {
func init() { proto.RegisterFile("lib/config/config.proto", fileDescriptor_baadf209193dc627) }
var fileDescriptor_baadf209193dc627 = []byte{
// 547 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0x4f, 0x8b, 0xd3, 0x40,
0x18, 0xc6, 0x13, 0xbb, 0xdb, 0xba, 0xd9, 0x7f, 0x90, 0x15, 0x4d, 0x55, 0x32, 0x75, 0xa8, 0x52,
0x45, 0xbb, 0xb0, 0x5e, 0xc4, 0x9b, 0xb5, 0xb8, 0x14, 0x05, 0x65, 0x60, 0x45, 0xbd, 0x48, 0xdb,
0xcc, 0xa6, 0x03, 0xed, 0x4c, 0x49, 0xd2, 0xb2, 0x7e, 0x0b, 0xf1, 0x13, 0x78, 0xf5, 0x1b, 0xf8,
0x11, 0x7a, 0x6b, 0x8f, 0x9e, 0x06, 0xb6, 0xbd, 0xf5, 0x98, 0xa3, 0x27, 0x99, 0x7f, 0xdd, 0x44,
0xa2, 0xa7, 0xe6, 0x7d, 0x9f, 0xe7, 0xf9, 0xbd, 0x2f, 0x6f, 0x13, 0xe7, 0xd6, 0x90, 0xf4, 0x8e,
0xfb, 0x8c, 0x9e, 0x93, 0x50, 0xff, 0x34, 0xc7, 0x11, 0x4b, 0x98, 0x5b, 0x56, 0xd5, 0xed, 0x7a,
0xc6, 0x70, 0xce, 0x86, 0x01, 0x8e, 0x54, 0x31, 0x89, 0xba, 0x09, 0x61, 0x54, 0xb9, 0x73, 0xae,
0x00, 0x4f, 0x49, 0x1f, 0x17, 0xb9, 0xee, 0x65, 0x5c, 0xe1, 0x84, 0x14, 0x59, 0x60, 0xc6, 0x32,
0x0c, 0xba, 0xe3, 0x22, 0xcf, 0xfd, 0x8c, 0x87, 0x8d, 0x85, 0x10, 0x17, 0xd9, 0xaa, 0x59, 0x5b,
0x2f, 0xc6, 0xd1, 0x14, 0x07, 0x5a, 0xda, 0xc1, 0x17, 0x89, 0x7a, 0x84, 0x3f, 0xcb, 0xce, 0xfe,
0xcb, 0x6c, 0xda, 0x45, 0x4e, 0x65, 0x8a, 0xa3, 0x98, 0x30, 0xea, 0xd9, 0x35, 0xbb, 0xb1, 0xdd,
0x7a, 0xb6, 0xe6, 0xc0, 0xb4, 0x52, 0x0e, 0xdc, 0x8b, 0xd1, 0xf0, 0x39, 0xd4, 0xf5, 0xe3, 0x6e,
0x92, 0x44, 0xf0, 0x37, 0x07, 0x25, 0x42, 0x93, 0xf5, 0xbc, 0xbe, 0x97, 0xed, 0x23, 0x93, 0x72,
0xdf, 0x3b, 0x15, 0x75, 0xbc, 0xd8, 0xbb, 0x56, 0x2b, 0x35, 0x76, 0x4f, 0xee, 0x34, 0xf5, 0xb5,
0x5f, 0xc9, 0x76, 0x6e, 0x83, 0x16, 0x98, 0x71, 0x60, 0x89, 0xa1, 0x3a, 0x93, 0x72, 0xb0, 0x27,
0x87, 0xaa, 0x1a, 0x22, 0x23, 0x08, 0xae, 0x3a, 0x77, 0xec, 0x95, 0xf2, 0xdc, 0xb6, 0x6c, 0xff,
0x83, 0xab, 0x33, 0x1b, 0xae, 0xaa, 0x21, 0x32, 0x82, 0x8b, 0x9c, 0x52, 0x38, 0x21, 0xde, 0x56,
0xcd, 0x6e, 0xec, 0x9e, 0x78, 0x86, 0x79, 0x7a, 0xd6, 0xc9, 0x03, 0x1f, 0x08, 0xe0, 0x92, 0x83,
0xd2, 0xe9, 0x59, 0x67, 0xcd, 0x81, 0xc8, 0xa4, 0x1c, 0xec, 0x48, 0x66, 0x38, 0x21, 0xf0, 0xdb,
0xa2, 0x2e, 0x24, 0x24, 0x04, 0xf7, 0xa3, 0xb3, 0x25, 0xfe, 0x51, 0x6f, 0x5b, 0x42, 0xab, 0x06,
0xfa, 0xa6, 0xfd, 0xe2, 0x5d, 0x9e, 0xfa, 0x48, 0x53, 0xb7, 0x84, 0xb4, 0xe6, 0x40, 0xc6, 0x52,
0x0e, 0x1c, 0xc9, 0x15, 0x85, 0x00, 0x4b, 0x15, 0x49, 0xcd, 0xfd, 0xe0, 0x54, 0xf4, 0x8b, 0xe0,
0x95, 0x25, 0xfd, 0xae, 0xa1, 0xbf, 0x55, 0xed, 0xfc, 0x80, 0x9a, 0xb9, 0x83, 0x0e, 0xa5, 0x1c,
0xec, 0x4b, 0xb6, 0xae, 0x21, 0x32, 0x8a, 0xfb, 0xc3, 0x76, 0x0e, 0x49, 0x48, 0x59, 0x84, 0x83,
0xcf, 0xe6, 0xd2, 0x15, 0x79, 0xe9, 0x9b, 0x9b, 0x11, 0xfa, 0xdd, 0x52, 0x17, 0x6f, 0x0d, 0x34,
0xfc, 0x46, 0x84, 0x47, 0x2c, 0xc1, 0x1d, 0x15, 0x6e, 0x6f, 0x2e, 0x5e, 0x95, 0x93, 0x0a, 0x44,
0xb8, 0x9e, 0xd7, 0x8f, 0x0a, 0xfa, 0xe9, 0xbc, 0x5e, 0xc8, 0x42, 0x07, 0x24, 0x57, 0xbb, 0xd4,
0x39, 0x1c, 0x63, 0x1a, 0x10, 0x1a, 0x6e, 0x56, 0xbd, 0xfe, 0xdf, 0x55, 0x9f, 0xe8, 0x55, 0x0f,
0x74, 0xec, 0x6a, 0xc9, 0x23, 0xb9, 0x64, 0xae, 0x0d, 0xd1, 0x5f, 0xb6, 0xd6, 0xeb, 0xd9, 0xa5,
0x6f, 0x2d, 0x2e, 0x7d, 0x6b, 0xb6, 0xf4, 0xed, 0xc5, 0xd2, 0xb7, 0xbf, 0xae, 0x7c, 0xeb, 0xfb,
0xca, 0xb7, 0x17, 0x2b, 0xdf, 0xfa, 0xb5, 0xf2, 0xad, 0x4f, 0x0f, 0x43, 0x92, 0x0c, 0x26, 0xbd,
0x66, 0x9f, 0x8d, 0x8e, 0xe3, 0x2f, 0xb4, 0x9f, 0x0c, 0x08, 0x0d, 0x33, 0x4f, 0x57, 0x5f, 0x68,
0xaf, 0x2c, 0x3f, 0xc7, 0xa7, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xcf, 0x98, 0x86, 0x91,
0x04, 0x00, 0x00,
// 575 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0x4d, 0x8b, 0xd3, 0x5e,
0x14, 0xc6, 0x93, 0x7f, 0x3b, 0xed, 0x7f, 0x32, 0x6f, 0x10, 0x45, 0x53, 0x5f, 0x72, 0x6b, 0xa8,
0x52, 0x65, 0xec, 0xc0, 0xb8, 0x11, 0x77, 0xd6, 0xe2, 0x58, 0x14, 0x1c, 0x02, 0x23, 0xea, 0x46,
0xda, 0xe6, 0x4e, 0x7a, 0xa1, 0xcd, 0x0d, 0xc9, 0x4d, 0x99, 0xf9, 0x08, 0xee, 0xc4, 0x4f, 0xe0,
0xd6, 0x6f, 0xd2, 0x5d, 0xbb, 0x70, 0xe1, 0xea, 0xc2, 0xb4, 0xbb, 0x2c, 0xb3, 0x74, 0x25, 0xf7,
0xad, 0x26, 0x10, 0x5d, 0x35, 0xe7, 0x3c, 0xcf, 0xf9, 0x9d, 0xc3, 0xd3, 0xc4, 0xb8, 0x39, 0x41,
0xc3, 0xa3, 0x11, 0x0e, 0xce, 0x91, 0x2f, 0x7f, 0x3a, 0x61, 0x84, 0x09, 0x36, 0x6b, 0xa2, 0xba,
0xd5, 0xca, 0x19, 0xce, 0xf1, 0xc4, 0x83, 0x91, 0x28, 0x92, 0x68, 0x40, 0x10, 0x0e, 0x84, 0xbb,
0xe0, 0xf2, 0xe0, 0x0c, 0x8d, 0x60, 0x99, 0xeb, 0x5e, 0xce, 0xe5, 0x27, 0xa8, 0xcc, 0xe2, 0xe4,
0x2c, 0x13, 0x6f, 0x10, 0x96, 0x79, 0xee, 0xe7, 0x3c, 0x38, 0x64, 0x42, 0x5c, 0x66, 0x6b, 0xe4,
0x6d, 0xc3, 0x18, 0x46, 0x33, 0xe8, 0x49, 0x69, 0x1b, 0x5e, 0x10, 0xf1, 0xe8, 0xfc, 0xa8, 0x19,
0x7b, 0x2f, 0xf2, 0xd3, 0xa6, 0x6b, 0xd4, 0x67, 0x30, 0x8a, 0x11, 0x0e, 0x2c, 0xbd, 0xa9, 0xb7,
0xb7, 0xba, 0x4f, 0x53, 0x0a, 0x54, 0x2b, 0xa3, 0xc0, 0xbc, 0x98, 0x4e, 0x9e, 0x39, 0xb2, 0x3e,
0x1c, 0x10, 0x12, 0x39, 0xbf, 0x28, 0xa8, 0xa0, 0x80, 0xa4, 0x8b, 0xd6, 0x6e, 0xbe, 0xef, 0xaa,
0x29, 0xf3, 0x9d, 0x51, 0x17, 0xe1, 0xc5, 0xd6, 0x7f, 0xcd, 0x4a, 0x7b, 0xe7, 0xf8, 0x76, 0x47,
0xa6, 0xfd, 0x92, 0xb7, 0x0b, 0x17, 0x74, 0xc1, 0x9c, 0x02, 0x8d, 0x2d, 0x95, 0x33, 0x19, 0x05,
0xbb, 0x7c, 0xa9, 0xa8, 0x1d, 0x57, 0x09, 0x8c, 0x2b, 0xe2, 0x8e, 0xad, 0x4a, 0x91, 0xdb, 0xe3,
0xed, 0xbf, 0x70, 0xe5, 0xcc, 0x86, 0x2b, 0x6a, 0xc7, 0x55, 0x82, 0xe9, 0x1a, 0x15, 0x3f, 0x41,
0x56, 0xb5, 0xa9, 0xb7, 0x77, 0x8e, 0x2d, 0xc5, 0x3c, 0x39, 0xeb, 0x17, 0x81, 0x0f, 0x18, 0x70,
0x45, 0x41, 0xe5, 0xe4, 0xac, 0x9f, 0x52, 0xc0, 0x66, 0x32, 0x0a, 0xb6, 0x39, 0xd3, 0x4f, 0x90,
0xf3, 0x75, 0xd9, 0x62, 0x92, 0xcb, 0x04, 0xf3, 0x83, 0x51, 0x65, 0xff, 0xa8, 0xb5, 0xc5, 0xa1,
0x0d, 0x05, 0x7d, 0xd3, 0x7b, 0x7e, 0x5a, 0xa4, 0x3e, 0x92, 0xd4, 0x2a, 0x93, 0x52, 0x0a, 0xf8,
0x58, 0x46, 0x81, 0xc1, 0xb9, 0xac, 0x60, 0x60, 0xae, 0xba, 0x5c, 0x33, 0xdf, 0x1b, 0x75, 0xf9,
0x22, 0x58, 0x35, 0x4e, 0xbf, 0xa3, 0xe8, 0x6f, 0x45, 0xbb, 0xb8, 0xa0, 0xa9, 0x72, 0x90, 0x43,
0x19, 0x05, 0x7b, 0x9c, 0x2d, 0x6b, 0xc7, 0x55, 0x8a, 0xf9, 0x5d, 0x37, 0x0e, 0x90, 0x1f, 0xe0,
0x08, 0x7a, 0x9f, 0x54, 0xd2, 0x75, 0x9e, 0xf4, 0x8d, 0xcd, 0x0a, 0xf9, 0x6e, 0x89, 0xc4, 0xbb,
0x63, 0x09, 0xbf, 0x1e, 0xc1, 0x29, 0x26, 0xb0, 0x2f, 0x86, 0x7b, 0x9b, 0xc4, 0x1b, 0x7c, 0x53,
0x89, 0xe8, 0xa4, 0x8b, 0xd6, 0xb5, 0x92, 0x7e, 0xb6, 0x68, 0x95, 0xb2, 0xdc, 0x7d, 0x54, 0xa8,
0xcd, 0xcf, 0xba, 0x71, 0x10, 0xc2, 0xc0, 0x43, 0x81, 0xbf, 0xb9, 0xf5, 0xff, 0x7f, 0xde, 0xfa,
0x4a, 0x26, 0x6d, 0xf5, 0x60, 0x18, 0xc1, 0xd1, 0x80, 0x40, 0xef, 0x54, 0x00, 0x24, 0x33, 0xa5,
0x40, 0x7f, 0x9c, 0x51, 0x70, 0x97, 0x1f, 0x1d, 0xe6, 0xb5, 0x43, 0x3c, 0x45, 0x04, 0x4e, 0x43,
0x72, 0xe9, 0x58, 0xba, 0xbb, 0x5f, 0xd0, 0xe2, 0xee, 0xeb, 0xf9, 0x95, 0xad, 0x2d, 0xaf, 0x6c,
0x6d, 0xbe, 0xb2, 0xf5, 0xe5, 0xca, 0xd6, 0xbf, 0xac, 0x6d, 0xed, 0xdb, 0xda, 0xd6, 0x97, 0x6b,
0x5b, 0xfb, 0xb9, 0xb6, 0xb5, 0x8f, 0x0f, 0x7d, 0x44, 0xc6, 0xc9, 0xb0, 0x33, 0xc2, 0xd3, 0xa3,
0xf8, 0x32, 0x18, 0x91, 0x31, 0x0a, 0xfc, 0xdc, 0xd3, 0x9f, 0xaf, 0x77, 0x58, 0xe3, 0x9f, 0xea,
0x93, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x97, 0x39, 0xe5, 0x72, 0xad, 0x04, 0x00, 0x00,
}
func (m *Configuration) Marshal() (dAtA []byte, err error) {
@@ -132,10 +133,10 @@ func (m *Configuration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.PendingDevices) > 0 {
for iNdEx := len(m.PendingDevices) - 1; iNdEx >= 0; iNdEx-- {
if len(m.DeprecatedPendingDevices) > 0 {
for iNdEx := len(m.DeprecatedPendingDevices) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.PendingDevices[iNdEx].MarshalToSizedBuffer(dAtA[:i])
size, err := m.DeprecatedPendingDevices[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
@@ -270,8 +271,8 @@ func (m *Configuration) ProtoSize() (n int) {
n += 1 + l + sovConfig(uint64(l))
}
}
if len(m.PendingDevices) > 0 {
for _, e := range m.PendingDevices {
if len(m.DeprecatedPendingDevices) > 0 {
for _, e := range m.DeprecatedPendingDevices {
l = e.ProtoSize()
n += 1 + l + sovConfig(uint64(l))
}
@@ -536,7 +537,7 @@ func (m *Configuration) Unmarshal(dAtA []byte) error {
iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PendingDevices", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedPendingDevices", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
@@ -563,8 +564,8 @@ func (m *Configuration) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.PendingDevices = append(m.PendingDevices, ObservedDevice{})
if err := m.PendingDevices[len(m.PendingDevices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
m.DeprecatedPendingDevices = append(m.DeprecatedPendingDevices, ObservedDevice{})
if err := m.DeprecatedPendingDevices[len(m.DeprecatedPendingDevices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex

View File

@@ -142,7 +142,6 @@ func TestDeviceConfig(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
{
DeviceID: device4,
@@ -151,7 +150,6 @@ func TestDeviceConfig(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
}
expectedDeviceIDs := []protocol.DeviceID{device1, device4}
@@ -248,21 +246,18 @@ func TestDeviceAddressesDynamic(t *testing.T) {
Addresses: []string{"dynamic"},
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device2: {
DeviceID: device2,
Addresses: []string{"dynamic"},
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device3: {
DeviceID: device3,
Addresses: []string{"dynamic"},
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device4: {
DeviceID: device4,
@@ -271,7 +266,6 @@ func TestDeviceAddressesDynamic(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
}
@@ -295,7 +289,6 @@ func TestDeviceCompression(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device2: {
DeviceID: device2,
@@ -303,7 +296,6 @@ func TestDeviceCompression(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device3: {
DeviceID: device3,
@@ -311,7 +303,6 @@ func TestDeviceCompression(t *testing.T) {
Compression: protocol.CompressionNever,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device4: {
DeviceID: device4,
@@ -320,7 +311,6 @@ func TestDeviceCompression(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
}
@@ -343,21 +333,18 @@ func TestDeviceAddressesStatic(t *testing.T) {
Addresses: []string{"tcp://192.0.2.1", "tcp://192.0.2.2"},
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device2: {
DeviceID: device2,
Addresses: []string{"tcp://192.0.2.3:6070", "tcp://[2001:db8::42]:4242"},
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device3: {
DeviceID: device3,
Addresses: []string{"tcp://[2001:db8::44]:4444", "tcp://192.0.2.4:6090"},
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
device4: {
DeviceID: device4,
@@ -366,7 +353,6 @@ func TestDeviceAddressesStatic(t *testing.T) {
Compression: protocol.CompressionMetadata,
AllowedNetworks: []string{},
IgnoredFolders: []ObservedFolder{},
PendingFolders: []ObservedFolder{},
},
}
@@ -1097,10 +1083,6 @@ func TestDeviceConfigObservedNotNil(t *testing.T) {
if dev.IgnoredFolders == nil {
t.Errorf("Ignored folders nil")
}
if dev.PendingFolders == nil {
t.Errorf("Pending folders nil")
}
}
}

View File

@@ -33,8 +33,6 @@ func (cfg DeviceConfiguration) Copy() DeviceConfiguration {
copy(c.AllowedNetworks, cfg.AllowedNetworks)
c.IgnoredFolders = make([]ObservedFolder, len(cfg.IgnoredFolders))
copy(c.IgnoredFolders, cfg.IgnoredFolders)
c.PendingFolders = make([]ObservedFolder, len(cfg.PendingFolders))
copy(c.PendingFolders, cfg.PendingFolders)
return c
}
@@ -47,19 +45,12 @@ func (cfg *DeviceConfiguration) prepare(sharedFolders []string) {
}
ignoredFolders := deduplicateObservedFoldersToMap(cfg.IgnoredFolders)
pendingFolders := deduplicateObservedFoldersToMap(cfg.PendingFolders)
for id := range ignoredFolders {
delete(pendingFolders, id)
}
for _, sharedFolder := range sharedFolders {
delete(ignoredFolders, sharedFolder)
delete(pendingFolders, sharedFolder)
}
cfg.IgnoredFolders = sortedObservedFolderSlice(ignoredFolders)
cfg.PendingFolders = sortedObservedFolderSlice(pendingFolders)
}
func (cfg *DeviceConfiguration) IgnoredFolder(folder string) bool {

View File

@@ -40,7 +40,7 @@ type DeviceConfiguration struct {
MaxSendKbps int `protobuf:"varint,12,opt,name=max_send_kbps,json=maxSendKbps,proto3,casttype=int" json:"maxSendKbps" xml:"maxSendKbps"`
MaxRecvKbps int `protobuf:"varint,13,opt,name=max_recv_kbps,json=maxRecvKbps,proto3,casttype=int" json:"maxRecvKbps" xml:"maxRecvKbps"`
IgnoredFolders []ObservedFolder `protobuf:"bytes,14,rep,name=ignored_folders,json=ignoredFolders,proto3" json:"ignoredFolders" xml:"ignoredFolder"`
PendingFolders []ObservedFolder `protobuf:"bytes,15,rep,name=pending_folders,json=pendingFolders,proto3" json:"pendingFolders" xml:"pendingFolder"`
DeprecatedPendingFolders []ObservedFolder `protobuf:"bytes,15,rep,name=pending_folders,json=pendingFolders,proto3" json:"-" xml:"pendingFolder,omitempty"` // Deprecated: Do not use.
MaxRequestKiB int `protobuf:"varint,16,opt,name=max_request_kib,json=maxRequestKib,proto3,casttype=int" json:"maxRequestKiB" xml:"maxRequestKiB"`
Untrusted bool `protobuf:"varint,17,opt,name=untrusted,proto3" json:"untrusted" xml:"untrusted"`
RemoteGUIPort int `protobuf:"varint,18,opt,name=remote_gui_port,json=remoteGuiPort,proto3,casttype=int" json:"remoteGUIPort" xml:"remoteGUIPort"`
@@ -88,69 +88,71 @@ func init() {
}
var fileDescriptor_744b782bd13071dd = []byte{
// 980 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x31, 0x6f, 0xdb, 0x46,
0x18, 0x15, 0xeb, 0xc4, 0xb6, 0x68, 0xcb, 0xb2, 0x68, 0xc4, 0x61, 0x0c, 0x44, 0x27, 0xb0, 0x1a,
0x14, 0x34, 0x95, 0x0b, 0xb7, 0x93, 0xd1, 0x16, 0x28, 0x13, 0xb4, 0x35, 0x8c, 0x26, 0xe9, 0x15,
0x5d, 0xbc, 0xb0, 0x24, 0xef, 0xac, 0x1c, 0x2c, 0xf2, 0x58, 0xf2, 0xa8, 0x48, 0x40, 0x87, 0x8e,
0x1d, 0x3a, 0x14, 0x59, 0xbb, 0x14, 0x1d, 0x0a, 0xb4, 0xbf, 0xc4, 0x9b, 0x35, 0x16, 0x1d, 0x0e,
0x88, 0xbd, 0x71, 0xe4, 0x98, 0xa9, 0xb8, 0x23, 0x45, 0x91, 0x74, 0x5c, 0x04, 0xe8, 0x76, 0xf7,
0xde, 0xbb, 0xf7, 0xee, 0xfb, 0xf4, 0x9d, 0xa8, 0xf6, 0xc7, 0xc4, 0xd9, 0x77, 0xa9, 0x7f, 0x4a,
0x46, 0xfb, 0x08, 0x4f, 0x88, 0x8b, 0xb3, 0x4d, 0x1c, 0xda, 0x8c, 0x50, 0x7f, 0x18, 0x84, 0x94,
0x51, 0x6d, 0x35, 0x03, 0xf7, 0x76, 0x85, 0x5a, 0x42, 0x2e, 0x1d, 0xef, 0x3b, 0x38, 0xc8, 0xf8,
0xbd, 0x7b, 0x25, 0x17, 0xea, 0x44, 0x38, 0x9c, 0x60, 0x94, 0x53, 0x4d, 0x3c, 0x65, 0xd9, 0xd2,
0xf8, 0x73, 0x5b, 0xdd, 0x79, 0x2c, 0x33, 0x1e, 0x95, 0x33, 0xb4, 0xbf, 0x14, 0xb5, 0x99, 0x65,
0x5b, 0x04, 0xe9, 0x4a, 0x4f, 0x19, 0x6c, 0x9a, 0x3f, 0x2b, 0xe7, 0x1c, 0x34, 0xfe, 0xe1, 0xe0,
0xa3, 0x11, 0x61, 0xcf, 0x63, 0x67, 0xe8, 0x52, 0x6f, 0x3f, 0x9a, 0xf9, 0x2e, 0x7b, 0x4e, 0xfc,
0x51, 0x69, 0x55, 0xbe, 0xd1, 0x30, 0x73, 0x3f, 0x7a, 0x7c, 0xc9, 0xc1, 0xfa, 0x62, 0x9d, 0x70,
0xb0, 0x8e, 0xf2, 0x75, 0xca, 0x41, 0x6b, 0xea, 0x8d, 0x0f, 0x0d, 0x82, 0x1e, 0xda, 0x8c, 0x85,
0x46, 0x72, 0xd1, 0x5f, 0xcb, 0xd7, 0xe9, 0x45, 0xbf, 0xd0, 0xfd, 0x34, 0xef, 0x2b, 0x2f, 0xe7,
0xfd, 0xc2, 0x03, 0x2e, 0x18, 0xa4, 0x3d, 0x53, 0x6f, 0xf9, 0xb6, 0x87, 0xf5, 0x77, 0x7a, 0xca,
0xa0, 0x69, 0x7e, 0x9c, 0x70, 0x20, 0xf7, 0x29, 0x07, 0xf7, 0xa4, 0xb3, 0xd8, 0x48, 0xbf, 0x87,
0xd4, 0x23, 0x0c, 0x7b, 0x01, 0x9b, 0x89, 0x94, 0x9d, 0x37, 0xe0, 0x50, 0x9e, 0xd4, 0xa6, 0x6a,
0xd3, 0x46, 0x28, 0xc4, 0x51, 0x84, 0x23, 0x7d, 0xa5, 0xb7, 0x32, 0x68, 0x9a, 0x27, 0x09, 0x07,
0x4b, 0x30, 0xe5, 0xe0, 0x81, 0xf4, 0xce, 0x91, 0x92, 0x73, 0x0f, 0xe1, 0x53, 0x3b, 0x1e, 0xb3,
0x43, 0x03, 0xcd, 0x7c, 0xdb, 0x23, 0xae, 0xc8, 0xea, 0x5c, 0xd3, 0xbd, 0xbe, 0xe8, 0xaf, 0xe5,
0x02, 0xb8, 0xf4, 0xd5, 0x26, 0xea, 0x86, 0x4b, 0xbd, 0x40, 0xec, 0x08, 0xf5, 0xf5, 0x5b, 0x3d,
0x65, 0xb0, 0x75, 0x70, 0x67, 0x58, 0xb4, 0xf3, 0xd1, 0x92, 0x34, 0x3f, 0x49, 0x38, 0x28, 0xab,
0x53, 0x0e, 0x76, 0xe5, 0xa5, 0x4a, 0x58, 0xd1, 0xd3, 0xed, 0x3a, 0x08, 0xcb, 0x47, 0x35, 0xac,
0x36, 0x5d, 0x1c, 0x32, 0x4b, 0x36, 0xf2, 0xb6, 0x6c, 0xe4, 0x97, 0xe2, 0x67, 0x12, 0xe0, 0x93,
0xac, 0x99, 0xf7, 0x33, 0xef, 0x1c, 0x78, 0x43, 0x43, 0xef, 0xde, 0xc0, 0xc1, 0xc2, 0x45, 0x3b,
0x51, 0x55, 0xe2, 0xb3, 0x90, 0xa2, 0xd8, 0xc5, 0xa1, 0xbe, 0xda, 0x53, 0x06, 0xeb, 0xe6, 0x61,
0xc2, 0x41, 0x09, 0x4d, 0x39, 0xb8, 0x93, 0x0d, 0x44, 0x01, 0x15, 0x45, 0xb4, 0x6b, 0x18, 0x2c,
0x9d, 0xd3, 0x7e, 0x57, 0xd4, 0xbd, 0xe8, 0x8c, 0x04, 0xd6, 0x02, 0x13, 0x93, 0x6c, 0x85, 0xd8,
0xa3, 0x13, 0x7b, 0x1c, 0xe9, 0x6b, 0x32, 0x0c, 0x25, 0x1c, 0xe8, 0x42, 0x75, 0x54, 0x12, 0xc1,
0x5c, 0x93, 0x72, 0xf0, 0xae, 0x8c, 0xbe, 0x49, 0x50, 0x5c, 0xe4, 0xfe, 0x7f, 0x2a, 0xe0, 0x8d,
0x09, 0xda, 0x1f, 0x8a, 0xda, 0x2a, 0xee, 0x8c, 0x2c, 0x67, 0xa6, 0xaf, 0xcb, 0xc7, 0xf5, 0xe3,
0xff, 0x7a, 0x5c, 0x09, 0x07, 0x9b, 0x4b, 0x57, 0x73, 0x96, 0x72, 0x70, 0xb7, 0xda, 0x43, 0x64,
0xce, 0x8a, 0xcb, 0x77, 0xae, 0xa1, 0xe2, 0x71, 0xc1, 0x8a, 0x83, 0x76, 0xa0, 0xae, 0x06, 0x76,
0x1c, 0x61, 0xa4, 0x37, 0x65, 0xe3, 0xf6, 0x12, 0x0e, 0x72, 0x24, 0xe5, 0x60, 0x53, 0xba, 0x67,
0x5b, 0x03, 0xe6, 0xb8, 0xf6, 0x83, 0xba, 0x6d, 0x8f, 0xc7, 0xf4, 0x05, 0x46, 0x96, 0x8f, 0xd9,
0x0b, 0x1a, 0x9e, 0x45, 0xba, 0x2a, 0x5f, 0xcf, 0xd7, 0x09, 0x07, 0xed, 0x9c, 0x7b, 0x92, 0x53,
0x29, 0x07, 0xdd, 0xec, 0x0d, 0x55, 0xf0, 0xea, 0x4c, 0xe9, 0x37, 0x91, 0xb0, 0x6e, 0xa7, 0x7d,
0xa7, 0xee, 0xd8, 0x31, 0xa3, 0x96, 0xed, 0xba, 0x38, 0x60, 0xd6, 0x29, 0x1d, 0x23, 0x1c, 0x46,
0xfa, 0x86, 0xbc, 0xfe, 0x07, 0x09, 0x07, 0x1d, 0x41, 0x7f, 0x26, 0xd9, 0xcf, 0x33, 0xb2, 0xe8,
0xd3, 0x35, 0xc6, 0x80, 0xd7, 0xd5, 0xda, 0x53, 0xb5, 0xe5, 0xd9, 0x53, 0x2b, 0xc2, 0x3e, 0xb2,
0xce, 0x9c, 0x20, 0xd2, 0x37, 0x7b, 0xca, 0xe0, 0xb6, 0xf9, 0x9e, 0x78, 0x87, 0x9e, 0x3d, 0xfd,
0x06, 0xfb, 0xe8, 0xd8, 0x09, 0x84, 0x6b, 0x47, 0xba, 0x96, 0x30, 0xe3, 0x35, 0x07, 0x2b, 0xc4,
0x67, 0xb0, 0x2c, 0x5c, 0x18, 0x86, 0xd8, 0x9d, 0x64, 0x86, 0xad, 0x8a, 0x21, 0xc4, 0xee, 0xa4,
0x6e, 0xb8, 0xc0, 0x2a, 0x86, 0x0b, 0x50, 0xf3, 0xd5, 0x36, 0x19, 0xf9, 0x34, 0xc4, 0xa8, 0xa8,
0x7f, 0xab, 0xb7, 0x32, 0xd8, 0x38, 0xd8, 0x1d, 0x66, 0xdf, 0x82, 0xe1, 0xd3, 0xfc, 0x5b, 0x90,
0xd5, 0x64, 0xbe, 0x2f, 0xc6, 0x2e, 0xe1, 0x60, 0x2b, 0x3f, 0xb6, 0x6c, 0xcc, 0x4e, 0x36, 0x40,
0x65, 0xd8, 0x80, 0x35, 0x99, 0xc8, 0x0b, 0xb0, 0x8f, 0x88, 0x3f, 0x2a, 0xf2, 0xda, 0x6f, 0x97,
0x97, 0x1f, 0xab, 0xe7, 0x55, 0x60, 0x03, 0xd6, 0x64, 0xda, 0xaf, 0x8a, 0xda, 0xce, 0x3a, 0xf6,
0x7d, 0x8c, 0x23, 0x66, 0x9d, 0x11, 0x47, 0xdf, 0x96, 0x3d, 0x8b, 0x2e, 0x39, 0x68, 0x7d, 0x25,
0x5a, 0x21, 0x99, 0x63, 0x62, 0x26, 0x1c, 0xb4, 0xbc, 0x32, 0x50, 0x84, 0x54, 0xd0, 0x45, 0x23,
0x93, 0x8b, 0x7e, 0x4d, 0x5e, 0x07, 0x5e, 0xce, 0xfb, 0xd5, 0x04, 0x58, 0xe1, 0x1d, 0xed, 0x53,
0xb5, 0x19, 0xfb, 0x2c, 0x8c, 0x23, 0x86, 0x91, 0xde, 0x91, 0x73, 0xd7, 0x13, 0x9f, 0x8d, 0x02,
0x4c, 0x39, 0x68, 0xcb, 0x1b, 0x14, 0x88, 0x01, 0x97, 0xac, 0xac, 0x4e, 0xfc, 0x5f, 0x31, 0x6c,
0x8d, 0x62, 0x62, 0x05, 0x34, 0x64, 0xba, 0xb6, 0xac, 0x0e, 0x4a, 0xea, 0x8b, 0x6f, 0x8f, 0x9e,
0xd1, 0x90, 0x89, 0xea, 0xc2, 0x32, 0x50, 0x54, 0x57, 0x41, 0xcb, 0xd5, 0x55, 0xe5, 0x75, 0x40,
0x54, 0x57, 0x49, 0x80, 0x0b, 0x3e, 0x26, 0x62, 0x6b, 0x1e, 0x9f, 0xbf, 0xea, 0x36, 0xe6, 0xaf,
0xba, 0x8d, 0xf3, 0xcb, 0xae, 0x32, 0xbf, 0xec, 0x2a, 0xbf, 0x5c, 0x75, 0x1b, 0xbf, 0x5d, 0x75,
0x95, 0xf9, 0x55, 0xb7, 0xf1, 0xf7, 0x55, 0xb7, 0x71, 0xf2, 0xe0, 0x2d, 0xfe, 0xbb, 0xb2, 0xb1,
0x70, 0x56, 0xe5, 0x7f, 0xd8, 0x87, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xe9, 0x0e, 0x8a, 0x12,
0xed, 0x08, 0x00, 0x00,
// 1009 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xbd, 0x6f, 0xdb, 0x46,
0x1c, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x96, 0x65, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x4e, 0x50, 0x35,
0x28, 0x68, 0x22, 0x17, 0x6e, 0x27, 0xa3, 0x2d, 0x50, 0xc6, 0x68, 0x63, 0x18, 0x4d, 0xdc, 0x2b,
0xba, 0x78, 0x61, 0x49, 0xde, 0x59, 0x39, 0x58, 0xfc, 0x28, 0x79, 0x54, 0x2c, 0xa0, 0x43, 0xc7,
0x16, 0xe8, 0x50, 0x64, 0xed, 0x52, 0x74, 0xe8, 0xd0, 0xff, 0xa3, 0x80, 0x37, 0x6b, 0x2c, 0x3a,
0x1c, 0x10, 0x7b, 0xe3, 0xc8, 0x31, 0x53, 0x71, 0x47, 0x8a, 0x3a, 0xca, 0x71, 0x50, 0x20, 0xdb,
0xdd, 0x7b, 0xef, 0xde, 0xef, 0x83, 0xbf, 0x3b, 0x82, 0xee, 0x90, 0x3a, 0x3b, 0x6e, 0xe0, 0x9f,
0xd0, 0xc1, 0x0e, 0x26, 0x23, 0xea, 0x92, 0x7c, 0x93, 0x44, 0x36, 0xa3, 0x81, 0xdf, 0x0f, 0xa3,
0x80, 0x05, 0xfa, 0x62, 0x0e, 0x6e, 0x6f, 0x09, 0xb5, 0x84, 0xdc, 0x60, 0xb8, 0xe3, 0x90, 0x30,
0xe7, 0xb7, 0xef, 0x29, 0x2e, 0x81, 0x13, 0x93, 0x68, 0x44, 0x70, 0x41, 0xd5, 0xc9, 0x19, 0xcb,
0x97, 0x9d, 0xbf, 0xd7, 0xc1, 0xe6, 0xbe, 0x8c, 0xf1, 0x58, 0x8d, 0xa1, 0xff, 0xa5, 0x81, 0x7a,
0x1e, 0xdb, 0xa2, 0xd8, 0xd0, 0xda, 0x5a, 0x6f, 0xd5, 0xfc, 0x45, 0x3b, 0xe7, 0xb0, 0xf6, 0x2f,
0x87, 0x1f, 0x0f, 0x28, 0x7b, 0x9e, 0x38, 0x7d, 0x37, 0xf0, 0x76, 0xe2, 0xb1, 0xef, 0xb2, 0xe7,
0xd4, 0x1f, 0x28, 0x2b, 0x35, 0xa3, 0x7e, 0xee, 0x7e, 0xb0, 0x7f, 0xc9, 0xe1, 0xf2, 0x74, 0x9d,
0x72, 0xb8, 0x8c, 0x8b, 0x75, 0xc6, 0x61, 0xe3, 0xcc, 0x1b, 0xee, 0x75, 0x28, 0x7e, 0x68, 0x33,
0x16, 0x75, 0xd2, 0x8b, 0xee, 0x52, 0xb1, 0xce, 0x2e, 0xba, 0xa5, 0xee, 0xa7, 0x49, 0x57, 0x7b,
0x39, 0xe9, 0x96, 0x1e, 0x68, 0xca, 0x60, 0xfd, 0x08, 0xdc, 0xf2, 0x6d, 0x8f, 0x18, 0xef, 0xb5,
0xb5, 0x5e, 0xdd, 0xfc, 0x24, 0xe5, 0x50, 0xee, 0x33, 0x0e, 0xef, 0x49, 0x67, 0xb1, 0x91, 0x7e,
0x0f, 0x03, 0x8f, 0x32, 0xe2, 0x85, 0x6c, 0x2c, 0xa2, 0x6c, 0xbe, 0x01, 0x47, 0xf2, 0xa4, 0x7e,
0x06, 0xea, 0x36, 0xc6, 0x11, 0x89, 0x63, 0x12, 0x1b, 0x0b, 0xed, 0x85, 0x5e, 0xdd, 0x3c, 0x4e,
0x39, 0x9c, 0x81, 0x19, 0x87, 0x0f, 0xa4, 0x77, 0x81, 0x28, 0xce, 0x6d, 0x4c, 0x4e, 0xec, 0x64,
0xc8, 0xf6, 0x3a, 0x78, 0xec, 0xdb, 0x1e, 0x75, 0x45, 0xac, 0x8d, 0x6b, 0xba, 0xd7, 0x17, 0xdd,
0xa5, 0x42, 0x80, 0x66, 0xbe, 0xfa, 0x08, 0xac, 0xb8, 0x81, 0x17, 0x8a, 0x1d, 0x0d, 0x7c, 0xe3,
0x56, 0x5b, 0xeb, 0xad, 0xed, 0xde, 0xe9, 0x97, 0xed, 0x7c, 0x3c, 0x23, 0xcd, 0x4f, 0x53, 0x0e,
0x55, 0x75, 0xc6, 0xe1, 0x96, 0x4c, 0x4a, 0xc1, 0xca, 0x9e, 0xae, 0xcf, 0x83, 0x48, 0x3d, 0xaa,
0x13, 0x50, 0x77, 0x49, 0xc4, 0x2c, 0xd9, 0xc8, 0xdb, 0xb2, 0x91, 0x4f, 0xc4, 0x67, 0x12, 0xe0,
0xd3, 0xbc, 0x99, 0xf7, 0x73, 0xef, 0x02, 0x78, 0x43, 0x43, 0xef, 0xde, 0xc0, 0xa1, 0xd2, 0x45,
0x3f, 0x06, 0x80, 0xfa, 0x2c, 0x0a, 0x70, 0xe2, 0x92, 0xc8, 0x58, 0x6c, 0x6b, 0xbd, 0x65, 0x73,
0x2f, 0xe5, 0x50, 0x41, 0x33, 0x0e, 0xef, 0xe4, 0x03, 0x51, 0x42, 0x65, 0x11, 0xcd, 0x39, 0x0c,
0x29, 0xe7, 0xf4, 0x3f, 0x34, 0xb0, 0x1d, 0x9f, 0xd2, 0xd0, 0x9a, 0x62, 0x62, 0x92, 0xad, 0x88,
0x78, 0xc1, 0xc8, 0x1e, 0xc6, 0xc6, 0x92, 0x0c, 0x86, 0x53, 0x0e, 0x0d, 0xa1, 0x3a, 0x50, 0x44,
0xa8, 0xd0, 0x64, 0x1c, 0xbe, 0x2f, 0x43, 0xdf, 0x24, 0x28, 0x13, 0xb9, 0xff, 0x56, 0x05, 0xba,
0x31, 0x82, 0xfe, 0xa7, 0x06, 0x1a, 0x65, 0xce, 0xd8, 0x72, 0xc6, 0xc6, 0xb2, 0xbc, 0x5c, 0x3f,
0xbe, 0xd3, 0xe5, 0x4a, 0x39, 0x5c, 0x9d, 0xb9, 0x9a, 0xe3, 0x8c, 0xc3, 0xbb, 0xd5, 0x1e, 0x62,
0x73, 0x5c, 0x26, 0xbf, 0x71, 0x0d, 0x15, 0x97, 0x0b, 0x55, 0x1c, 0xf4, 0x5d, 0xb0, 0x18, 0xda,
0x49, 0x4c, 0xb0, 0x51, 0x97, 0x8d, 0xdb, 0x4e, 0x39, 0x2c, 0x90, 0x8c, 0xc3, 0x55, 0xe9, 0x9e,
0x6f, 0x3b, 0xa8, 0xc0, 0xf5, 0x1f, 0xc0, 0xba, 0x3d, 0x1c, 0x06, 0x2f, 0x08, 0xb6, 0x7c, 0xc2,
0x5e, 0x04, 0xd1, 0x69, 0x6c, 0x00, 0x79, 0x7b, 0xbe, 0x4e, 0x39, 0x6c, 0x16, 0xdc, 0xd3, 0x82,
0xca, 0x38, 0x6c, 0xe5, 0x77, 0xa8, 0x82, 0x57, 0x67, 0xca, 0xb8, 0x89, 0x44, 0xf3, 0x76, 0xfa,
0x77, 0x60, 0xd3, 0x4e, 0x58, 0x60, 0xd9, 0xae, 0x4b, 0x42, 0x66, 0x9d, 0x04, 0x43, 0x4c, 0xa2,
0xd8, 0x58, 0x91, 0xe9, 0x7f, 0x98, 0x72, 0xb8, 0x21, 0xe8, 0xcf, 0x25, 0xfb, 0x45, 0x4e, 0x96,
0x7d, 0xba, 0xc6, 0x74, 0xd0, 0x75, 0xb5, 0xfe, 0x0c, 0x34, 0x3c, 0xfb, 0xcc, 0x8a, 0x89, 0x8f,
0xad, 0x53, 0x27, 0x8c, 0x8d, 0xd5, 0xb6, 0xd6, 0xbb, 0x6d, 0x7e, 0x20, 0xee, 0xa1, 0x67, 0x9f,
0x7d, 0x43, 0x7c, 0x7c, 0xe8, 0x84, 0xc2, 0x75, 0x43, 0xba, 0x2a, 0x58, 0xe7, 0x35, 0x87, 0x0b,
0xd4, 0x67, 0x48, 0x15, 0x4e, 0x0d, 0x23, 0xe2, 0x8e, 0x72, 0xc3, 0x46, 0xc5, 0x10, 0x11, 0x77,
0x34, 0x6f, 0x38, 0xc5, 0x2a, 0x86, 0x53, 0x50, 0xf7, 0x41, 0x93, 0x0e, 0xfc, 0x20, 0x22, 0xb8,
0xac, 0x7f, 0xad, 0xbd, 0xd0, 0x5b, 0xd9, 0xdd, 0xea, 0xe7, 0xff, 0x82, 0xfe, 0xb3, 0xe2, 0x5f,
0x90, 0xd7, 0x64, 0x3e, 0x12, 0x63, 0x97, 0x72, 0xb8, 0x56, 0x1c, 0x9b, 0x35, 0x66, 0x33, 0x1f,
0x20, 0x15, 0xee, 0xa0, 0x39, 0x99, 0xfe, 0xb3, 0x06, 0x9a, 0x21, 0xf1, 0x31, 0xf5, 0x07, 0x65,
0xc0, 0xe6, 0x5b, 0x03, 0x3e, 0x11, 0x01, 0x2f, 0x39, 0x34, 0xf6, 0x49, 0x18, 0x11, 0xd7, 0x66,
0x04, 0x1f, 0xe5, 0x06, 0x85, 0x67, 0xca, 0xa1, 0xf6, 0xa8, 0x7c, 0x6e, 0x42, 0x95, 0x53, 0x46,
0xc3, 0xd0, 0xd0, 0x5a, 0x85, 0x8b, 0xf5, 0xdf, 0x34, 0xd0, 0xcc, 0xbb, 0xf9, 0x7d, 0x42, 0x62,
0x66, 0x9d, 0x52, 0xc7, 0x58, 0x97, 0xfd, 0x8c, 0x2f, 0x39, 0x6c, 0x7c, 0x25, 0xda, 0x24, 0x99,
0x43, 0x6a, 0xa6, 0x1c, 0x36, 0x3c, 0x15, 0x28, 0x0b, 0xae, 0xa0, 0xd3, 0x26, 0xa7, 0x17, 0xdd,
0x39, 0xf9, 0x3c, 0xf0, 0x72, 0xd2, 0xad, 0x46, 0x40, 0x15, 0xde, 0xd1, 0x3f, 0x03, 0xf5, 0xc4,
0x67, 0x51, 0x12, 0x33, 0x82, 0x8d, 0x0d, 0x39, 0x93, 0x6d, 0xf1, 0x4b, 0x29, 0xc1, 0x8c, 0xc3,
0xa6, 0xcc, 0xa0, 0x44, 0x3a, 0x68, 0xc6, 0xca, 0xea, 0xc4, 0x5b, 0xc6, 0x88, 0x35, 0x48, 0xa8,
0x15, 0x06, 0x11, 0x33, 0xf4, 0x59, 0x75, 0x48, 0x52, 0x5f, 0x7e, 0x7b, 0x70, 0x14, 0x44, 0x4c,
0x54, 0x17, 0xa9, 0x40, 0x59, 0x5d, 0x05, 0x55, 0xab, 0xab, 0xca, 0xe7, 0x01, 0x51, 0x5d, 0x25,
0x02, 0x9a, 0xf2, 0x09, 0x15, 0x5b, 0xf3, 0xf0, 0xfc, 0x55, 0xab, 0x36, 0x79, 0xd5, 0xaa, 0x9d,
0x5f, 0xb6, 0xb4, 0xc9, 0x65, 0x4b, 0xfb, 0xf5, 0xaa, 0x55, 0xfb, 0xfd, 0xaa, 0xa5, 0x4d, 0xae,
0x5a, 0xb5, 0x7f, 0xae, 0x5a, 0xb5, 0xe3, 0x07, 0xff, 0xe3, 0x5d, 0xcb, 0x27, 0xc6, 0x59, 0x94,
0xef, 0xdb, 0x47, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x01, 0x84, 0xd4, 0x09, 0x09, 0x00,
0x00,
}
func (m *DeviceConfiguration) Marshal() (dAtA []byte, err error) {
@@ -199,10 +201,10 @@ func (m *DeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x80
}
if len(m.PendingFolders) > 0 {
for iNdEx := len(m.PendingFolders) - 1; iNdEx >= 0; iNdEx-- {
if len(m.DeprecatedPendingFolders) > 0 {
for iNdEx := len(m.DeprecatedPendingFolders) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.PendingFolders[iNdEx].MarshalToSizedBuffer(dAtA[:i])
size, err := m.DeprecatedPendingFolders[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
@@ -405,8 +407,8 @@ func (m *DeviceConfiguration) ProtoSize() (n int) {
n += 1 + l + sovDeviceconfiguration(uint64(l))
}
}
if len(m.PendingFolders) > 0 {
for _, e := range m.PendingFolders {
if len(m.DeprecatedPendingFolders) > 0 {
for _, e := range m.DeprecatedPendingFolders {
l = e.ProtoSize()
n += 1 + l + sovDeviceconfiguration(uint64(l))
}
@@ -825,7 +827,7 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error {
iNdEx = postIndex
case 15:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PendingFolders", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedPendingFolders", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
@@ -852,8 +854,8 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.PendingFolders = append(m.PendingFolders, ObservedFolder{})
if err := m.PendingFolders[len(m.PendingFolders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
m.DeprecatedPendingFolders = append(m.DeprecatedPendingFolders, ObservedFolder{})
if err := m.DeprecatedPendingFolders[len(m.DeprecatedPendingFolders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex

View File

@@ -14,7 +14,7 @@ import (
"strings"
"time"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/v3/disk"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"

View File

@@ -27,6 +27,7 @@ import (
// put the newest on top for readability.
var (
migrations = migrationSet{
{33, migrateToConfigV33},
{32, migrateToConfigV32},
{31, migrateToConfigV31},
{30, migrateToConfigV30},
@@ -91,9 +92,11 @@ func (m migration) apply(cfg *Configuration) {
cfg.Version = m.targetVersion
}
func migrateToConfigV31(cfg *Configuration) {
// Show a notification about setting User and Password
cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs, "authenticationUserAndPassword")
func migrateToConfigV33(cfg *Configuration) {
for i := range cfg.Devices {
cfg.Devices[i].DeprecatedPendingFolders = nil
}
cfg.DeprecatedPendingDevices = nil
}
func migrateToConfigV32(cfg *Configuration) {
@@ -102,6 +105,11 @@ func migrateToConfigV32(cfg *Configuration) {
}
}
func migrateToConfigV31(cfg *Configuration) {
// Show a notification about setting User and Password
cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs, "authenticationUserAndPassword")
}
func migrateToConfigV30(cfg *Configuration) {
// The "max concurrent scans" option is now spelled "max folder concurrency"
// to be more general.

View File

@@ -7,9 +7,9 @@ import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
_ "github.com/golang/protobuf/ptypes/timestamp"
github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
_ "github.com/syncthing/syncthing/proto/ext"
_ "google.golang.org/protobuf/types/known/timestamppb"
io "io"
math "math"
math_bits "math/bits"

View File

@@ -47,6 +47,14 @@ func (opts *OptionsConfiguration) prepare(guiPWIsSet bool) {
}
}
}
// Negative limits are meaningless, zero means unlimited.
if opts.ConnectionLimitEnough < 0 {
opts.ConnectionLimitEnough = 0
}
if opts.ConnectionLimitMax < 0 {
opts.ConnectionLimitMax = 0
}
}
// RequiresRestartOnly returns a copy with only the attributes that require
@@ -178,3 +186,17 @@ func (opts OptionsConfiguration) FeatureFlag(name string) bool {
return false
}
// LowestConnectionLimit is the lower of ConnectionLimitEnough or
// ConnectionLimitMax, or whichever of them is actually set if only one of
// them is set. It's the point where we should stop dialling.
func (opts OptionsConfiguration) LowestConnectionLimit() int {
limit := opts.ConnectionLimitEnough
if limit == 0 || (opts.ConnectionLimitMax != 0 && opts.ConnectionLimitMax < limit) {
// It doesn't really make sense to set Max lower than Enough but
// someone might do it while experimenting and it's easy for us to
// do the right thing.
limit = opts.ConnectionLimitMax
}
return limit
}

View File

@@ -74,6 +74,13 @@ type OptionsConfiguration struct {
AnnounceLANAddresses bool `protobuf:"varint,48,opt,name=announce_lan_addresses,json=announceLanAddresses,proto3" json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"`
SendFullIndexOnUpgrade bool `protobuf:"varint,49,opt,name=send_full_index_on_upgrade,json=sendFullIndexOnUpgrade,proto3" json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"`
FeatureFlags []string `protobuf:"bytes,50,rep,name=feature_flags,json=featureFlags,proto3" json:"featureFlags" xml:"featureFlag"`
// The number of connections at which we stop trying to connect to more
// devices, zero meaning no limit. Does not affect incoming connections.
ConnectionLimitEnough int `protobuf:"varint,51,opt,name=connection_limit_enough,json=connectionLimitEnough,proto3,casttype=int" json:"connectionLimitEnough" xml:"connectionLimitEnough"`
// The maximum number of connections which we will allow in total, zero
// meaning no limit. Affects incoming connections and prevents
// attempting outgoing connections.
ConnectionLimitMax int `protobuf:"varint,52,opt,name=connection_limit_max,json=connectionLimitMax,proto3,casttype=int" json:"connectionLimitMax" xml:"connectionLimitMax"`
// Legacy deprecated
DeprecatedUPnPEnabled bool `protobuf:"varint,9000,opt,name=upnp_enabled,json=upnpEnabled,proto3" json:"-" xml:"upnpEnabled,omitempty"` // Deprecated: Do not use.
DeprecatedUPnPLeaseM int `protobuf:"varint,9001,opt,name=upnp_lease_m,json=upnpLeaseM,proto3,casttype=int" json:"-" xml:"upnpLeaseMinutes,omitempty"` // Deprecated: Do not use.
@@ -126,204 +133,208 @@ func init() {
}
var fileDescriptor_d09882599506ca03 = []byte{
// 3138 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x5a, 0x5b, 0x6c, 0x1d, 0x47,
0xf9, 0xcf, 0x26, 0x4d, 0xda, 0x6c, 0x1c, 0x27, 0x5e, 0x3b, 0xf6, 0x36, 0x49, 0xbd, 0xee, 0xc9,
0x49, 0xeb, 0xde, 0xe2, 0x4b, 0xda, 0xfc, 0xf3, 0xb7, 0x84, 0xc0, 0x97, 0x9a, 0xba, 0xb1, 0x1d,
0x6b, 0x6c, 0xab, 0xa8, 0x08, 0xad, 0xe6, 0xec, 0x99, 0x63, 0x2f, 0xde, 0x33, 0x7b, 0xb2, 0x3b,
0xeb, 0x4b, 0x41, 0xa5, 0x2a, 0xe2, 0xf2, 0x06, 0x58, 0x5c, 0x24, 0x90, 0x50, 0x11, 0x20, 0x51,
0x4a, 0x11, 0x12, 0x12, 0x12, 0xbc, 0x80, 0x90, 0x90, 0x2a, 0x78, 0xb0, 0x1f, 0x91, 0x28, 0x8b,
0xea, 0xf4, 0x01, 0x9d, 0x07, 0x1e, 0xce, 0xa3, 0x79, 0x41, 0xdf, 0xec, 0x6d, 0x76, 0x77, 0x4e,
0x93, 0xb7, 0xb3, 0xdf, 0xef, 0x9b, 0x6f, 0x7e, 0xdf, 0x5c, 0xbe, 0xf9, 0xbe, 0x99, 0xa3, 0x5e,
0x77, 0xec, 0xda, 0x98, 0xe5, 0xd2, 0x86, 0xbd, 0x31, 0xe6, 0xb6, 0x98, 0xed, 0x52, 0x3f, 0xfa,
0x0a, 0x3c, 0x0c, 0x5f, 0x37, 0x5a, 0x9e, 0xcb, 0x5c, 0xed, 0x4c, 0x24, 0xbc, 0x3c, 0x24, 0xa8,
0xb3, 0x80, 0xda, 0x74, 0x23, 0x52, 0xb8, 0x7c, 0x49, 0x00, 0x7c, 0xfb, 0x0d, 0x12, 0x8b, 0xcf,
0x92, 0x5d, 0x16, 0xfd, 0xac, 0xfc, 0x7b, 0x5e, 0x1d, 0xb8, 0x1b, 0xf5, 0x30, 0x2b, 0xf6, 0xa0,
0xfd, 0x58, 0x51, 0x2f, 0x3a, 0xb6, 0xcf, 0x08, 0x35, 0x71, 0xbd, 0xee, 0x11, 0xdf, 0x27, 0xbe,
0xae, 0x8c, 0x9c, 0x1a, 0x3d, 0x3b, 0xe3, 0x1f, 0x85, 0x86, 0x86, 0xf0, 0xce, 0x22, 0x87, 0xa7,
0x13, 0xb4, 0x1d, 0x1a, 0x17, 0x9c, 0xbc, 0xa8, 0x13, 0x1a, 0xd7, 0x77, 0x9b, 0xce, 0x54, 0x25,
0x27, 0xaf, 0x8c, 0xd4, 0x49, 0x03, 0x07, 0x0e, 0x9b, 0xaa, 0xc4, 0x3f, 0x2a, 0xc7, 0x07, 0xd5,
0x47, 0xe3, 0xdf, 0xfb, 0x87, 0x55, 0x89, 0x71, 0x54, 0x34, 0xad, 0xfd, 0x47, 0x51, 0xf5, 0x0d,
0xc7, 0xad, 0x61, 0xc7, 0xac, 0xdb, 0xbe, 0xe5, 0x6e, 0x13, 0x6f, 0xcf, 0xf4, 0x89, 0xb7, 0x4d,
0x3c, 0x5f, 0x3f, 0xc9, 0x89, 0xfe, 0x56, 0x39, 0x0a, 0x8d, 0x7e, 0x84, 0x77, 0x3e, 0xcb, 0xf5,
0xa6, 0x29, 0x5d, 0x8d, 0xf0, 0x76, 0x68, 0x5c, 0xda, 0x48, 0x64, 0x6e, 0x40, 0x2d, 0x12, 0x03,
0x9d, 0xd0, 0x78, 0x9e, 0x13, 0x96, 0xa1, 0x12, 0xde, 0xed, 0x83, 0xea, 0x80, 0x4c, 0xb5, 0x73,
0x50, 0x95, 0x77, 0x90, 0x77, 0x54, 0xc6, 0x0d, 0x0d, 0x46, 0x0d, 0xe7, 0x12, 0xa7, 0x62, 0xb9,
0xf6, 0xb1, 0xcc, 0x61, 0x42, 0x71, 0xcd, 0x21, 0x75, 0xfd, 0xd4, 0x88, 0x32, 0xfa, 0xd8, 0xcc,
0xbb, 0xe0, 0xf0, 0xc5, 0xd4, 0xe2, 0xcb, 0x11, 0x58, 0xf6, 0x36, 0x06, 0x3a, 0xa1, 0xf1, 0xac,
0xc4, 0xdb, 0x18, 0x15, 0xdc, 0x65, 0x5e, 0x40, 0xc0, 0xd7, 0x2e, 0x66, 0xba, 0x01, 0xc7, 0x07,
0xd5, 0x47, 0xa0, 0xe9, 0xfe, 0x61, 0xb5, 0x44, 0xaa, 0xe4, 0x66, 0x2c, 0xd7, 0x3e, 0x54, 0xd4,
0x21, 0xc7, 0xb5, 0xa4, 0x5e, 0x3e, 0xc2, 0xbd, 0xfc, 0x29, 0x78, 0x79, 0x61, 0x11, 0x74, 0x72,
0x4e, 0x0e, 0x38, 0xb1, 0xa8, 0xe0, 0xe3, 0x33, 0xd1, 0x12, 0x94, 0x80, 0x12, 0x17, 0xe5, 0x46,
0xba, 0xc8, 0x05, 0x07, 0x8b, 0x7c, 0xd0, 0x25, 0xde, 0xa0, 0xe4, 0xde, 0xdf, 0x14, 0xb5, 0x3f,
0x72, 0x0f, 0xc7, 0xb6, 0xcc, 0x96, 0xeb, 0x31, 0xfd, 0xf4, 0x88, 0x32, 0x7a, 0x7a, 0xe6, 0x87,
0xe0, 0x5a, 0x4f, 0x62, 0x6a, 0xc5, 0xf5, 0x58, 0x3b, 0x34, 0xfa, 0x72, 0x5d, 0x83, 0xb0, 0x13,
0x1a, 0x4f, 0x97, 0x9d, 0x02, 0x44, 0xf0, 0x68, 0x72, 0x62, 0x7c, 0xf2, 0xff, 0x2a, 0xc7, 0xa1,
0x71, 0xca, 0xa6, 0xac, 0x7d, 0x50, 0x95, 0x98, 0x91, 0x09, 0x8f, 0x0f, 0xaa, 0xa7, 0x79, 0xd3,
0xfd, 0xc3, 0x6a, 0x8e, 0x09, 0x2a, 0xeb, 0x6a, 0x5f, 0x3d, 0xa9, 0x8e, 0x14, 0xbc, 0x69, 0x06,
0x0e, 0xb3, 0x2d, 0xec, 0xb3, 0x24, 0x6e, 0xe8, 0x67, 0x46, 0x94, 0xd1, 0xb3, 0x33, 0xbf, 0x07,
0xd7, 0x7a, 0x13, 0x83, 0x4b, 0xb3, 0xb0, 0x93, 0xdb, 0xa1, 0xd1, 0x9f, 0x33, 0x1a, 0x89, 0x3b,
0xa1, 0x71, 0xab, 0xec, 0x5e, 0x84, 0x09, 0x0e, 0x7e, 0xbe, 0xd1, 0x98, 0x98, 0x9c, 0x9a, 0xba,
0x7d, 0xf3, 0xf6, 0x8b, 0x5f, 0x98, 0x8a, 0xbc, 0x6d, 0x1f, 0x54, 0xa5, 0x06, 0xe5, 0xe2, 0xe3,
0x83, 0xaa, 0x56, 0x36, 0xb2, 0x7f, 0x58, 0x2d, 0xd0, 0x44, 0x4f, 0xe4, 0x1b, 0x27, 0x1e, 0xc6,
0xc1, 0x48, 0xbb, 0xab, 0x9e, 0x6f, 0xe2, 0x5d, 0xd3, 0x27, 0xb4, 0x6e, 0x6e, 0xd5, 0x5a, 0xbe,
0xfe, 0x28, 0x9f, 0xcc, 0xe7, 0xda, 0xa1, 0x71, 0xae, 0x89, 0x77, 0x57, 0x09, 0xad, 0xdf, 0xa9,
0xb5, 0x20, 0xb8, 0xf4, 0x71, 0xb7, 0x04, 0x59, 0x32, 0x3f, 0x48, 0x54, 0x4c, 0x0c, 0x7a, 0xc4,
0xda, 0x8e, 0x0c, 0x3e, 0x96, 0x33, 0x88, 0x88, 0xb5, 0x5d, 0x34, 0x98, 0xc8, 0x72, 0x06, 0x13,
0xa1, 0xf6, 0x3b, 0x45, 0x1d, 0xf2, 0x88, 0xe5, 0x52, 0x4a, 0x2c, 0x08, 0xef, 0xa6, 0x4d, 0x19,
0xf1, 0xb6, 0xb1, 0x63, 0xfa, 0xfa, 0x59, 0x6e, 0xfb, 0x4d, 0x1e, 0xd4, 0x13, 0x95, 0x85, 0x18,
0x5e, 0x85, 0xd8, 0x21, 0x36, 0x4c, 0x81, 0x4e, 0x68, 0x8c, 0xf2, 0xbe, 0xa5, 0xa8, 0x30, 0x4b,
0xb7, 0xc6, 0x13, 0x4a, 0xc7, 0x07, 0xd5, 0x93, 0xb7, 0xc6, 0x79, 0x7c, 0x2f, 0xf5, 0x83, 0xe4,
0xbd, 0x68, 0x0d, 0xb5, 0xd7, 0x23, 0x0e, 0xde, 0xf3, 0xd3, 0x18, 0xa0, 0xf2, 0x18, 0xf0, 0xe9,
0x76, 0x68, 0x9c, 0x8f, 0x90, 0x6c, 0xa3, 0x57, 0x62, 0x42, 0x82, 0xb4, 0xb8, 0xc3, 0x93, 0x1d,
0x8b, 0xf2, 0x8d, 0xb5, 0xb7, 0x4f, 0xaa, 0x57, 0xe2, 0x8e, 0x52, 0x22, 0xd9, 0x20, 0x35, 0xf5,
0x73, 0x7c, 0x90, 0xfe, 0x0c, 0x6b, 0x78, 0x08, 0x81, 0x5e, 0xc9, 0x85, 0xa5, 0x76, 0x68, 0x0c,
0x79, 0x72, 0x28, 0x0d, 0xb4, 0x5d, 0x70, 0x81, 0xe5, 0xc4, 0xb8, 0xb0, 0x65, 0xbb, 0xda, 0xeb,
0x0e, 0xc1, 0x20, 0x4f, 0xc0, 0x20, 0x77, 0xa3, 0x89, 0xf4, 0xc8, 0xcf, 0x32, 0xa2, 0xd5, 0xd4,
0xf3, 0x3e, 0xc3, 0x1e, 0x33, 0x6b, 0x9e, 0xbb, 0xe3, 0x13, 0x4f, 0xef, 0xe1, 0x63, 0xfd, 0xa9,
0x76, 0x68, 0xf4, 0x70, 0x60, 0x26, 0x92, 0x77, 0x42, 0xe3, 0x49, 0xee, 0x8e, 0x28, 0xec, 0x3a,
0xd2, 0xb9, 0xa6, 0xda, 0xcf, 0x15, 0xf5, 0x12, 0xc5, 0xcc, 0x64, 0x1e, 0x86, 0x53, 0x0d, 0x3b,
0xe9, 0xc4, 0xf6, 0xf2, 0xce, 0xee, 0x1d, 0x85, 0x86, 0xba, 0x3c, 0xbd, 0x96, 0x85, 0x75, 0x95,
0x62, 0x96, 0xcd, 0xb1, 0xc1, 0x3b, 0xce, 0x44, 0x92, 0x10, 0x2e, 0x36, 0xc8, 0x7d, 0x09, 0xe1,
0x5a, 0xe8, 0x02, 0xf5, 0x53, 0xcc, 0xd6, 0x12, 0x3a, 0xc9, 0x82, 0xf8, 0x43, 0x89, 0xa7, 0x43,
0xb0, 0x4f, 0xcc, 0xa6, 0x7e, 0x81, 0x2f, 0x85, 0xaf, 0xc3, 0x52, 0x38, 0xbb, 0x3c, 0xbd, 0xb6,
0x08, 0x62, 0x98, 0xfc, 0x0b, 0x14, 0xb3, 0xe8, 0xc3, 0xa6, 0x01, 0xe3, 0xc9, 0x4f, 0x25, 0x21,
0x2b, 0xca, 0xa5, 0x7b, 0xa3, 0x7d, 0x50, 0x2d, 0xb5, 0x2f, 0x8b, 0xd2, 0x1d, 0x94, 0x75, 0x8c,
0x34, 0x91, 0x7d, 0x24, 0xd3, 0xfe, 0xaa, 0xa8, 0x43, 0x79, 0xf2, 0x1e, 0xa1, 0x64, 0x87, 0xaf,
0xe4, 0x8b, 0x9c, 0xfe, 0x3e, 0xd0, 0x3f, 0xb7, 0x3c, 0xbd, 0x86, 0x22, 0x00, 0x1c, 0xe8, 0xa3,
0x98, 0x25, 0x9f, 0xa9, 0x0b, 0xd5, 0xc4, 0x85, 0x3c, 0x22, 0x38, 0x71, 0x53, 0x74, 0x42, 0x62,
0x43, 0x26, 0x04, 0x47, 0x6e, 0x82, 0x23, 0x22, 0x05, 0x34, 0x20, 0xba, 0x92, 0x48, 0x25, 0xce,
0x30, 0xbb, 0x49, 0xdc, 0x80, 0x99, 0xbe, 0xde, 0x97, 0x77, 0x66, 0x2d, 0x02, 0x56, 0x63, 0x67,
0x92, 0x4f, 0x58, 0xe9, 0xf5, 0x9c, 0x33, 0x79, 0xa4, 0xdb, 0xf6, 0x93, 0xd8, 0x90, 0x09, 0xd3,
0x2d, 0x27, 0x52, 0xc8, 0x3b, 0x93, 0x48, 0xb5, 0x1f, 0x29, 0xaa, 0x1e, 0xf8, 0x78, 0x83, 0x98,
0x1e, 0x81, 0x73, 0xdf, 0xa6, 0x1b, 0x26, 0xb6, 0x2c, 0xd2, 0x62, 0xa4, 0xae, 0x6b, 0xdc, 0x1b,
0x0c, 0x3b, 0x60, 0x1d, 0x4d, 0xc7, 0x52, 0xd8, 0x01, 0x81, 0x97, 0x7c, 0x75, 0x42, 0xe3, 0x22,
0x77, 0x22, 0x13, 0x09, 0x84, 0x45, 0xc5, 0xdc, 0x17, 0xac, 0xf8, 0xcc, 0x24, 0x1a, 0xe4, 0x14,
0x50, 0xc2, 0x20, 0x91, 0x6b, 0x5f, 0x52, 0x07, 0x8a, 0xe4, 0x7c, 0x42, 0xa8, 0xde, 0xcf, 0x89,
0x2d, 0x1c, 0x85, 0xc6, 0x99, 0x75, 0xb4, 0x4a, 0x08, 0x6d, 0x87, 0xc6, 0x99, 0xc0, 0x83, 0x5f,
0x9d, 0xd0, 0xe8, 0x89, 0x09, 0xc1, 0xa7, 0x40, 0x26, 0x51, 0x48, 0x7f, 0xed, 0x1f, 0x56, 0xe3,
0xe6, 0x48, 0xcb, 0x13, 0x00, 0x99, 0xf6, 0x3d, 0x45, 0x7d, 0xbc, 0xd8, 0x7b, 0x40, 0xed, 0x7b,
0x01, 0x31, 0xed, 0xba, 0x3e, 0xc0, 0x93, 0x88, 0xd7, 0xa3, 0xb1, 0x59, 0xe7, 0xe2, 0x85, 0xb9,
0x68, 0x6c, 0xe2, 0x2f, 0x71, 0x6c, 0x12, 0x85, 0x4a, 0x34, 0x28, 0xc9, 0x67, 0x47, 0xfc, 0x8a,
0x07, 0x25, 0xc1, 0x8a, 0x83, 0x92, 0x68, 0x69, 0x7f, 0x52, 0xd4, 0xfe, 0x12, 0x2f, 0xcf, 0xd1,
0x2f, 0x71, 0x46, 0xdf, 0x82, 0xb5, 0x77, 0x7a, 0x1d, 0xad, 0xa3, 0xc5, 0x76, 0x68, 0x9c, 0x0e,
0xbc, 0x75, 0xb4, 0xd8, 0x09, 0x8d, 0xdb, 0x09, 0x11, 0xb4, 0x28, 0xac, 0xae, 0x4d, 0xc6, 0x5a,
0xfe, 0xd4, 0xd8, 0x58, 0x1d, 0x33, 0x7c, 0xc3, 0xdf, 0xa3, 0x16, 0xdb, 0x84, 0x62, 0x8d, 0x12,
0x36, 0x46, 0xc9, 0x0e, 0x48, 0x81, 0x70, 0x6c, 0x24, 0xf9, 0x71, 0x7c, 0x50, 0x7d, 0x88, 0x86,
0xfb, 0x87, 0xd5, 0x88, 0x05, 0xea, 0x2b, 0xf8, 0xe1, 0x39, 0xda, 0xbf, 0x14, 0xd5, 0x28, 0xba,
0xd0, 0x72, 0x7d, 0x38, 0xe1, 0x7c, 0x62, 0x05, 0x1e, 0x71, 0xf6, 0xf4, 0x41, 0x1e, 0x7e, 0x7f,
0xc0, 0x2b, 0x88, 0x75, 0xb4, 0xe2, 0xfa, 0x6c, 0x21, 0x05, 0xdb, 0xa1, 0x71, 0x31, 0xf0, 0xf2,
0xb2, 0x4e, 0x68, 0x3c, 0x15, 0x3b, 0x99, 0x07, 0x04, 0x7f, 0x1b, 0xd8, 0xf1, 0x79, 0x48, 0x2e,
0xb7, 0x96, 0xc8, 0x20, 0xf3, 0xe4, 0x2d, 0xa0, 0x5e, 0x28, 0x52, 0x40, 0x57, 0xf3, 0x6e, 0xe5,
0x51, 0xed, 0x9f, 0x12, 0x0f, 0x6d, 0x6a, 0x33, 0x1b, 0xea, 0x08, 0x38, 0xef, 0x4c, 0x5f, 0x1f,
0xe2, 0xab, 0xf8, 0xfb, 0xbc, 0x7a, 0x58, 0x47, 0x0b, 0x11, 0x3a, 0x07, 0x20, 0x04, 0x8c, 0x0b,
0x81, 0x97, 0x13, 0xa5, 0xe1, 0xa2, 0x20, 0x17, 0x83, 0xc5, 0xed, 0xf1, 0x5c, 0x00, 0x2f, 0x5a,
0x28, 0x8b, 0xe0, 0x04, 0x82, 0x56, 0x50, 0x30, 0x14, 0x28, 0xa0, 0x2b, 0x79, 0x07, 0x73, 0xa0,
0xe6, 0xaa, 0x7d, 0x1e, 0x89, 0x0e, 0x67, 0x97, 0x9a, 0x3b, 0x78, 0x8b, 0x04, 0x2d, 0x5d, 0xe7,
0x53, 0x36, 0x0b, 0xe4, 0x63, 0xf0, 0x2e, 0x7d, 0x8d, 0x43, 0x29, 0xf9, 0x82, 0xbc, 0xeb, 0x21,
0x5d, 0x34, 0xa0, 0x7d, 0x43, 0x51, 0x87, 0x70, 0xc0, 0x5c, 0x33, 0x68, 0x6d, 0x78, 0xb8, 0x4e,
0xb2, 0x64, 0x68, 0x53, 0x7f, 0x9c, 0x0f, 0xe4, 0x0a, 0x94, 0x5c, 0xa0, 0xb2, 0x1e, 0x69, 0x24,
0x79, 0xc4, 0x2b, 0x69, 0x75, 0x22, 0x03, 0xc5, 0xe1, 0x9b, 0x14, 0x33, 0xc3, 0x89, 0x49, 0x24,
0xb5, 0xa6, 0x35, 0xd5, 0xa1, 0x84, 0x03, 0x73, 0xcd, 0x96, 0x07, 0x53, 0xcc, 0xcf, 0x62, 0x5f,
0xbf, 0xcc, 0x07, 0xe0, 0x16, 0x10, 0x89, 0x55, 0xd6, 0xdc, 0x15, 0x8f, 0xa0, 0x18, 0xef, 0x84,
0xc6, 0xe5, 0x68, 0x0a, 0x25, 0x60, 0x05, 0x49, 0xdb, 0x68, 0xdb, 0xaa, 0xb6, 0x45, 0x48, 0xcb,
0x64, 0xa4, 0xd9, 0x72, 0x3d, 0xec, 0xd9, 0xc4, 0x37, 0x37, 0xf5, 0x2b, 0xdc, 0xe5, 0x57, 0x60,
0x23, 0x00, 0xba, 0x96, 0x81, 0xe0, 0xee, 0x35, 0xde, 0x4b, 0x11, 0x10, 0x6b, 0xb1, 0x17, 0x45,
0x57, 0x27, 0x5f, 0x44, 0x25, 0x2b, 0xda, 0x9e, 0xda, 0x6f, 0x61, 0x6b, 0x93, 0x98, 0xf6, 0x06,
0x75, 0x3d, 0x52, 0x37, 0x1b, 0xb6, 0x43, 0x7c, 0xfd, 0x2a, 0x77, 0x71, 0x01, 0x4e, 0x34, 0x0e,
0x2f, 0x44, 0xe8, 0x3c, 0x80, 0xe9, 0x40, 0x97, 0x90, 0xd2, 0x1e, 0x4c, 0xf7, 0x16, 0x2a, 0x9b,
0xd1, 0xbe, 0xa3, 0xa8, 0x97, 0x5b, 0x9e, 0xbb, 0x01, 0xc5, 0x8c, 0x19, 0xb4, 0xea, 0x98, 0x11,
0xb1, 0x40, 0x78, 0x82, 0xfb, 0xbe, 0x06, 0xf9, 0x6d, 0xa2, 0xb5, 0xce, 0x95, 0xc4, 0x62, 0x20,
0x2a, 0xb2, 0xbb, 0xe0, 0x02, 0x9d, 0x97, 0x84, 0x81, 0x50, 0x5e, 0x42, 0xdd, 0x2c, 0x6a, 0x6f,
0x2b, 0xea, 0xa0, 0x63, 0x37, 0x6d, 0x66, 0xd6, 0x30, 0xad, 0xef, 0xd8, 0x75, 0xb6, 0x69, 0xda,
0xd4, 0x74, 0x30, 0xd5, 0x87, 0xf9, 0x90, 0x2c, 0xf1, 0xe2, 0x11, 0x34, 0x66, 0x12, 0x85, 0x05,
0xba, 0x88, 0x69, 0x56, 0xf0, 0x97, 0xb1, 0x4f, 0x18, 0x16, 0x99, 0x29, 0xed, 0x2d, 0x45, 0xd5,
0x9a, 0x36, 0x35, 0x37, 0xdd, 0x26, 0x31, 0xeb, 0xb6, 0xbf, 0x65, 0x36, 0x3c, 0x42, 0x74, 0x63,
0x44, 0x19, 0x3d, 0x37, 0xd9, 0x73, 0x23, 0xba, 0x59, 0xbb, 0xb1, 0x6a, 0xbf, 0x41, 0x66, 0x5e,
0xfe, 0x20, 0x34, 0x4e, 0xc0, 0x4e, 0x6c, 0xda, 0xf4, 0x15, 0xb7, 0x49, 0xe6, 0x6c, 0x7f, 0x6b,
0xde, 0x23, 0x24, 0x5d, 0x1d, 0x05, 0xb9, 0xb8, 0x0f, 0x46, 0xae, 0x03, 0x91, 0x53, 0x13, 0x23,
0xd7, 0x51, 0xb1, 0xb9, 0x76, 0x5f, 0x51, 0x7b, 0x92, 0xf5, 0xce, 0x8f, 0x9d, 0x11, 0x7e, 0xec,
0xfc, 0x91, 0xa7, 0x3c, 0xc9, 0xa2, 0x8d, 0x0e, 0x9f, 0x73, 0x5e, 0xf6, 0xd9, 0x09, 0x8d, 0xb9,
0xa4, 0xe2, 0x48, 0x64, 0x92, 0x83, 0x28, 0xde, 0x01, 0x7e, 0xe1, 0x4c, 0x69, 0x12, 0x86, 0x6f,
0x7c, 0xd1, 0x77, 0x29, 0xc4, 0xee, 0x9c, 0xd9, 0xfc, 0xe7, 0xf1, 0x41, 0x75, 0xf4, 0x61, 0x4d,
0x41, 0x7e, 0x24, 0xf0, 0x45, 0x99, 0x1d, 0xcf, 0xd1, 0x5e, 0x53, 0xfb, 0xb0, 0xb3, 0x03, 0xd5,
0x57, 0x74, 0x9b, 0x40, 0x09, 0xf3, 0xf5, 0x27, 0xf9, 0x25, 0x1e, 0x14, 0xbd, 0x17, 0x22, 0x90,
0x57, 0xe5, 0xcb, 0x84, 0xc1, 0xc2, 0x1f, 0x88, 0x22, 0x4c, 0x4e, 0x5e, 0x41, 0x45, 0x45, 0xed,
0xbf, 0x8a, 0x3a, 0xea, 0x6e, 0x13, 0x6f, 0xc7, 0xb3, 0x19, 0x04, 0x8e, 0xa6, 0xcb, 0x88, 0x59,
0x27, 0xdb, 0xb6, 0x45, 0x4c, 0x8a, 0x9b, 0xc4, 0x87, 0x70, 0x1a, 0x17, 0x42, 0x7a, 0x25, 0xbb,
0x5e, 0x1a, 0xba, 0x9b, 0x34, 0x42, 0xbc, 0xcd, 0x1c, 0xd9, 0x5e, 0x06, 0xf5, 0x76, 0x68, 0x5c,
0x73, 0x4b, 0x90, 0x6d, 0x11, 0x8e, 0xde, 0xa5, 0xb3, 0x91, 0xa9, 0x4e, 0x68, 0xfc, 0x3f, 0x27,
0xf8, 0x10, 0xba, 0xdd, 0x17, 0x25, 0x54, 0x71, 0x5d, 0x78, 0xa0, 0x87, 0x61, 0xa1, 0x7d, 0x45,
0xbd, 0x04, 0x61, 0xcc, 0xb4, 0x69, 0x9d, 0xec, 0x9a, 0xb0, 0x92, 0x6b, 0x8e, 0x6b, 0x6d, 0xf9,
0xfa, 0x35, 0xbe, 0xa5, 0x61, 0xd1, 0x68, 0xa0, 0xb0, 0x00, 0xf8, 0x92, 0x4d, 0x67, 0x38, 0x9a,
0xde, 0xda, 0x96, 0x21, 0x69, 0xa6, 0x1c, 0xe5, 0xbf, 0x48, 0x62, 0x49, 0xfb, 0x07, 0xa4, 0xbb,
0x14, 0x5b, 0x5b, 0xa4, 0x6e, 0x52, 0x97, 0xd9, 0x0d, 0xdb, 0xc2, 0xd1, 0xfd, 0x43, 0xdd, 0xd7,
0xab, 0x7c, 0x7e, 0xdf, 0x81, 0xe1, 0x1e, 0x5c, 0x8f, 0x94, 0x96, 0x05, 0x9d, 0x85, 0x39, 0x18,
0xed, 0xc1, 0x40, 0x8a, 0x74, 0x42, 0xe3, 0x4a, 0x14, 0xda, 0x65, 0x30, 0xbf, 0xab, 0x94, 0x22,
0x9d, 0x83, 0x6a, 0x17, 0x8b, 0xfb, 0x87, 0xd5, 0x2e, 0x2c, 0x90, 0xb4, 0x45, 0xdd, 0xd7, 0x90,
0x7a, 0x9e, 0x79, 0xb8, 0xd1, 0xb0, 0x2d, 0xd3, 0x72, 0xb0, 0xef, 0xeb, 0xd7, 0xf9, 0xb0, 0xbe,
0x00, 0xf5, 0x72, 0x0c, 0xcc, 0x82, 0xbc, 0x13, 0x1a, 0x5a, 0x34, 0xa0, 0x82, 0x30, 0xbd, 0xa8,
0xc9, 0xa9, 0x6a, 0xf7, 0xd4, 0xfe, 0x78, 0x88, 0xcd, 0x86, 0xeb, 0xd4, 0x89, 0x67, 0xb6, 0x30,
0xdb, 0xd4, 0x9f, 0xe2, 0xbb, 0x7e, 0x1a, 0x8e, 0x81, 0x18, 0x9e, 0xe7, 0xe8, 0x0a, 0x66, 0x9b,
0x69, 0x88, 0x29, 0x21, 0xc2, 0x74, 0xbd, 0x09, 0xcb, 0x4a, 0x79, 0x13, 0x95, 0x9b, 0x6b, 0x5b,
0xea, 0x45, 0x9f, 0x30, 0xd3, 0x71, 0x77, 0xcc, 0x96, 0x67, 0xbb, 0x9e, 0xcd, 0xf6, 0xf4, 0xa7,
0xf9, 0x56, 0x80, 0xfe, 0x7a, 0x7d, 0xc2, 0x16, 0xdd, 0x9d, 0x95, 0x18, 0x49, 0x3b, 0xcb, 0x8b,
0xbb, 0x26, 0x16, 0x85, 0xe6, 0xda, 0xbb, 0x8a, 0x3a, 0xd8, 0xc4, 0xbb, 0x89, 0x73, 0x96, 0x4b,
0xad, 0xc0, 0xf3, 0x08, 0xb5, 0xf6, 0xf4, 0x51, 0x3e, 0x7a, 0x3e, 0xbf, 0x62, 0xc1, 0x3b, 0x4b,
0x78, 0x37, 0xe2, 0x38, 0x9b, 0xa9, 0xc0, 0x41, 0xdf, 0x94, 0xc8, 0xd3, 0x83, 0x5e, 0x06, 0x26,
0x03, 0xcd, 0xef, 0x44, 0xe4, 0x76, 0x91, 0xd4, 0xaa, 0xf6, 0xa1, 0xa2, 0xf6, 0x5b, 0x1e, 0xf6,
0x37, 0x0b, 0x99, 0xff, 0x33, 0x7c, 0x32, 0xde, 0xe3, 0x99, 0xff, 0x6c, 0x92, 0xf9, 0x5b, 0x71,
0xe6, 0x3f, 0x1f, 0x9d, 0xc8, 0xd0, 0x2c, 0xcb, 0xc1, 0xa5, 0xc1, 0x97, 0xeb, 0x94, 0xb3, 0x79,
0x2e, 0x86, 0x15, 0xdc, 0x57, 0x32, 0x02, 0x35, 0x81, 0x15, 0xd7, 0x04, 0xd5, 0x87, 0x31, 0x03,
0x55, 0xc1, 0x6c, 0x54, 0x15, 0x14, 0x8c, 0x79, 0x8e, 0xf6, 0x13, 0x45, 0x1d, 0x2a, 0xba, 0x97,
0x5c, 0xc6, 0x3c, 0xcb, 0xe7, 0xdf, 0x3e, 0x0a, 0x8d, 0xb3, 0xb3, 0x48, 0x78, 0x47, 0xc8, 0x5b,
0x29, 0xbe, 0x23, 0x48, 0xd1, 0x6e, 0x4b, 0x63, 0xff, 0xb0, 0x9a, 0xd9, 0x46, 0x72, 0xcb, 0xda,
0xd7, 0x14, 0x75, 0xd0, 0x67, 0x01, 0x35, 0x21, 0x5f, 0xc2, 0x8e, 0xbd, 0x4d, 0xcc, 0x28, 0x0b,
0xf6, 0xf5, 0xe7, 0xd2, 0x2c, 0xb4, 0x1f, 0x34, 0xee, 0x24, 0x0a, 0xab, 0x80, 0xaf, 0xa6, 0xb9,
0x91, 0x04, 0xcb, 0xa7, 0xf0, 0x42, 0x18, 0x3b, 0x35, 0x71, 0x7b, 0x1c, 0xc9, 0xac, 0x41, 0x65,
0x5c, 0xa0, 0x01, 0xd1, 0xd4, 0xd7, 0x9f, 0xe7, 0x24, 0x5e, 0x85, 0x7d, 0x99, 0x6b, 0xb6, 0x64,
0xd3, 0xac, 0x82, 0x28, 0x21, 0x62, 0x66, 0x98, 0x0b, 0xa3, 0x93, 0xe3, 0xa8, 0x6c, 0x07, 0x72,
0xf1, 0x1e, 0xde, 0x7b, 0xf2, 0xbc, 0xf5, 0x02, 0x8f, 0x9c, 0xf5, 0xa3, 0xd0, 0xe8, 0x45, 0x78,
0x67, 0x95, 0x05, 0xc2, 0xc3, 0xd6, 0x39, 0x3f, 0xfb, 0x4c, 0xaf, 0xa0, 0x32, 0xd9, 0x03, 0x1f,
0xdf, 0x0a, 0x16, 0x91, 0x68, 0x4f, 0xdb, 0x56, 0x2f, 0x40, 0xb1, 0x59, 0xc3, 0x3e, 0x31, 0xa3,
0x97, 0x46, 0xfd, 0xc6, 0x88, 0x32, 0xda, 0x3b, 0xd9, 0x9b, 0x24, 0x43, 0x6b, 0x5c, 0xca, 0xef,
0x0c, 0x7b, 0x13, 0xd5, 0x48, 0x96, 0x85, 0xa9, 0x9c, 0xb8, 0x32, 0x12, 0x97, 0x1e, 0xf1, 0xf2,
0x78, 0xeb, 0xb0, 0xaa, 0xa0, 0x42, 0x53, 0xed, 0xbb, 0x27, 0xd5, 0x6b, 0x10, 0x35, 0xd2, 0x70,
0x01, 0xa5, 0xab, 0xe5, 0x36, 0x61, 0xc9, 0x7a, 0xe4, 0x5e, 0x40, 0x7c, 0x66, 0x6e, 0xd9, 0x35,
0x7d, 0x8c, 0x4f, 0xc7, 0x5f, 0x94, 0xf8, 0x85, 0x72, 0x09, 0xef, 0xce, 0x2e, 0xa0, 0x08, 0xbf,
0x63, 0xcf, 0xb4, 0x43, 0xc3, 0x68, 0xe2, 0xdd, 0x74, 0x8b, 0xb3, 0x85, 0xd8, 0x46, 0xa6, 0x92,
0x9e, 0x7d, 0x0f, 0xd0, 0x13, 0xca, 0xbe, 0x07, 0x9a, 0x7c, 0xb0, 0x4a, 0xfc, 0xe6, 0x59, 0xa0,
0x8b, 0x1e, 0xd0, 0xac, 0xa6, 0x7d, 0xac, 0xa8, 0x83, 0xe9, 0xc3, 0x8b, 0x83, 0xc5, 0xa7, 0xda,
0x71, 0xbe, 0x81, 0xdf, 0x87, 0x91, 0x18, 0x48, 0x1e, 0x2e, 0x16, 0xa7, 0x97, 0xc5, 0xd7, 0xda,
0x01, 0x2c, 0x91, 0xa7, 0xe9, 0xb3, 0x0c, 0x94, 0xbd, 0x97, 0x49, 0x8d, 0x74, 0x91, 0x0b, 0x5b,
0x5f, 0x4a, 0x0a, 0x65, 0xad, 0xb0, 0xf0, 0xd4, 0xbb, 0xad, 0x5e, 0xe6, 0x6f, 0x2b, 0x8d, 0xc0,
0x71, 0xe2, 0x5c, 0xc6, 0xa5, 0x49, 0x61, 0xaa, 0x4f, 0x70, 0x4f, 0xa7, 0x20, 0x57, 0x00, 0xad,
0xf9, 0xc0, 0x71, 0x78, 0x16, 0x72, 0x97, 0xc6, 0xa5, 0x64, 0x27, 0x34, 0xae, 0xc6, 0x47, 0x96,
0x0c, 0xae, 0xa0, 0x2e, 0xed, 0xb4, 0x57, 0xd5, 0xf3, 0x0d, 0x82, 0x59, 0xe0, 0x11, 0xb3, 0xe1,
0xe0, 0x0d, 0x5f, 0x9f, 0xe4, 0xfb, 0xee, 0x3a, 0x9c, 0xef, 0x31, 0x30, 0x0f, 0xf2, 0xf4, 0x1d,
0x46, 0x10, 0x56, 0x50, 0x4e, 0x45, 0xfb, 0xb2, 0xda, 0x13, 0xb4, 0x68, 0x2b, 0x0d, 0xb0, 0xbf,
0x98, 0xe7, 0xb4, 0x3f, 0x77, 0x14, 0x1a, 0x97, 0xe6, 0x48, 0xcb, 0x23, 0x16, 0x66, 0xa4, 0xbe,
0xbe, 0x42, 0x57, 0xb2, 0x68, 0xab, 0xbc, 0x90, 0xa5, 0x39, 0x2d, 0xda, 0x8a, 0x81, 0xe7, 0xdd,
0xa6, 0x0d, 0xa9, 0x16, 0xdb, 0xab, 0xec, 0x1f, 0x56, 0xe5, 0x8d, 0x75, 0x05, 0x9d, 0x13, 0x9a,
0x68, 0x3f, 0x53, 0xe2, 0xee, 0x93, 0x4b, 0xec, 0x77, 0xe7, 0xf9, 0x4e, 0x79, 0x8b, 0xaf, 0x8f,
0xbc, 0x89, 0xf4, 0x42, 0x9b, 0x77, 0x3f, 0x92, 0x76, 0x2f, 0x5e, 0x44, 0x0b, 0x1c, 0xb2, 0x8d,
0x70, 0xb9, 0xbb, 0x16, 0x4c, 0xb8, 0xac, 0x17, 0x5d, 0x41, 0x6a, 0xd6, 0x4a, 0xfb, 0x8d, 0xa2,
0xf6, 0x72, 0x9a, 0xd9, 0x75, 0xf5, 0x2f, 0x23, 0xa2, 0xdf, 0xe4, 0x59, 0x62, 0xde, 0x84, 0x70,
0x75, 0xcd, 0xa9, 0x56, 0x52, 0xaa, 0xf9, 0xcb, 0x66, 0x29, 0xd9, 0xab, 0x9f, 0xa4, 0x07, 0xb9,
0xa0, 0xbc, 0x2f, 0x5d, 0x41, 0x3d, 0x62, 0xcb, 0x8c, 0x72, 0x76, 0x29, 0xfd, 0x5e, 0x77, 0xca,
0xc2, 0x05, 0x75, 0x81, 0x72, 0xfe, 0x4a, 0xb9, 0x3b, 0xe5, 0x6e, 0x7a, 0x65, 0xca, 0x89, 0x66,
0x42, 0x39, 0xbd, 0x83, 0x6e, 0xa8, 0xd1, 0xe3, 0x57, 0x7a, 0x9c, 0xfc, 0x6a, 0x9e, 0xaf, 0xeb,
0xcf, 0xe4, 0xf9, 0xf2, 0xf7, 0xa3, 0xec, 0x5c, 0x11, 0x16, 0xa3, 0x97, 0x21, 0x02, 0x51, 0xe8,
0x47, 0x40, 0x7c, 0x5e, 0xc2, 0x97, 0xab, 0x67, 0xb3, 0x65, 0x31, 0xfd, 0x7d, 0x18, 0x22, 0x65,
0x66, 0xe9, 0x28, 0x34, 0xae, 0x66, 0x3d, 0x2e, 0xe5, 0x6b, 0xdf, 0x15, 0x8b, 0xe5, 0xc7, 0xa9,
0x59, 0xc2, 0xf3, 0xdd, 0x6b, 0x65, 0x05, 0x38, 0x3b, 0x07, 0x0a, 0x27, 0x87, 0x6f, 0x61, 0xea,
0xeb, 0xbf, 0x8e, 0x66, 0x69, 0xad, 0x40, 0x41, 0x8c, 0xb8, 0xab, 0xa0, 0x58, 0xa0, 0x50, 0xc2,
0xcb, 0x53, 0xc5, 0x99, 0x94, 0xf4, 0x66, 0xee, 0x7c, 0xf0, 0xd1, 0xf0, 0x89, 0xc3, 0x8f, 0x86,
0x4f, 0x7c, 0x70, 0x34, 0xac, 0x1c, 0x1e, 0x0d, 0x2b, 0xdf, 0xbe, 0x3f, 0x7c, 0xe2, 0x9d, 0xfb,
0xc3, 0xca, 0xe1, 0xfd, 0xe1, 0x13, 0x7f, 0xbf, 0x3f, 0x7c, 0xe2, 0xf5, 0x67, 0x36, 0x6c, 0xb6,
0x19, 0xd4, 0x6e, 0x58, 0x6e, 0x73, 0x2c, 0xcd, 0xe7, 0x84, 0x5f, 0xd9, 0xbf, 0x79, 0x6a, 0x67,
0xf8, 0xdf, 0x77, 0x6e, 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x64, 0xe8, 0xf8, 0x5e, 0x2a, 0x24,
0x00, 0x00,
// 3211 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x5a, 0x5b, 0x6c, 0xdc, 0xc6,
0xd5, 0x36, 0xed, 0xd8, 0x89, 0x69, 0x59, 0xb6, 0x28, 0x59, 0x62, 0x6c, 0x47, 0x54, 0xd6, 0xeb,
0x44, 0xb9, 0xd9, 0x92, 0xec, 0xf8, 0x77, 0x0c, 0xfc, 0xc8, 0xaf, 0x4b, 0xf4, 0x5b, 0xb1, 0x64,
0x0b, 0x23, 0x09, 0xf9, 0x91, 0x1f, 0x05, 0x31, 0xcb, 0x9d, 0x95, 0x58, 0x71, 0x87, 0x6b, 0x72,
0xa8, 0x95, 0xd2, 0x22, 0x0d, 0x52, 0xf4, 0xf2, 0xd6, 0x56, 0xe8, 0x05, 0x68, 0x81, 0x22, 0x45,
0x5b, 0xa0, 0x69, 0x9a, 0xa2, 0x40, 0x81, 0x02, 0xed, 0x4b, 0x8b, 0x02, 0x05, 0x82, 0x16, 0xa8,
0xf4, 0x58, 0xa0, 0x29, 0x8b, 0xc8, 0x79, 0xda, 0x87, 0x3e, 0xec, 0xa3, 0xfa, 0x52, 0x9c, 0xe1,
0x6d, 0x48, 0xce, 0xc6, 0x7e, 0x5b, 0x9e, 0xef, 0xcc, 0x99, 0xef, 0xcc, 0xe5, 0xcc, 0x39, 0x33,
0xab, 0x5e, 0x76, 0xec, 0xda, 0x55, 0xcb, 0xa5, 0x0d, 0x7b, 0xfd, 0xaa, 0xdb, 0x62, 0xb6, 0x4b,
0xfd, 0xe8, 0x2b, 0xf0, 0x30, 0x7c, 0x5d, 0x69, 0x79, 0x2e, 0x73, 0xb5, 0x13, 0x91, 0xf0, 0xfc,
0x88, 0xa0, 0xce, 0x02, 0x6a, 0xd3, 0xf5, 0x48, 0xe1, 0xfc, 0x39, 0x01, 0xf0, 0xed, 0xb7, 0x48,
0x2c, 0x3e, 0x49, 0xb6, 0x59, 0xf4, 0xb3, 0xf2, 0xd7, 0xdb, 0xea, 0xd0, 0xbd, 0xa8, 0x87, 0x59,
0xb1, 0x07, 0xed, 0x87, 0x8a, 0x7a, 0xd6, 0xb1, 0x7d, 0x46, 0xa8, 0x89, 0xeb, 0x75, 0x8f, 0xf8,
0x3e, 0xf1, 0x75, 0x65, 0xec, 0xd8, 0xf8, 0xc9, 0x19, 0xff, 0x20, 0x34, 0x34, 0x84, 0xdb, 0x8b,
0x1c, 0x9e, 0x4e, 0xd0, 0x4e, 0x68, 0x9c, 0x71, 0xf2, 0xa2, 0x6e, 0x68, 0x5c, 0xde, 0x6e, 0x3a,
0xb7, 0x2a, 0x39, 0x79, 0x65, 0xac, 0x4e, 0x1a, 0x38, 0x70, 0xd8, 0xad, 0x4a, 0xfc, 0xa3, 0x72,
0xb8, 0x57, 0x7d, 0x3c, 0xfe, 0xbd, 0xbb, 0x5f, 0x95, 0x18, 0x47, 0x45, 0xd3, 0xda, 0xbf, 0x14,
0x55, 0x5f, 0x77, 0xdc, 0x1a, 0x76, 0xcc, 0xba, 0xed, 0x5b, 0xee, 0x16, 0xf1, 0x76, 0x4c, 0x9f,
0x78, 0x5b, 0xc4, 0xf3, 0xf5, 0xa3, 0x9c, 0xe8, 0xaf, 0x95, 0x83, 0xd0, 0x18, 0x44, 0xb8, 0xfd,
0xbf, 0x5c, 0x6f, 0x9a, 0xd2, 0x95, 0x08, 0xef, 0x84, 0xc6, 0xb9, 0xf5, 0x44, 0xe6, 0x06, 0xd4,
0x22, 0x31, 0xd0, 0x0d, 0x8d, 0x17, 0x39, 0x61, 0x19, 0x2a, 0xe1, 0xdd, 0xd9, 0xab, 0x0e, 0xc9,
0x54, 0xbb, 0x7b, 0x55, 0x79, 0x07, 0x79, 0x47, 0x65, 0xdc, 0xd0, 0x70, 0xd4, 0x70, 0x2e, 0x71,
0x2a, 0x96, 0x6b, 0x9f, 0xca, 0x1c, 0x26, 0x14, 0xd7, 0x1c, 0x52, 0xd7, 0x8f, 0x8d, 0x29, 0xe3,
0x4f, 0xcc, 0xbc, 0x0f, 0x0e, 0x9f, 0x4d, 0x2d, 0xbe, 0x16, 0x81, 0x65, 0x6f, 0x63, 0xa0, 0x1b,
0x1a, 0xcf, 0x4b, 0xbc, 0x8d, 0x51, 0xc1, 0x5d, 0xe6, 0x05, 0x04, 0x7c, 0xed, 0x61, 0xa6, 0x17,
0x70, 0xb8, 0x57, 0x7d, 0x0c, 0x9a, 0xee, 0xee, 0x57, 0x4b, 0xa4, 0x4a, 0x6e, 0xc6, 0x72, 0xed,
0x63, 0x45, 0x1d, 0x71, 0x5c, 0x4b, 0xea, 0xe5, 0x63, 0xdc, 0xcb, 0x1f, 0x83, 0x97, 0x67, 0x16,
0x41, 0x27, 0xe7, 0xe4, 0x90, 0x13, 0x8b, 0x0a, 0x3e, 0x3e, 0x17, 0x2d, 0x41, 0x09, 0x28, 0x71,
0x51, 0x6e, 0xa4, 0x87, 0x5c, 0x70, 0xb0, 0xc8, 0x07, 0x9d, 0xe3, 0x0d, 0x4a, 0xee, 0xfd, 0x45,
0x51, 0x07, 0x23, 0xf7, 0x70, 0x6c, 0xcb, 0x6c, 0xb9, 0x1e, 0xd3, 0x8f, 0x8f, 0x29, 0xe3, 0xc7,
0x67, 0xbe, 0x0f, 0xae, 0xf5, 0x25, 0xa6, 0x96, 0x5d, 0x8f, 0x75, 0x42, 0x63, 0x20, 0xd7, 0x35,
0x08, 0xbb, 0xa1, 0xf1, 0x6c, 0xd9, 0x29, 0x40, 0x04, 0x8f, 0xa6, 0x26, 0x27, 0xa6, 0xfe, 0xab,
0x72, 0x18, 0x1a, 0xc7, 0x6c, 0xca, 0x3a, 0x7b, 0x55, 0x89, 0x19, 0x99, 0xf0, 0x70, 0xaf, 0x7a,
0x9c, 0x37, 0xdd, 0xdd, 0xaf, 0xe6, 0x98, 0xa0, 0xb2, 0xae, 0xf6, 0xe5, 0xa3, 0xea, 0x58, 0xc1,
0x9b, 0x66, 0xe0, 0x30, 0xdb, 0xc2, 0x3e, 0x4b, 0xe2, 0x86, 0x7e, 0x62, 0x4c, 0x19, 0x3f, 0x39,
0xf3, 0x5b, 0x70, 0xad, 0x3f, 0x31, 0xb8, 0x34, 0x0b, 0x3b, 0xb9, 0x13, 0x1a, 0x83, 0x39, 0xa3,
0x91, 0xb8, 0x1b, 0x1a, 0x37, 0xca, 0xee, 0x45, 0x98, 0xe0, 0xe0, 0xff, 0x37, 0x1a, 0x93, 0x53,
0xb7, 0x6e, 0xdd, 0xbc, 0x76, 0xf3, 0xfa, 0xe7, 0x6e, 0x45, 0xde, 0x76, 0xf6, 0xaa, 0x52, 0x83,
0x72, 0xf1, 0xe1, 0x5e, 0x55, 0x2b, 0x1b, 0xd9, 0xdd, 0xaf, 0x16, 0x68, 0xa2, 0xa7, 0xf2, 0x8d,
0x13, 0x0f, 0xe3, 0x60, 0xa4, 0xdd, 0x53, 0x4f, 0x37, 0xf1, 0xb6, 0xe9, 0x13, 0x5a, 0x37, 0x37,
0x6b, 0x2d, 0x5f, 0x7f, 0x9c, 0x4f, 0xe6, 0x0b, 0x9d, 0xd0, 0x38, 0xd5, 0xc4, 0xdb, 0x2b, 0x84,
0xd6, 0xef, 0xd4, 0x5a, 0x10, 0x5c, 0x06, 0xb8, 0x5b, 0x82, 0x2c, 0x99, 0x1f, 0x24, 0x2a, 0x26,
0x06, 0x3d, 0x62, 0x6d, 0x45, 0x06, 0x9f, 0xc8, 0x19, 0x44, 0xc4, 0xda, 0x2a, 0x1a, 0x4c, 0x64,
0x39, 0x83, 0x89, 0x50, 0xfb, 0x8d, 0xa2, 0x8e, 0x78, 0xc4, 0x72, 0x29, 0x25, 0x16, 0x84, 0x77,
0xd3, 0xa6, 0x8c, 0x78, 0x5b, 0xd8, 0x31, 0x7d, 0xfd, 0x24, 0xb7, 0xfd, 0x36, 0x0f, 0xea, 0x89,
0xca, 0x42, 0x0c, 0xaf, 0x40, 0xec, 0x10, 0x1b, 0xa6, 0x40, 0x37, 0x34, 0xc6, 0x79, 0xdf, 0x52,
0x54, 0x98, 0xa5, 0x1b, 0x13, 0x09, 0xa5, 0xc3, 0xbd, 0xea, 0xd1, 0x1b, 0x13, 0x3c, 0xbe, 0x97,
0xfa, 0x41, 0xf2, 0x5e, 0xb4, 0x86, 0xda, 0xef, 0x11, 0x07, 0xef, 0xf8, 0x69, 0x0c, 0x50, 0x79,
0x0c, 0x78, 0xb5, 0x13, 0x1a, 0xa7, 0x23, 0x24, 0xdb, 0xe8, 0x95, 0x98, 0x90, 0x20, 0x2d, 0xee,
0xf0, 0x64, 0xc7, 0xa2, 0x7c, 0x63, 0xed, 0xdd, 0xa3, 0xea, 0x85, 0xb8, 0xa3, 0x94, 0x48, 0x36,
0x48, 0x4d, 0xfd, 0x14, 0x1f, 0xa4, 0x3f, 0xc2, 0x1a, 0x1e, 0x41, 0xa0, 0x57, 0x72, 0x61, 0xa9,
0x13, 0x1a, 0x23, 0x9e, 0x1c, 0x4a, 0x03, 0x6d, 0x0f, 0x5c, 0x60, 0x39, 0x39, 0x21, 0x6c, 0xd9,
0x9e, 0xf6, 0x7a, 0x43, 0x30, 0xc8, 0x93, 0x30, 0xc8, 0xbd, 0x68, 0x22, 0x3d, 0xf2, 0xb3, 0x8c,
0x68, 0x35, 0xf5, 0xb4, 0xcf, 0xb0, 0xc7, 0xcc, 0x9a, 0xe7, 0xb6, 0x7d, 0xe2, 0xe9, 0x7d, 0x7c,
0xac, 0xff, 0xbb, 0x13, 0x1a, 0x7d, 0x1c, 0x98, 0x89, 0xe4, 0xdd, 0xd0, 0x78, 0x9a, 0xbb, 0x23,
0x0a, 0x7b, 0x8e, 0x74, 0xae, 0xa9, 0xf6, 0x53, 0x45, 0x3d, 0x47, 0x31, 0x33, 0x99, 0x87, 0xe1,
0x54, 0xc3, 0x4e, 0x3a, 0xb1, 0xfd, 0xbc, 0xb3, 0xfb, 0x07, 0xa1, 0xa1, 0xde, 0x9d, 0x5e, 0xcd,
0xc2, 0xba, 0x4a, 0x31, 0xcb, 0xe6, 0xd8, 0xe0, 0x1d, 0x67, 0x22, 0x49, 0x08, 0x17, 0x1b, 0xe4,
0xbe, 0x84, 0x70, 0x2d, 0x74, 0x81, 0x06, 0x29, 0x66, 0xab, 0x09, 0x9d, 0x64, 0x41, 0xfc, 0xae,
0xc4, 0xd3, 0x21, 0xd8, 0x27, 0x66, 0x53, 0x3f, 0xc3, 0x97, 0xc2, 0x57, 0x61, 0x29, 0x9c, 0xbc,
0x3b, 0xbd, 0xba, 0x08, 0x62, 0x98, 0xfc, 0x33, 0x14, 0xb3, 0xe8, 0xc3, 0xa6, 0x01, 0xe3, 0xc9,
0x4f, 0x25, 0x21, 0x2b, 0xca, 0xa5, 0x7b, 0xa3, 0xb3, 0x57, 0x2d, 0xb5, 0x2f, 0x8b, 0xd2, 0x1d,
0x94, 0x75, 0x8c, 0x34, 0x91, 0x7d, 0x24, 0xd3, 0xfe, 0xac, 0xa8, 0x23, 0x79, 0xf2, 0x1e, 0xa1,
0xa4, 0xcd, 0x57, 0xf2, 0x59, 0x4e, 0x7f, 0x17, 0xe8, 0x9f, 0xba, 0x3b, 0xbd, 0x8a, 0x22, 0x00,
0x1c, 0x18, 0xa0, 0x98, 0x25, 0x9f, 0xa9, 0x0b, 0xd5, 0xc4, 0x85, 0x3c, 0x22, 0x38, 0x71, 0x4d,
0x74, 0x42, 0x62, 0x43, 0x26, 0x04, 0x47, 0xae, 0x81, 0x23, 0x22, 0x05, 0x34, 0x24, 0xba, 0x92,
0x48, 0x25, 0xce, 0x30, 0xbb, 0x49, 0xdc, 0x80, 0x99, 0xbe, 0x3e, 0x90, 0x77, 0x66, 0x35, 0x02,
0x56, 0x62, 0x67, 0x92, 0x4f, 0x58, 0xe9, 0xf5, 0x9c, 0x33, 0x79, 0xa4, 0xd7, 0xf6, 0x93, 0xd8,
0x90, 0x09, 0xd3, 0x2d, 0x27, 0x52, 0xc8, 0x3b, 0x93, 0x48, 0xb5, 0x1f, 0x28, 0xaa, 0x1e, 0xf8,
0x78, 0x9d, 0x98, 0x1e, 0x81, 0x73, 0xdf, 0xa6, 0xeb, 0x26, 0xb6, 0x2c, 0xd2, 0x62, 0xa4, 0xae,
0x6b, 0xdc, 0x1b, 0x0c, 0x3b, 0x60, 0x0d, 0x4d, 0xc7, 0x52, 0xd8, 0x01, 0x81, 0x97, 0x7c, 0x75,
0x43, 0xe3, 0x2c, 0x77, 0x22, 0x13, 0x09, 0x84, 0x45, 0xc5, 0xdc, 0x17, 0xac, 0xf8, 0xcc, 0x24,
0x1a, 0xe6, 0x14, 0x50, 0xc2, 0x20, 0x91, 0x6b, 0x5f, 0x50, 0x87, 0x8a, 0xe4, 0x7c, 0x42, 0xa8,
0x3e, 0xc8, 0x89, 0x2d, 0x1c, 0x84, 0xc6, 0x89, 0x35, 0xb4, 0x42, 0x08, 0xed, 0x84, 0xc6, 0x89,
0xc0, 0x83, 0x5f, 0xdd, 0xd0, 0xe8, 0x8b, 0x09, 0xc1, 0xa7, 0x40, 0x26, 0x51, 0x48, 0x7f, 0xed,
0xee, 0x57, 0xe3, 0xe6, 0x48, 0xcb, 0x13, 0x00, 0x99, 0xf6, 0x1d, 0x45, 0x7d, 0xb2, 0xd8, 0x7b,
0x40, 0xed, 0xfb, 0x01, 0x31, 0xed, 0xba, 0x3e, 0xc4, 0x93, 0x88, 0x37, 0xa3, 0xb1, 0x59, 0xe3,
0xe2, 0x85, 0xb9, 0x68, 0x6c, 0xe2, 0x2f, 0x71, 0x6c, 0x12, 0x85, 0x4a, 0x34, 0x28, 0xc9, 0x67,
0x57, 0xfc, 0x8a, 0x07, 0x25, 0xc1, 0x8a, 0x83, 0x92, 0x68, 0x69, 0x7f, 0x50, 0xd4, 0xc1, 0x12,
0x2f, 0xcf, 0xd1, 0xcf, 0x71, 0x46, 0xdf, 0x80, 0xb5, 0x77, 0x7c, 0x0d, 0xad, 0xa1, 0xc5, 0x4e,
0x68, 0x1c, 0x0f, 0xbc, 0x35, 0xb4, 0xd8, 0x0d, 0x8d, 0x9b, 0x09, 0x11, 0xb4, 0x28, 0xac, 0xae,
0x0d, 0xc6, 0x5a, 0xfe, 0xad, 0xab, 0x57, 0xeb, 0x98, 0xe1, 0x2b, 0xfe, 0x0e, 0xb5, 0xd8, 0x06,
0x14, 0x6b, 0x94, 0xb0, 0xab, 0x94, 0xb4, 0x41, 0x0a, 0x84, 0x63, 0x23, 0xc9, 0x8f, 0xc3, 0xbd,
0xea, 0x23, 0x34, 0xdc, 0xdd, 0xaf, 0x46, 0x2c, 0xd0, 0x40, 0xc1, 0x0f, 0xcf, 0xd1, 0xfe, 0xa9,
0xa8, 0x46, 0xd1, 0x85, 0x96, 0xeb, 0xc3, 0x09, 0xe7, 0x13, 0x2b, 0xf0, 0x88, 0xb3, 0xa3, 0x0f,
0xf3, 0xf0, 0xfb, 0x3d, 0x5e, 0x41, 0xac, 0xa1, 0x65, 0xd7, 0x67, 0x0b, 0x29, 0xd8, 0x09, 0x8d,
0xb3, 0x81, 0x97, 0x97, 0x75, 0x43, 0xe3, 0x99, 0xd8, 0xc9, 0x3c, 0x20, 0xf8, 0xdb, 0xc0, 0x8e,
0xcf, 0x43, 0x72, 0xb9, 0xb5, 0x44, 0x06, 0x99, 0x27, 0x6f, 0x01, 0xf5, 0x42, 0x91, 0x02, 0xba,
0x98, 0x77, 0x2b, 0x8f, 0x6a, 0xff, 0x90, 0x78, 0x68, 0x53, 0x9b, 0xd9, 0x50, 0x47, 0xc0, 0x79,
0x67, 0xfa, 0xfa, 0x08, 0x5f, 0xc5, 0xdf, 0xe5, 0xd5, 0xc3, 0x1a, 0x5a, 0x88, 0xd0, 0x39, 0x00,
0x21, 0x60, 0x9c, 0x09, 0xbc, 0x9c, 0x28, 0x0d, 0x17, 0x05, 0xb9, 0x18, 0x2c, 0x6e, 0x4e, 0xe4,
0x02, 0x78, 0xd1, 0x42, 0x59, 0x04, 0x27, 0x10, 0xb4, 0x82, 0x82, 0xa1, 0x40, 0x01, 0x5d, 0xc8,
0x3b, 0x98, 0x03, 0x35, 0x57, 0x1d, 0xf0, 0x48, 0x74, 0x38, 0xbb, 0xd4, 0x6c, 0xe3, 0x4d, 0x12,
0xb4, 0x74, 0x9d, 0x4f, 0xd9, 0x2c, 0x90, 0x8f, 0xc1, 0x7b, 0xf4, 0x0d, 0x0e, 0xa5, 0xe4, 0x0b,
0xf2, 0x9e, 0x87, 0x74, 0xd1, 0x80, 0xf6, 0x35, 0x45, 0x1d, 0xc1, 0x01, 0x73, 0xcd, 0xa0, 0xb5,
0xee, 0xe1, 0x3a, 0xc9, 0x92, 0xa1, 0x0d, 0xfd, 0x49, 0x3e, 0x90, 0xcb, 0x50, 0x72, 0x81, 0xca,
0x5a, 0xa4, 0x91, 0xe4, 0x11, 0xb7, 0xd3, 0xea, 0x44, 0x06, 0x8a, 0xc3, 0x37, 0x25, 0x66, 0x86,
0x93, 0x53, 0x48, 0x6a, 0x4d, 0x6b, 0xaa, 0x23, 0x09, 0x07, 0xe6, 0x9a, 0x2d, 0x0f, 0xa6, 0x98,
0x9f, 0xc5, 0xbe, 0x7e, 0x9e, 0x0f, 0xc0, 0x0d, 0x20, 0x12, 0xab, 0xac, 0xba, 0xcb, 0x1e, 0x41,
0x31, 0xde, 0x0d, 0x8d, 0xf3, 0xd1, 0x14, 0x4a, 0xc0, 0x0a, 0x92, 0xb6, 0xd1, 0xb6, 0x54, 0x6d,
0x93, 0x90, 0x96, 0xc9, 0x48, 0xb3, 0xe5, 0x7a, 0xd8, 0xb3, 0x89, 0x6f, 0x6e, 0xe8, 0x17, 0xb8,
0xcb, 0xb7, 0x61, 0x23, 0x00, 0xba, 0x9a, 0x81, 0xe0, 0xee, 0x25, 0xde, 0x4b, 0x11, 0x10, 0x6b,
0xb1, 0xeb, 0xa2, 0xab, 0x53, 0xd7, 0x51, 0xc9, 0x8a, 0xb6, 0xa3, 0x0e, 0x5a, 0xd8, 0xda, 0x20,
0xa6, 0xbd, 0x4e, 0x5d, 0x8f, 0xd4, 0xcd, 0x86, 0xed, 0x10, 0x5f, 0xbf, 0xc8, 0x5d, 0x5c, 0x80,
0x13, 0x8d, 0xc3, 0x0b, 0x11, 0x3a, 0x0f, 0x60, 0x3a, 0xd0, 0x25, 0xa4, 0xb4, 0x07, 0xd3, 0xbd,
0x85, 0xca, 0x66, 0xb4, 0x6f, 0x29, 0xea, 0xf9, 0x96, 0xe7, 0xae, 0x43, 0x31, 0x63, 0x06, 0xad,
0x3a, 0x66, 0x44, 0x2c, 0x10, 0x9e, 0xe2, 0xbe, 0xaf, 0x42, 0x7e, 0x9b, 0x68, 0xad, 0x71, 0x25,
0xb1, 0x18, 0x88, 0x8a, 0xec, 0x1e, 0xb8, 0x40, 0xe7, 0x65, 0x61, 0x20, 0x94, 0x97, 0x51, 0x2f,
0x8b, 0xda, 0xbb, 0x8a, 0x3a, 0xec, 0xd8, 0x4d, 0x9b, 0x99, 0x35, 0x4c, 0xeb, 0x6d, 0xbb, 0xce,
0x36, 0x4c, 0x9b, 0x9a, 0x0e, 0xa6, 0xfa, 0x28, 0x1f, 0x92, 0x25, 0x5e, 0x3c, 0x82, 0xc6, 0x4c,
0xa2, 0xb0, 0x40, 0x17, 0x31, 0xcd, 0x0a, 0xfe, 0x32, 0xf6, 0x19, 0xc3, 0x22, 0x33, 0xa5, 0xbd,
0xa3, 0xa8, 0x5a, 0xd3, 0xa6, 0xe6, 0x86, 0xdb, 0x24, 0x66, 0xdd, 0xf6, 0x37, 0xcd, 0x86, 0x47,
0x88, 0x6e, 0x8c, 0x29, 0xe3, 0xa7, 0xa6, 0xfa, 0xae, 0x44, 0x37, 0x6b, 0x57, 0x56, 0xec, 0xb7,
0xc8, 0xcc, 0x6b, 0x1f, 0x85, 0xc6, 0x11, 0xd8, 0x89, 0x4d, 0x9b, 0xde, 0x76, 0x9b, 0x64, 0xce,
0xf6, 0x37, 0xe7, 0x3d, 0x42, 0xd2, 0xd5, 0x51, 0x90, 0x8b, 0xfb, 0x60, 0xec, 0x32, 0x10, 0x39,
0x36, 0x39, 0x76, 0x19, 0x15, 0x9b, 0x6b, 0x0f, 0x14, 0xb5, 0x2f, 0x59, 0xef, 0xfc, 0xd8, 0x19,
0xe3, 0xc7, 0xce, 0xef, 0x79, 0xca, 0x93, 0x2c, 0xda, 0xe8, 0xf0, 0x39, 0xe5, 0x65, 0x9f, 0xdd,
0xd0, 0x98, 0x4b, 0x2a, 0x8e, 0x44, 0x26, 0x39, 0x88, 0xe2, 0x1d, 0xe0, 0x17, 0xce, 0x94, 0x26,
0x61, 0xf8, 0xca, 0xe7, 0x7d, 0x97, 0x42, 0xec, 0xce, 0x99, 0xcd, 0x7f, 0x1e, 0xee, 0x55, 0xc7,
0x1f, 0xd5, 0x14, 0xe4, 0x47, 0x02, 0x5f, 0x94, 0xd9, 0xf1, 0x1c, 0xed, 0x0d, 0x75, 0x00, 0x3b,
0x6d, 0xa8, 0xbe, 0xa2, 0xdb, 0x04, 0x4a, 0x98, 0xaf, 0x3f, 0xcd, 0x2f, 0xf1, 0xa0, 0xe8, 0x3d,
0x13, 0x81, 0xbc, 0x2a, 0xbf, 0x4b, 0x18, 0x2c, 0xfc, 0xa1, 0x28, 0xc2, 0xe4, 0xe4, 0x15, 0x54,
0x54, 0xd4, 0xfe, 0xad, 0xa8, 0xe3, 0xee, 0x16, 0xf1, 0xda, 0x9e, 0xcd, 0x20, 0x70, 0x34, 0x5d,
0x46, 0xcc, 0x3a, 0xd9, 0xb2, 0x2d, 0x62, 0x52, 0xdc, 0x24, 0x3e, 0x84, 0xd3, 0xb8, 0x10, 0xd2,
0x2b, 0xd9, 0xf5, 0xd2, 0xc8, 0xbd, 0xa4, 0x11, 0xe2, 0x6d, 0xe6, 0xc8, 0xd6, 0x5d, 0x50, 0xef,
0x84, 0xc6, 0x25, 0xb7, 0x04, 0xd9, 0x16, 0xe1, 0xe8, 0x3d, 0x3a, 0x1b, 0x99, 0xea, 0x86, 0xc6,
0x2b, 0x9c, 0xe0, 0x23, 0xe8, 0xf6, 0x5e, 0x94, 0x50, 0xc5, 0xf5, 0xe0, 0x81, 0x1e, 0x85, 0x85,
0xf6, 0x25, 0xf5, 0x1c, 0x84, 0x31, 0xd3, 0xa6, 0x75, 0xb2, 0x6d, 0xc2, 0x4a, 0xae, 0x39, 0xae,
0xb5, 0xe9, 0xeb, 0x97, 0xf8, 0x96, 0x86, 0x45, 0xa3, 0x81, 0xc2, 0x02, 0xe0, 0x4b, 0x36, 0x9d,
0xe1, 0x68, 0x7a, 0x6b, 0x5b, 0x86, 0xa4, 0x99, 0x72, 0x94, 0xff, 0x22, 0x89, 0x25, 0xed, 0xef,
0x90, 0xee, 0x52, 0x6c, 0x6d, 0x92, 0xba, 0x49, 0x5d, 0x66, 0x37, 0x6c, 0x0b, 0x47, 0xf7, 0x0f,
0x75, 0x5f, 0xaf, 0xf2, 0xf9, 0x7d, 0x0f, 0x86, 0x7b, 0x78, 0x2d, 0x52, 0xba, 0x2b, 0xe8, 0x2c,
0xcc, 0xc1, 0x68, 0x0f, 0x07, 0x52, 0xa4, 0x1b, 0x1a, 0x17, 0xa2, 0xd0, 0x2e, 0x83, 0xf9, 0x5d,
0xa5, 0x14, 0xe9, 0xee, 0x55, 0x7b, 0x58, 0xdc, 0xdd, 0xaf, 0xf6, 0x60, 0x81, 0xa4, 0x2d, 0xea,
0xbe, 0x86, 0xd4, 0xd3, 0xcc, 0xc3, 0x8d, 0x86, 0x6d, 0x99, 0x96, 0x83, 0x7d, 0x5f, 0xbf, 0xcc,
0x87, 0xf5, 0x25, 0xa8, 0x97, 0x63, 0x60, 0x16, 0xe4, 0xdd, 0xd0, 0xd0, 0xa2, 0x01, 0x15, 0x84,
0xe9, 0x45, 0x4d, 0x4e, 0x55, 0xbb, 0xaf, 0x0e, 0xc6, 0x43, 0x6c, 0x36, 0x5c, 0xa7, 0x4e, 0x3c,
0xb3, 0x85, 0xd9, 0x86, 0xfe, 0x0c, 0xdf, 0xf5, 0xd3, 0x70, 0x0c, 0xc4, 0xf0, 0x3c, 0x47, 0x97,
0x31, 0xdb, 0x48, 0x43, 0x4c, 0x09, 0x11, 0xa6, 0xeb, 0x6d, 0x58, 0x56, 0xca, 0xdb, 0xa8, 0xdc,
0x5c, 0xdb, 0x54, 0xcf, 0xfa, 0x84, 0x99, 0x8e, 0xdb, 0x36, 0x5b, 0x9e, 0xed, 0x7a, 0x36, 0xdb,
0xd1, 0x9f, 0xe5, 0x5b, 0x01, 0xfa, 0xeb, 0xf7, 0x09, 0x5b, 0x74, 0xdb, 0xcb, 0x31, 0x92, 0x76,
0x96, 0x17, 0xf7, 0x4c, 0x2c, 0x0a, 0xcd, 0xb5, 0xf7, 0x15, 0x75, 0xb8, 0x89, 0xb7, 0x13, 0xe7,
0x2c, 0x97, 0x5a, 0x81, 0xe7, 0x11, 0x6a, 0xed, 0xe8, 0xe3, 0x7c, 0xf4, 0x7c, 0x7e, 0xc5, 0x82,
0xdb, 0x4b, 0x78, 0x3b, 0xe2, 0x38, 0x9b, 0xa9, 0xc0, 0x41, 0xdf, 0x94, 0xc8, 0xd3, 0x83, 0x5e,
0x06, 0x26, 0x03, 0xcd, 0xef, 0x44, 0xe4, 0x76, 0x91, 0xd4, 0xaa, 0xf6, 0xb1, 0xa2, 0x0e, 0x5a,
0x1e, 0xf6, 0x37, 0x0a, 0x99, 0xff, 0x73, 0x7c, 0x32, 0x3e, 0xe0, 0x99, 0xff, 0x6c, 0x92, 0xf9,
0x5b, 0x71, 0xe6, 0x3f, 0x1f, 0x9d, 0xc8, 0xd0, 0x2c, 0xcb, 0xc1, 0xa5, 0xc1, 0x97, 0xeb, 0x94,
0xb3, 0x79, 0x2e, 0x86, 0x15, 0x3c, 0x50, 0x32, 0x02, 0x35, 0x81, 0x15, 0xd7, 0x04, 0xd5, 0x47,
0x31, 0x03, 0x55, 0xc1, 0x6c, 0x54, 0x15, 0x14, 0x8c, 0x79, 0x8e, 0xf6, 0x23, 0x45, 0x1d, 0x29,
0xba, 0x97, 0x5c, 0xc6, 0x3c, 0xcf, 0xe7, 0xdf, 0x3e, 0x08, 0x8d, 0x93, 0xb3, 0x48, 0x78, 0x47,
0xc8, 0x5b, 0x29, 0xbe, 0x23, 0x48, 0xd1, 0x5e, 0x4b, 0x63, 0x77, 0xbf, 0x9a, 0xd9, 0x46, 0x72,
0xcb, 0xda, 0x57, 0x14, 0x75, 0xd8, 0x67, 0x01, 0x35, 0x21, 0x5f, 0xc2, 0x8e, 0xbd, 0x45, 0xcc,
0x28, 0x0b, 0xf6, 0xf5, 0x17, 0xd2, 0x2c, 0x74, 0x10, 0x34, 0xee, 0x24, 0x0a, 0x2b, 0x80, 0xaf,
0xa4, 0xb9, 0x91, 0x04, 0xcb, 0xa7, 0xf0, 0x42, 0x18, 0x3b, 0x36, 0x79, 0x73, 0x02, 0xc9, 0xac,
0x41, 0x65, 0x5c, 0xa0, 0x01, 0xd1, 0xd4, 0xd7, 0x5f, 0xe4, 0x24, 0x5e, 0x87, 0x7d, 0x99, 0x6b,
0xb6, 0x64, 0xd3, 0xac, 0x82, 0x28, 0x21, 0x62, 0x66, 0x98, 0x0b, 0xa3, 0x53, 0x13, 0xa8, 0x6c,
0x07, 0x72, 0xf1, 0x3e, 0xde, 0x7b, 0xf2, 0xbc, 0xf5, 0x12, 0x8f, 0x9c, 0xf5, 0x83, 0xd0, 0xe8,
0x47, 0xb8, 0xbd, 0xc2, 0x02, 0xe1, 0x61, 0xeb, 0x94, 0x9f, 0x7d, 0xa6, 0x57, 0x50, 0x99, 0xec,
0xa1, 0x8f, 0x6f, 0x05, 0x8b, 0x48, 0xb4, 0xa7, 0x6d, 0xa9, 0x67, 0xa0, 0xd8, 0xac, 0x61, 0x9f,
0x98, 0xd1, 0x4b, 0xa3, 0x7e, 0x65, 0x4c, 0x19, 0xef, 0x9f, 0xea, 0x4f, 0x92, 0xa1, 0x55, 0x2e,
0xe5, 0x77, 0x86, 0xfd, 0x89, 0x6a, 0x24, 0xcb, 0xc2, 0x54, 0x4e, 0x5c, 0x19, 0x8b, 0x4b, 0x8f,
0x78, 0x79, 0xbc, 0xb3, 0x5f, 0x55, 0x50, 0xa1, 0xa9, 0xf6, 0xed, 0xa3, 0xea, 0x25, 0x88, 0x1a,
0x69, 0xb8, 0x80, 0xd2, 0xd5, 0x72, 0x9b, 0xb0, 0x64, 0x3d, 0x72, 0x3f, 0x20, 0x3e, 0x33, 0x37,
0xed, 0x9a, 0x7e, 0x95, 0x4f, 0xc7, 0x9f, 0x94, 0xf8, 0x85, 0x72, 0x09, 0x6f, 0xcf, 0x2e, 0xa0,
0x08, 0xbf, 0x63, 0xcf, 0x74, 0x42, 0xc3, 0x68, 0xe2, 0xed, 0x74, 0x8b, 0xb3, 0x85, 0xd8, 0x46,
0xa6, 0x92, 0x9e, 0x7d, 0x0f, 0xd1, 0x13, 0xca, 0xbe, 0x87, 0x9a, 0x7c, 0xb8, 0x4a, 0xfc, 0xe6,
0x59, 0xa0, 0x8b, 0x1e, 0xd2, 0xac, 0xa6, 0x7d, 0xaa, 0xa8, 0xc3, 0xe9, 0xc3, 0x8b, 0x83, 0xc5,
0xa7, 0xda, 0x09, 0xbe, 0x81, 0x3f, 0x84, 0x91, 0x18, 0x4a, 0x1e, 0x2e, 0x16, 0xa7, 0xef, 0x8a,
0xaf, 0xb5, 0x43, 0x58, 0x22, 0x4f, 0xd3, 0x67, 0x19, 0x28, 0x7b, 0x2f, 0x93, 0x1a, 0xe9, 0x21,
0x17, 0xb6, 0xbe, 0x94, 0x14, 0xca, 0x5a, 0x61, 0xe1, 0xa9, 0x77, 0x4b, 0x3d, 0xcf, 0xdf, 0x56,
0x1a, 0x81, 0xe3, 0xc4, 0xb9, 0x8c, 0x4b, 0x93, 0xc2, 0x54, 0x9f, 0xe4, 0x9e, 0xde, 0x82, 0x5c,
0x01, 0xb4, 0xe6, 0x03, 0xc7, 0xe1, 0x59, 0xc8, 0x3d, 0x1a, 0x97, 0x92, 0xdd, 0xd0, 0xb8, 0x18,
0x1f, 0x59, 0x32, 0xb8, 0x82, 0x7a, 0xb4, 0xd3, 0x5e, 0x57, 0x4f, 0x37, 0x08, 0x66, 0x81, 0x47,
0xcc, 0x86, 0x83, 0xd7, 0x7d, 0x7d, 0x8a, 0xef, 0xbb, 0xcb, 0x70, 0xbe, 0xc7, 0xc0, 0x3c, 0xc8,
0xd3, 0x77, 0x18, 0x41, 0x58, 0x41, 0x39, 0x15, 0xad, 0xad, 0x8e, 0x08, 0xcf, 0x2f, 0x51, 0x65,
0x43, 0xa8, 0x1b, 0xac, 0x6f, 0xe8, 0xd7, 0xf8, 0xa2, 0x7d, 0x95, 0x87, 0xd7, 0x54, 0x65, 0x11,
0x34, 0x5e, 0xe3, 0x0a, 0x69, 0xae, 0x23, 0x45, 0xd3, 0x3c, 0x42, 0xde, 0x58, 0xdb, 0x54, 0x87,
0x4a, 0x1d, 0x37, 0xf1, 0xb6, 0x7e, 0x9d, 0xf7, 0xfa, 0x0a, 0xa4, 0x80, 0x85, 0x86, 0x4b, 0x78,
0xbb, 0x1b, 0x1a, 0xba, 0xac, 0xcb, 0x25, 0xbc, 0x9d, 0xf6, 0x27, 0x69, 0xa6, 0x7d, 0x51, 0xed,
0x0b, 0x5a, 0xb4, 0x95, 0x1e, 0x23, 0x3f, 0x9b, 0xe7, 0x93, 0xf3, 0x7f, 0x07, 0xa1, 0x71, 0x6e,
0x8e, 0xb4, 0x3c, 0x62, 0x61, 0x46, 0xea, 0x6b, 0xcb, 0x74, 0x39, 0x3b, 0x53, 0x94, 0x97, 0xb2,
0x64, 0xae, 0x45, 0x5b, 0x31, 0xf0, 0xa2, 0xdb, 0xb4, 0x21, 0xa1, 0x64, 0x3b, 0x95, 0xdd, 0xfd,
0xaa, 0xbc, 0xb1, 0xae, 0xa0, 0x53, 0x42, 0x13, 0xed, 0x27, 0x4a, 0xdc, 0x7d, 0x72, 0x55, 0xff,
0xfe, 0x3c, 0x77, 0xf2, 0x1d, 0xbe, 0x0b, 0xf2, 0x26, 0xd2, 0x6b, 0x7b, 0xde, 0xfd, 0x58, 0xda,
0xbd, 0x78, 0xdd, 0x2e, 0x70, 0xc8, 0xb6, 0xfb, 0xf9, 0xde, 0x5a, 0xb0, 0xac, 0x65, 0xbd, 0xe8,
0x0a, 0x52, 0xb3, 0x56, 0xda, 0xaf, 0x14, 0xb5, 0x9f, 0xd3, 0xcc, 0x2e, 0xe5, 0x7f, 0x1e, 0x11,
0xfd, 0x3a, 0xcf, 0x85, 0xf3, 0x26, 0x84, 0x0b, 0x7a, 0x4e, 0xb5, 0x92, 0x52, 0xcd, 0x5f, 0xa9,
0x4b, 0xc9, 0x5e, 0xfc, 0x2c, 0x3d, 0xc8, 0x78, 0xe5, 0x7d, 0xe9, 0x0a, 0xea, 0x13, 0x5b, 0x66,
0x94, 0xb3, 0xab, 0xf7, 0x0f, 0x7a, 0x53, 0x16, 0xae, 0xe1, 0x0b, 0x94, 0xf3, 0x17, 0xe7, 0xbd,
0x29, 0xf7, 0xd2, 0x2b, 0x53, 0x4e, 0x34, 0x13, 0xca, 0xe9, 0x4d, 0x7b, 0x43, 0x8d, 0x9e, 0xf8,
0xd2, 0x43, 0xf3, 0x17, 0xf3, 0x7c, 0xf7, 0xfe, 0x4f, 0x9e, 0x2f, 0x7f, 0x25, 0xcb, 0x4e, 0x4f,
0x61, 0x31, 0x7a, 0x19, 0x22, 0x10, 0x85, 0x7e, 0x04, 0xc4, 0xe7, 0x17, 0x15, 0xe5, 0x3b, 0x02,
0xb3, 0x65, 0x31, 0xfd, 0x43, 0x18, 0x22, 0x65, 0x66, 0xe9, 0x20, 0x34, 0x2e, 0x66, 0x3d, 0x2e,
0xe5, 0x2b, 0xfc, 0x65, 0x8b, 0xe5, 0xc7, 0xa9, 0x59, 0xc2, 0xf3, 0xdd, 0x6b, 0x65, 0x05, 0xc8,
0x10, 0x86, 0x0a, 0xe7, 0xa3, 0x6f, 0x61, 0xea, 0xeb, 0xbf, 0x8c, 0x66, 0x69, 0xb5, 0x40, 0x41,
0x3c, 0x57, 0x56, 0x40, 0xb1, 0x40, 0xa1, 0x84, 0x97, 0xa7, 0x8a, 0x33, 0x29, 0xe9, 0xcd, 0xdc,
0xf9, 0xe8, 0x93, 0xd1, 0x23, 0xfb, 0x9f, 0x8c, 0x1e, 0xf9, 0xe8, 0x60, 0x54, 0xd9, 0x3f, 0x18,
0x55, 0xbe, 0xf9, 0x60, 0xf4, 0xc8, 0x7b, 0x0f, 0x46, 0x95, 0xfd, 0x07, 0xa3, 0x47, 0xfe, 0xf6,
0x60, 0xf4, 0xc8, 0x9b, 0xcf, 0xad, 0xdb, 0x6c, 0x23, 0xa8, 0x5d, 0xb1, 0xdc, 0xe6, 0xd5, 0x34,
0x6b, 0x15, 0x7e, 0x65, 0xff, 0x59, 0xaa, 0x9d, 0xe0, 0x7f, 0x52, 0xba, 0xf6, 0x9f, 0x00, 0x00,
0x00, 0xff, 0xff, 0x43, 0x37, 0xb7, 0x54, 0x10, 0x25, 0x00, 0x00,
}
func (m *OptionsConfiguration) Marshal() (dAtA []byte, err error) {
@@ -419,6 +430,20 @@ func (m *OptionsConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0xc0
}
if m.ConnectionLimitMax != 0 {
i = encodeVarintOptionsconfiguration(dAtA, i, uint64(m.ConnectionLimitMax))
i--
dAtA[i] = 0x3
i--
dAtA[i] = 0xa0
}
if m.ConnectionLimitEnough != 0 {
i = encodeVarintOptionsconfiguration(dAtA, i, uint64(m.ConnectionLimitEnough))
i--
dAtA[i] = 0x3
i--
dAtA[i] = 0x98
}
if len(m.FeatureFlags) > 0 {
for iNdEx := len(m.FeatureFlags) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.FeatureFlags[iNdEx])
@@ -1040,6 +1065,12 @@ func (m *OptionsConfiguration) ProtoSize() (n int) {
n += 2 + l + sovOptionsconfiguration(uint64(l))
}
}
if m.ConnectionLimitEnough != 0 {
n += 2 + sovOptionsconfiguration(uint64(m.ConnectionLimitEnough))
}
if m.ConnectionLimitMax != 0 {
n += 2 + sovOptionsconfiguration(uint64(m.ConnectionLimitMax))
}
if m.DeprecatedUPnPEnabled {
n += 4
}
@@ -2218,6 +2249,44 @@ func (m *OptionsConfiguration) Unmarshal(dAtA []byte) error {
}
m.FeatureFlags = append(m.FeatureFlags, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 51:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ConnectionLimitEnough", wireType)
}
m.ConnectionLimitEnough = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowOptionsconfiguration
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ConnectionLimitEnough |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 52:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ConnectionLimitMax", wireType)
}
m.ConnectionLimitMax = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowOptionsconfiguration
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ConnectionLimitMax |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 9000:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedUPnPEnabled", wireType)

View File

@@ -9,7 +9,6 @@ package config
import (
"os"
"sync/atomic"
"time"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
@@ -85,8 +84,7 @@ type Wrapper interface {
SetDevice(DeviceConfiguration) (Waiter, error)
SetDevices([]DeviceConfiguration) (Waiter, error)
AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string)
AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID)
IgnoredDevices() []ObservedDevice
IgnoredDevice(id protocol.DeviceID) bool
IgnoredFolder(device protocol.DeviceID, folder string) bool
@@ -428,6 +426,15 @@ func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
return false
}
// IgnoredDevices returns a slice of ignored devices.
func (w *wrapper) IgnoredDevices() []ObservedDevice {
w.mut.Lock()
defer w.mut.Unlock()
res := make([]ObservedDevice, len(w.cfg.IgnoredDevices))
copy(res, w.cfg.IgnoredDevices)
return res
}
// IgnoredFolder returns whether or not share attempts for the given
// folder should be silently ignored.
func (w *wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
@@ -495,49 +502,3 @@ func (w *wrapper) RequiresRestart() bool {
func (w *wrapper) setRequiresRestart() {
atomic.StoreUint32(&w.requiresRestart, 1)
}
func (w *wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
w.mut.Lock()
defer w.mut.Unlock()
for i := range w.cfg.PendingDevices {
if w.cfg.PendingDevices[i].ID == device {
w.cfg.PendingDevices[i].Time = time.Now().Round(time.Second)
w.cfg.PendingDevices[i].Name = name
w.cfg.PendingDevices[i].Address = address
return
}
}
w.cfg.PendingDevices = append(w.cfg.PendingDevices, ObservedDevice{
Time: time.Now().Round(time.Second),
ID: device,
Name: name,
Address: address,
})
}
func (w *wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
w.mut.Lock()
defer w.mut.Unlock()
for i := range w.cfg.Devices {
if w.cfg.Devices[i].DeviceID == device {
for j := range w.cfg.Devices[i].PendingFolders {
if w.cfg.Devices[i].PendingFolders[j].ID == id {
w.cfg.Devices[i].PendingFolders[j].Label = label
w.cfg.Devices[i].PendingFolders[j].Time = time.Now().Round(time.Second)
return
}
}
w.cfg.Devices[i].PendingFolders = append(w.cfg.Devices[i].PendingFolders, ObservedFolder{
Time: time.Now().Round(time.Second),
ID: id,
Label: label,
})
return
}
}
panic("bug: adding pending folder for non-existing device")
}

View File

@@ -8,29 +8,39 @@ package connections
import "github.com/syncthing/syncthing/lib/config"
// deprecatedListener is never valid
type deprecatedListener struct {
// invalidListener is never valid
type invalidListener struct {
listenerFactory
err error
}
func (deprecatedListener) Valid(_ config.Configuration) error {
return errDeprecated
func (i invalidListener) Valid(_ config.Configuration) error {
if i.err == nil {
// fallback so we don't accidentally return nil
return errUnsupported
}
return i.err
}
// deprecatedDialer is never valid
type deprecatedDialer struct {
// invalidDialer is never valid
type invalidDialer struct {
dialerFactory
err error
}
func (deprecatedDialer) Valid(_ config.Configuration) error {
return errDeprecated
func (i invalidDialer) Valid(_ config.Configuration) error {
if i.err == nil {
// fallback so we don't accidentally return nil
return errUnsupported
}
return i.err
}
func init() {
listeners["kcp"] = deprecatedListener{}
listeners["kcp4"] = deprecatedListener{}
listeners["kcp6"] = deprecatedListener{}
dialers["kcp"] = deprecatedDialer{}
dialers["kcp4"] = deprecatedDialer{}
dialers["kcp6"] = deprecatedDialer{}
listeners["kcp"] = invalidListener{err: errDeprecated}
listeners["kcp4"] = invalidListener{err: errDeprecated}
listeners["kcp6"] = invalidListener{err: errDeprecated}
dialers["kcp"] = invalidDialer{err: errDeprecated}
dialers["kcp4"] = invalidDialer{err: errDeprecated}
dialers["kcp6"] = invalidDialer{err: errDeprecated}
}

View File

@@ -0,0 +1,54 @@
// Copyright (C) 2021 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 connections
import (
"sort"
"time"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
)
type dialQueueEntry struct {
id protocol.DeviceID
lastSeen time.Time
shortLived bool
targets []dialTarget
}
type dialQueue []dialQueueEntry
func (queue dialQueue) Sort() {
// Sort the queue with the most recently seen device at the head,
// increasing the likelihood of connecting to a device that we're
// already almost up to date with, index wise.
sort.Slice(queue, func(a, b int) bool {
qa, qb := queue[a], queue[b]
if qa.shortLived != qb.shortLived {
return qb.shortLived
}
return qa.lastSeen.After(qb.lastSeen)
})
// Shuffle the part of the connection queue that are devices we haven't
// connected to recently, so that if we only try a limited set of
// devices (or they in turn have limits and we're trying to load balance
// over several) and the usual ones are down it won't be the same ones
// in the same order every time.
idx := 0
cutoff := time.Now().Add(-recentlySeenCutoff)
for idx < len(queue) {
if queue[idx].lastSeen.Before(cutoff) {
break
}
idx++
}
if idx < len(queue)-1 {
rand.Shuffle(queue[idx:])
}
}

View File

@@ -0,0 +1,119 @@
// Copyright (C) 2021 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 connections
import (
"reflect"
"testing"
"time"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestDialQueueSort(t *testing.T) {
t.Parallel()
t.Run("ByLastSeen", func(t *testing.T) {
t.Parallel()
// Devices seen within the last week or so should be sorted stricly in order.
now := time.Now()
queue := dialQueue{
{id: device1, lastSeen: now.Add(-5 * time.Hour)}, // 1
{id: device2, lastSeen: now.Add(-50 * time.Hour)}, // 3
{id: device3, lastSeen: now.Add(-25 * time.Hour)}, // 2
{id: device4, lastSeen: now.Add(-2 * time.Hour)}, // 0
}
expected := []protocol.ShortID{device4.Short(), device1.Short(), device3.Short(), device2.Short()}
queue.Sort()
if !reflect.DeepEqual(shortDevices(queue), expected) {
t.Error("expected different order")
}
})
t.Run("OldConnections", func(t *testing.T) {
t.Parallel()
// Devices seen long ago should be randomized.
now := time.Now()
queue := dialQueue{
{id: device1, lastSeen: now.Add(-5 * time.Hour)}, // 1
{id: device2, lastSeen: now.Add(-50 * 24 * time.Hour)}, // 2, 3
{id: device3, lastSeen: now.Add(-25 * 24 * time.Hour)}, // 2, 3
{id: device4, lastSeen: now.Add(-2 * time.Hour)}, // 0
}
expected1 := []protocol.ShortID{device4.Short(), device1.Short(), device3.Short(), device2.Short()}
expected2 := []protocol.ShortID{device4.Short(), device1.Short(), device2.Short(), device3.Short()}
var seen1, seen2 int
for i := 0; i < 100; i++ {
queue.Sort()
res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) {
seen1++
continue
}
if reflect.DeepEqual(res, expected2) {
seen2++
continue
}
t.Fatal("expected different order")
}
if seen1 < 10 || seen2 < 10 {
t.Error("expected more even distribution", seen1, seen2)
}
})
t.Run("ShortLivedConnections", func(t *testing.T) {
t.Parallel()
// Short lived connections should be sorted as if they were long ago
now := time.Now()
queue := dialQueue{
{id: device1, lastSeen: now.Add(-5 * time.Hour)}, // 1
{id: device2, lastSeen: now.Add(-3 * time.Hour)}, // 0
{id: device3, lastSeen: now.Add(-25 * 24 * time.Hour)}, // 2, 3
{id: device4, lastSeen: now.Add(-2 * time.Hour), shortLived: true}, // 2, 3
}
expected1 := []protocol.ShortID{device2.Short(), device1.Short(), device3.Short(), device4.Short()}
expected2 := []protocol.ShortID{device2.Short(), device1.Short(), device4.Short(), device3.Short()}
var seen1, seen2 int
for i := 0; i < 100; i++ {
queue.Sort()
res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) {
seen1++
continue
}
if reflect.DeepEqual(res, expected2) {
seen2++
continue
}
t.Fatal("expected different order")
}
if seen1 < 10 || seen2 < 10 {
t.Error("expected more even distribution", seen1, seen2)
}
})
}
func shortDevices(queue dialQueue) []protocol.ShortID {
res := make([]protocol.ShortID, len(queue))
for i, qe := range queue {
res[i] = qe.id.Short()
}
return res
}

View File

@@ -4,7 +4,7 @@
// 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/.
// +build go1.12,!noquic
// +build go1.14,!noquic,!go1.16
package connections
@@ -87,7 +87,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
return internalConn{}, errors.Wrap(err, "open stream")
}
return internalConn{&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority}, nil
return newInternalConn(&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority), nil
}
type quicDialerFactory struct {

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.12,!noquic
// +build go1.14,!noquic,!go1.16
package connections
@@ -24,7 +24,7 @@ import (
"github.com/syncthing/syncthing/lib/connections/registry"
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/stun"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
func init() {
@@ -35,7 +35,7 @@ func init() {
}
type quicListener struct {
util.ServiceWithError
svcutil.ServiceWithError
nat atomic.Value
onAddressesChangedNotifier
@@ -150,7 +150,7 @@ func (t *quicListener) serve(ctx context.Context) error {
continue
}
t.conns <- internalConn{&quicTlsConn{session, stream, nil}, connTypeQUICServer, quicPriority}
t.conns <- newInternalConn(&quicTlsConn{session, stream, nil}, connTypeQUICServer, quicPriority)
}
}
@@ -205,7 +205,7 @@ func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.
conns: conns,
factory: f,
}
l.ServiceWithError = util.AsService(l.serve, l.String())
l.ServiceWithError = svcutil.AsService(l.serve, l.String())
l.nat.Store(stun.NATUnknown)
return l
}

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.12,!noquic
// +build go1.14,!noquic,!go1.16
package connections

View File

@@ -0,0 +1,16 @@
// Copyright (C) 2020 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build noquic !go1.14 go1.16
package connections
func init() {
for _, scheme := range []string{"quic", "quic4", "quic6"} {
listeners[scheme] = invalidListener{err: errNotInBuild}
dialers[scheme] = invalidDialer{err: errNotInBuild}
}
}

View File

@@ -63,7 +63,7 @@ func (d *relayDialer) Dial(ctx context.Context, id protocol.DeviceID, uri *url.U
return internalConn{}, err
}
return internalConn{tc, connTypeRelayClient, relayPriority}, nil
return newInternalConn(tc, connTypeRelayClient, relayPriority), nil
}
type relayDialerFactory struct{}

View File

@@ -19,7 +19,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"
"github.com/syncthing/syncthing/lib/svcutil"
)
func init() {
@@ -30,7 +30,7 @@ func init() {
}
type relayListener struct {
util.ServiceWithError
svcutil.ServiceWithError
onAddressesChangedNotifier
uri *url.URL
@@ -105,7 +105,7 @@ func (t *relayListener) serve(ctx context.Context) error {
continue
}
t.conns <- internalConn{tc, connTypeRelayServer, relayPriority}
t.conns <- newInternalConn(tc, connTypeRelayServer, relayPriority)
// Poor mans notifier that informs the connection service that the
// relay URI has changed. This can only happen when we connect to a
@@ -184,7 +184,7 @@ func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls
conns: conns,
factory: f,
}
t.ServiceWithError = util.AsService(t.serve, t.String())
t.ServiceWithError = svcutil.AsService(t.serve, t.String())
return t
}

View File

@@ -10,6 +10,7 @@ import (
"context"
"crypto/tls"
"fmt"
"math"
"net"
"net/url"
"sort"
@@ -23,6 +24,7 @@ import (
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
@@ -41,14 +43,26 @@ var (
)
var (
errDisabled = errors.New("disabled by configuration")
errDeprecated = errors.New("deprecated protocol")
// Dialers and listeners return errUnsupported (or a wrapped variant)
// when they are intentionally out of service due to configuration,
// build, etc. This is not logged loudly.
errUnsupported = errors.New("unsupported protocol")
// These are specific explanations for errUnsupported.
errDisabled = fmt.Errorf("%w: disabled by configuration", errUnsupported)
errDeprecated = fmt.Errorf("%w: deprecated", errUnsupported)
errNotInBuild = fmt.Errorf("%w: disabled at build time", errUnsupported)
)
const (
perDeviceWarningIntv = 15 * time.Minute
tlsHandshakeTimeout = 10 * time.Second
minConnectionReplaceAge = 10 * time.Second
perDeviceWarningIntv = 15 * time.Minute
tlsHandshakeTimeout = 10 * time.Second
minConnectionReplaceAge = 10 * time.Second
minConnectionLoopSleep = 5 * time.Second
stdConnectionLoopSleep = time.Minute
worstDialerPriority = math.MaxInt32
recentlySeenCutoff = 7 * 24 * time.Hour
shortLivedConnectionThreshold = 5 * time.Second
)
// From go/src/crypto/tls/cipher_suites.go
@@ -132,10 +146,7 @@ type service struct {
}
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger) Service {
spec := util.Spec()
spec.EventHook = func(e suture.Event) {
l.Infoln(e)
}
spec := svcutil.SpecWithInfoLogger(l)
service := &service{
Supervisor: suture.New("connections.Service", spec),
connectionStatusHandler: newConnectionStatusHandler(),
@@ -183,12 +194,12 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
// the common handling regardless of whether the connection was
// incoming or outgoing.
service.Add(util.AsService(service.connect, fmt.Sprintf("%s/connect", service)))
service.Add(util.AsService(service.handle, fmt.Sprintf("%s/handle", service)))
service.Add(svcutil.AsService(service.connect, fmt.Sprintf("%s/connect", service)))
service.Add(svcutil.AsService(service.handle, fmt.Sprintf("%s/handle", service)))
service.Add(service.listenerSupervisor)
service.Add(service.natService)
util.OnSupervisorDone(service.Supervisor, func() {
svcutil.OnSupervisorDone(service.Supervisor, func() {
service.cfg.Unsubscribe(service.limiter)
service.cfg.Unsubscribe(service)
})
@@ -325,187 +336,254 @@ func (s *service) handle(ctx context.Context) error {
var protoConn protocol.Connection
passwords := s.cfg.FolderPasswords(remoteID)
if len(passwords) > 0 {
protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, c, s.model, c, deviceCfg.Compression)
} else {
protoConn = protocol.NewConnection(remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
protoConn = protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression)
}
modelConn := completeConn{c, protoConn}
l.Infof("Established secure connection to %s at %s", remoteID, c)
s.model.AddConnection(modelConn, hello)
s.model.AddConnection(protoConn, hello)
continue
}
return nil
}
func (s *service) connect(ctx context.Context) error {
nextDial := make(map[string]time.Time)
// Map of when to earliest dial each given device + address again
nextDialAt := make(map[string]time.Time)
// Used as delay for the first few connection attempts, increases
// exponentially
// Used as delay for the first few connection attempts (adjusted up to
// minConnectionLoopSleep), increased exponentially until it reaches
// stdConnectionLoopSleep, at which time the normal sleep mechanism
// kicks in.
initialRampup := time.Second
// Calculated from actual dialers reconnectInterval
var sleep time.Duration
for {
cfg := s.cfg.RawCopy()
bestDialerPriority := s.bestDialerPriority(cfg)
isInitialRampup := initialRampup < stdConnectionLoopSleep
bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
for _, df := range dialers {
if df.Valid(cfg) != nil {
continue
}
if prio := df.Priority(); prio < bestDialerPrio {
bestDialerPrio = prio
}
l.Debugln("Connection loop")
if isInitialRampup {
l.Debugln("Connection loop in initial rampup")
}
l.Debugln("Reconnect loop")
// Used for consistency throughout this loop run, as time passes
// while we try connections etc.
now := time.Now()
var seen []string
for _, deviceCfg := range cfg.Devices {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// Attempt to dial all devices that are unconnected or can be connection-upgraded
s.dialDevices(ctx, now, cfg, bestDialerPriority, nextDialAt, isInitialRampup)
deviceID := deviceCfg.DeviceID
if deviceID == s.myID {
continue
}
if deviceCfg.Paused {
continue
}
ct, connected := s.model.Connection(deviceID)
if connected && ct.Priority() == bestDialerPrio {
// Things are already as good as they can get.
continue
}
var addrs []string
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if s.discoverer != nil {
if t, err := s.discoverer.Lookup(ctx, deviceID); err == nil {
addrs = append(addrs, t...)
}
}
} else {
addrs = append(addrs, addr)
}
}
addrs = util.UniqueTrimmedStrings(addrs)
l.Debugln("Reconnect loop for", deviceID, addrs)
dialTargets := make([]dialTarget, 0)
for _, addr := range addrs {
// Use a special key that is more than just the address, as you might have two devices connected to the same relay
nextDialKey := deviceID.String() + "/" + addr
seen = append(seen, nextDialKey)
nextDialAt, ok := nextDial[nextDialKey]
if ok && initialRampup >= sleep && nextDialAt.After(now) {
l.Debugf("Not dialing %s via %v as sleep is %v, next dial is at %s and current time is %s", deviceID, addr, sleep, nextDialAt, now)
continue
}
// If we fail at any step before actually getting the dialer
// retry in a minute
nextDial[nextDialKey] = now.Add(time.Minute)
uri, err := url.Parse(addr)
if err != nil {
s.setConnectionStatus(addr, err)
l.Infof("Parsing dialer address %s: %v", addr, err)
continue
}
if len(deviceCfg.AllowedNetworks) > 0 {
if !IsAllowedNetwork(uri.Host, deviceCfg.AllowedNetworks) {
s.setConnectionStatus(addr, errors.New("network disallowed"))
l.Debugln("Network for", uri, "is disallowed")
continue
}
}
dialerFactory, err := getDialerFactory(cfg, uri)
if err != nil {
s.setConnectionStatus(addr, err)
}
switch err {
case nil:
// all good
case errDisabled:
l.Debugln("Dialer for", uri, "is disabled")
continue
case errDeprecated:
l.Debugln("Dialer for", uri, "is deprecated")
continue
default:
l.Infof("Dialer for %v: %v", uri, err)
continue
}
priority := dialerFactory.Priority()
if connected && priority >= ct.Priority() {
l.Debugf("Not dialing using %s as priority is less than current connection (%d >= %d)", dialerFactory, dialerFactory.Priority(), ct.Priority())
continue
}
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg)
nextDial[nextDialKey] = now.Add(dialer.RedialFrequency())
// For LAN addresses, increase the priority so that we
// try these first.
switch {
case dialerFactory.AlwaysWAN():
// Do nothing.
case s.isLANHost(uri.Host):
priority -= 1
}
dialTargets = append(dialTargets, dialTarget{
addr: addr,
dialer: dialer,
priority: priority,
deviceID: deviceID,
uri: uri,
})
}
conn, ok := s.dialParallel(ctx, deviceCfg.DeviceID, dialTargets)
if ok {
s.conns <- conn
}
}
nextDial, sleep = filterAndFindSleepDuration(nextDial, seen, now)
if initialRampup < sleep {
l.Debugln("initial rampup; sleep", initialRampup, "and update to", initialRampup*2)
var sleep time.Duration
if isInitialRampup {
// We are in the initial rampup time, so we slowly, statically
// increase the sleep time.
sleep = initialRampup
initialRampup *= 2
} else {
l.Debugln("sleep until next dial", sleep)
// The sleep time is until the next dial scheduled in nextDialAt,
// clamped by stdConnectionLoopSleep as we don't want to sleep too
// long (config changes might happen).
sleep = filterAndFindSleepDuration(nextDialAt, now)
}
// ... while making sure not to loop too quickly either.
if sleep < minConnectionLoopSleep {
sleep = minConnectionLoopSleep
}
l.Debugln("Next connection loop in", sleep)
select {
case <-time.After(sleep):
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
func (s *service) bestDialerPriority(cfg config.Configuration) int {
bestDialerPriority := worstDialerPriority
for _, df := range dialers {
if df.Valid(cfg) != nil {
continue
}
if prio := df.Priority(); prio < bestDialerPriority {
bestDialerPriority = prio
}
}
return bestDialerPriority
}
func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Configuration, bestDialerPriority int, nextDialAt map[string]time.Time, initial bool) {
// Figure out current connection limits up front to see if there's any
// point in resolving devices and such at all.
allowAdditional := 0 // no limit
connectionLimit := cfg.Options.LowestConnectionLimit()
if connectionLimit > 0 {
current := s.model.NumConnections()
allowAdditional = connectionLimit - current
if allowAdditional <= 0 {
l.Debugf("Skipping dial because we've reached the connection limit, current %d >= limit %d", current, connectionLimit)
return
}
}
// Get device statistics for the last seen time of each device. This
// isn't critical, so ignore the potential error.
stats, _ := s.model.DeviceStatistics()
queue := make(dialQueue, 0, len(cfg.Devices))
for _, deviceCfg := range cfg.Devices {
// Don't attempt to connect to ourselves...
if deviceCfg.DeviceID == s.myID {
continue
}
// Don't attempt to connect to paused devices...
if deviceCfg.Paused {
continue
}
// See if we are already connected and, if so, what our cutoff is
// for dialer priority.
priorityCutoff := worstDialerPriority
connection, connected := s.model.Connection(deviceCfg.DeviceID)
if connected {
priorityCutoff = connection.Priority()
if bestDialerPriority >= priorityCutoff {
// Our best dialer is not any better than what we already
// have, so nothing to do here.
continue
}
}
dialTargets := s.resolveDialTargets(ctx, now, cfg, deviceCfg, nextDialAt, initial, priorityCutoff)
if len(dialTargets) > 0 {
queue = append(queue, dialQueueEntry{
id: deviceCfg.DeviceID,
lastSeen: stats[deviceCfg.DeviceID].LastSeen,
shortLived: stats[deviceCfg.DeviceID].LastConnectionDurationS < shortLivedConnectionThreshold.Seconds(),
targets: dialTargets,
})
}
}
// Sort the queue in an order we think will be useful (most recent
// first, deprioriting unstable devices, randomizing those we haven't
// seen in a long while). If we don't do connection limiting the sorting
// doesn't have much effect, but it may result in getting up and running
// quicker if only a subset of configured devices are actually reachable
// (by prioritizing those that were reachable recently).
dialQueue.Sort(queue)
// Perform dials according to the queue, stopping when we've reached the
// allowed additional number of connections (if limited).
numConns := 0
for _, entry := range queue {
if conn, ok := s.dialParallel(ctx, entry.id, entry.targets); ok {
s.conns <- conn
numConns++
if allowAdditional > 0 && numConns >= allowAdditional {
break
}
}
}
}
func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg config.Configuration, deviceCfg config.DeviceConfiguration, nextDialAt map[string]time.Time, initial bool, priorityCutoff int) []dialTarget {
deviceID := deviceCfg.DeviceID
addrs := s.resolveDeviceAddrs(ctx, deviceCfg)
l.Debugln("Resolved device", deviceID, "addresses:", addrs)
dialTargets := make([]dialTarget, 0, len(addrs))
for _, addr := range addrs {
// Use a special key that is more than just the address, as you
// might have two devices connected to the same relay
nextDialKey := deviceID.String() + "/" + addr
when, ok := nextDialAt[nextDialKey]
if ok && !initial && when.After(now) {
l.Debugf("Not dialing %s via %v as it's not time yet", deviceID, addr)
continue
}
// If we fail at any step before actually getting the dialer
// retry in a minute
nextDialAt[nextDialKey] = now.Add(time.Minute)
uri, err := url.Parse(addr)
if err != nil {
s.setConnectionStatus(addr, err)
l.Infof("Parsing dialer address %s: %v", addr, err)
continue
}
if len(deviceCfg.AllowedNetworks) > 0 {
if !IsAllowedNetwork(uri.Host, deviceCfg.AllowedNetworks) {
s.setConnectionStatus(addr, errors.New("network disallowed"))
l.Debugln("Network for", uri, "is disallowed")
continue
}
}
dialerFactory, err := getDialerFactory(cfg, uri)
if err != nil {
s.setConnectionStatus(addr, err)
}
if errors.Is(err, errUnsupported) {
l.Debugf("Dialer for %v: %v", uri, err)
continue
} else if err != nil {
l.Infof("Dialer for %v: %v", uri, err)
continue
}
priority := dialerFactory.Priority()
if priority >= priorityCutoff {
l.Debugf("Not dialing using %s as priority is not better than current connection (%d >= %d)", dialerFactory, dialerFactory.Priority(), priorityCutoff)
continue
}
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg)
nextDialAt[nextDialKey] = now.Add(dialer.RedialFrequency())
// For LAN addresses, increase the priority so that we
// try these first.
switch {
case dialerFactory.AlwaysWAN():
// Do nothing.
case s.isLANHost(uri.Host):
priority--
}
dialTargets = append(dialTargets, dialTarget{
addr: addr,
dialer: dialer,
priority: priority,
deviceID: deviceID,
uri: uri,
})
}
return dialTargets
}
func (s *service) resolveDeviceAddrs(ctx context.Context, cfg config.DeviceConfiguration) []string {
var addrs []string
for _, addr := range cfg.Addresses {
if addr == "dynamic" {
if s.discoverer != nil {
if t, err := s.discoverer.Lookup(ctx, cfg.DeviceID); err == nil {
addrs = append(addrs, t...)
}
}
} else {
addrs = append(addrs, addr)
}
}
return util.UniqueTrimmedStrings(addrs)
}
func (s *service) isLANHost(host string) bool {
@@ -634,16 +712,10 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
}
factory, err := getListenerFactory(to, uri)
switch err {
case nil:
// all good
case errDisabled:
l.Debugln("Listener for", uri, "is disabled")
if errors.Is(err, errUnsupported) {
l.Debugf("Listener for %v: %v", uri, err)
continue
case errDeprecated:
l.Debugln("Listener for", uri, "is deprecated")
continue
default:
} else if err != nil {
l.Infof("Listener for %v: %v", uri, err)
continue
}
@@ -789,24 +861,19 @@ func getListenerFactory(cfg config.Configuration, uri *url.URL) (listenerFactory
return listenerFactory, nil
}
func filterAndFindSleepDuration(nextDial map[string]time.Time, seen []string, now time.Time) (map[string]time.Time, time.Duration) {
newNextDial := make(map[string]time.Time)
for _, addr := range seen {
nextDialAt, ok := nextDial[addr]
if ok {
newNextDial[addr] = nextDialAt
func filterAndFindSleepDuration(nextDialAt map[string]time.Time, now time.Time) time.Duration {
sleep := stdConnectionLoopSleep
for key, next := range nextDialAt {
if next.Before(now) {
// Expired entry, address was not seen in last pass(es)
delete(nextDialAt, key)
continue
}
if cur := next.Sub(now); cur < sleep {
sleep = cur
}
}
min := time.Minute
for _, next := range newNextDial {
cur := next.Sub(now)
if cur < min {
min = cur
}
}
return newNextDial, min
return sleep
}
func urlsToStrings(urls []*url.URL) []string {

View File

@@ -18,35 +18,11 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/stats"
"github.com/thejerf/suture/v4"
)
// Connection is what we expose to the outside. It is a protocol.Connection
// that can be closed and has some metadata.
type Connection interface {
protocol.Connection
Type() string
Transport() string
RemoteAddr() net.Addr
Priority() int
String() string
Crypto() string
}
// completeConn is the aggregation of an internalConn and the
// protocol.Connection running on top of it. It implements the Connection
// interface.
type completeConn struct {
internalConn
protocol.Connection
}
func (c completeConn) Close(err error) {
c.Connection.Close(err)
c.internalConn.Close()
}
type tlsConn interface {
io.ReadWriteCloser
ConnectionState() tls.ConnectionState
@@ -60,8 +36,9 @@ type tlsConn interface {
// came from (type, priority).
type internalConn struct {
tlsConn
connType connType
priority int
connType connType
priority int
establishedAt time.Time
}
type connType int
@@ -107,12 +84,21 @@ func (t connType) Transport() string {
}
}
func (c internalConn) Close() {
func newInternalConn(tc tlsConn, connType connType, priority int) internalConn {
return internalConn{
tlsConn: tc,
connType: connType,
priority: priority,
establishedAt: time.Now(),
}
}
func (c internalConn) Close() error {
// *tls.Conn.Close() does more than it says on the tin. Specifically, it
// sends a TLS alert message, which might block forever if the
// connection is dead and we don't have a deadline set.
_ = c.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
_ = c.tlsConn.Close()
return c.tlsConn.Close()
}
func (c internalConn) Type() string {
@@ -144,6 +130,10 @@ func (c internalConn) Transport() string {
return transport + "6"
}
func (c internalConn) EstablishedAt() time.Time {
return c.establishedAt
}
func (c internalConn) String() string {
return fmt.Sprintf("%s-%s/%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto())
}
@@ -203,10 +193,12 @@ type genericListener interface {
type Model interface {
protocol.Model
AddConnection(conn Connection, hello protocol.Hello)
Connection(remoteID protocol.DeviceID) (Connection, bool)
AddConnection(conn protocol.Connection, hello protocol.Hello)
NumConnections() int
Connection(remoteID protocol.DeviceID) (protocol.Connection, bool)
OnHello(protocol.DeviceID, net.Addr, protocol.Hello) error
GetHello(protocol.DeviceID) protocol.HelloIntf
DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error)
}
type onAddressesChangedNotifier struct {

View File

@@ -57,7 +57,7 @@ func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL)
return internalConn{}, err
}
return internalConn{tc, connTypeTCPClient, tcpPriority}, nil
return newInternalConn(tc, connTypeTCPClient, tcpPriority), nil
}
type tcpDialerFactory struct{}

View File

@@ -18,7 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/connections/registry"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
func init() {
@@ -29,7 +29,7 @@ func init() {
}
type tcpListener struct {
util.ServiceWithError
svcutil.ServiceWithError
onAddressesChangedNotifier
uri *url.URL
@@ -137,7 +137,7 @@ func (t *tcpListener) serve(ctx context.Context) error {
continue
}
t.conns <- internalConn{tc, connTypeTCPServer, tcpPriority}
t.conns <- newInternalConn(tc, connTypeTCPServer, tcpPriority)
}
}
@@ -207,7 +207,7 @@ func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.C
natService: natService,
factory: f,
}
l.ServiceWithError = util.AsService(l.serve, l.String())
l.ServiceWithError = svcutil.AsService(l.serve, l.String())
return l
}

View File

@@ -11,7 +11,6 @@ import (
"testing"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -44,11 +43,11 @@ func lazyInitBenchFiles() {
}
}
func getBenchFileSet() (*db.Lowlevel, *db.FileSet) {
func getBenchFileSet(b testing.TB) (*db.Lowlevel, *db.FileSet) {
lazyInitBenchFiles()
ldb := db.NewLowlevel(backend.OpenMemory())
benchS := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
ldb := newLowlevelMemory(b)
benchS := newFileSet(b, "test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
replace(benchS, remoteDevice0, files)
replace(benchS, protocol.LocalDeviceID, firstHalf)
@@ -56,12 +55,12 @@ func getBenchFileSet() (*db.Lowlevel, *db.FileSet) {
}
func BenchmarkReplaceAll(b *testing.B) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(b)
defer ldb.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(b, "test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
replace(m, protocol.LocalDeviceID, files)
}
@@ -69,7 +68,7 @@ func BenchmarkReplaceAll(b *testing.B) {
}
func BenchmarkUpdateOneChanged(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
changed := make([]protocol.FileInfo, 1)
@@ -89,7 +88,7 @@ func BenchmarkUpdateOneChanged(b *testing.B) {
}
func BenchmarkUpdate100Changed(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -118,7 +117,7 @@ func setup10Remotes(benchS *db.FileSet) {
}
func BenchmarkUpdate100Changed10Remotes(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
setup10Remotes(benchS)
@@ -136,7 +135,7 @@ func BenchmarkUpdate100Changed10Remotes(b *testing.B) {
}
func BenchmarkUpdate100ChangedRemote(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -152,7 +151,7 @@ func BenchmarkUpdate100ChangedRemote(b *testing.B) {
}
func BenchmarkUpdate100ChangedRemote10Remotes(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -168,7 +167,7 @@ func BenchmarkUpdate100ChangedRemote10Remotes(b *testing.B) {
}
func BenchmarkUpdateOneUnchanged(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -180,7 +179,7 @@ func BenchmarkUpdateOneUnchanged(b *testing.B) {
}
func BenchmarkNeedHalf(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -201,9 +200,9 @@ func BenchmarkNeedHalf(b *testing.B) {
}
func BenchmarkNeedHalfRemote(b *testing.B) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(b)
defer ldb.Close()
fset := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
fset := newFileSet(b, "test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
replace(fset, remoteDevice0, firstHalf)
replace(fset, protocol.LocalDeviceID, files)
@@ -225,7 +224,7 @@ func BenchmarkNeedHalfRemote(b *testing.B) {
}
func BenchmarkHave(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -246,7 +245,7 @@ func BenchmarkHave(b *testing.B) {
}
func BenchmarkGlobal(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -267,7 +266,7 @@ func BenchmarkGlobal(b *testing.B) {
}
func BenchmarkNeedHalfTruncated(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -288,7 +287,7 @@ func BenchmarkNeedHalfTruncated(b *testing.B) {
}
func BenchmarkHaveTruncated(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -309,7 +308,7 @@ func BenchmarkHaveTruncated(b *testing.B) {
}
func BenchmarkGlobalTruncated(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
b.ResetTimer()
@@ -330,7 +329,7 @@ func BenchmarkGlobalTruncated(b *testing.B) {
}
func BenchmarkNeedCount(b *testing.B) {
ldb, benchS := getBenchFileSet()
ldb, benchS := getBenchFileSet(b)
defer ldb.Close()
benchS.Update(protocol.LocalDeviceID, changed100)

View File

@@ -10,7 +10,6 @@ import (
"encoding/binary"
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -36,10 +35,9 @@ func init() {
}
}
func setup() (*Lowlevel, *BlockFinder) {
// Setup
db := NewLowlevel(backend.OpenMemory())
func setup(t testing.TB) (*Lowlevel, *BlockFinder) {
t.Helper()
db := newLowlevelMemory(t)
return db, NewBlockFinder(db)
}
@@ -105,7 +103,7 @@ func discardFromBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) er
}
func TestBlockMapAddUpdateWipe(t *testing.T) {
db, f := setup()
db, f := setup(t)
defer db.Close()
if !dbEmpty(db) {
@@ -193,7 +191,7 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
}
func TestBlockFinderLookup(t *testing.T) {
db, f := setup()
db, f := setup(t)
defer db.Close()
folder1 := []byte("folder1")

View File

@@ -13,6 +13,7 @@ import (
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -35,17 +36,17 @@ func TestIgnoredFiles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
db := NewLowlevel(ldb)
db := newLowlevel(t, ldb)
defer db.Close()
if err := UpdateSchema(db); err != nil {
t.Fatal(err)
}
fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// The contents of the database are like this:
//
// fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// fs.Update(protocol.LocalDeviceID, []protocol.FileInfo{
// { // invalid (ignored) file
// Name: "foo",
@@ -164,7 +165,7 @@ func TestUpdate0to3(t *testing.T) {
t.Fatal(err)
}
db := NewLowlevel(ldb)
db := newLowlevel(t, ldb)
defer db.Close()
updater := schemaUpdater{db}
@@ -293,7 +294,7 @@ func TestUpdate0to3(t *testing.T) {
// TestRepairSequence checks that a few hand-crafted messed-up sequence entries get fixed.
func TestRepairSequence(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
folderStr := "test"
@@ -397,7 +398,7 @@ func TestRepairSequence(t *testing.T) {
// Loading the metadata for the first time means a "re"calculation happens,
// along which the sequences get repaired too.
db.gcMut.RLock()
_ = db.loadMetadataTracker(folderStr)
_, err = db.loadMetadataTracker(folderStr)
db.gcMut.RUnlock()
if err != nil {
t.Fatal(err)
@@ -466,7 +467,7 @@ func TestRepairSequence(t *testing.T) {
}
func TestDowngrade(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
// sets the min version etc
if err := UpdateSchema(db); err != nil {
@@ -491,10 +492,10 @@ func TestDowngrade(t *testing.T) {
}
func TestCheckGlobals(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
// Add any file
name := "foo"
@@ -532,14 +533,17 @@ func TestUpdateTo10(t *testing.T) {
if err != nil {
t.Fatal(err)
}
db := NewLowlevel(ldb)
db := newLowlevel(t, ldb)
defer db.Close()
UpdateSchema(db)
folder := "test"
meta := db.getMetaAndCheck(folder)
meta, err := db.getMetaAndCheck(folder)
if err != nil {
t.Fatal(err)
}
empty := Counts{}
@@ -643,9 +647,9 @@ func TestDropDuplicates(t *testing.T) {
func TestGCIndirect(t *testing.T) {
// Verify that the gcIndirect run actually removes block lists.
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
meta := newMetadataTracker(db.keyer)
meta := newMetadataTracker(db.keyer, events.NoopLogger)
// Add three files with different block lists
@@ -731,7 +735,7 @@ func TestGCIndirect(t *testing.T) {
}
func TestUpdateTo14(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
folderStr := "default"
@@ -741,7 +745,10 @@ func TestUpdateTo14(t *testing.T) {
file.BlocksHash = protocol.BlocksHash(file.Blocks)
fileWOBlocks := file
fileWOBlocks.Blocks = nil
meta := db.loadMetadataTracker(folderStr)
meta, err := db.loadMetadataTracker(folderStr)
if err != nil {
t.Fatal(err)
}
// Initally add the correct file the usual way, all good here.
if err := db.updateLocalFiles(folder, []protocol.FileInfo{file}, meta); err != nil {
@@ -800,7 +807,7 @@ func TestFlushRecursion(t *testing.T) {
// Verify that a commit hook can write to the transaction without
// causing another flush and thus recursion.
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
// A commit hook that writes a small piece of data to the transaction.
@@ -838,11 +845,11 @@ func TestFlushRecursion(t *testing.T) {
}
func TestCheckLocalNeed(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
folderStr := "test"
fs := NewFileSet(folderStr, fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
fs := newFileSet(t, folderStr, fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
// Add files such that we are in sync for a and b, and need c and d.
files := []protocol.FileInfo{
@@ -913,13 +920,13 @@ func TestCheckLocalNeed(t *testing.T) {
}
func TestDuplicateNeedCount(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
folder := "test"
testFs := fs.NewFilesystem(fs.FilesystemTypeFake, "")
fs := NewFileSet(folder, testFs, db)
fs := newFileSet(t, folder, testFs, db)
files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID), Sequence: 1}}
fs.Update(protocol.LocalDeviceID, files)
files[0].Version = files[0].Version.Update(remoteDevice0.Short())
@@ -927,7 +934,7 @@ func TestDuplicateNeedCount(t *testing.T) {
db.checkRepair()
fs = NewFileSet(folder, testFs, db)
fs = newFileSet(t, folder, testFs, db)
found := false
for _, c := range fs.meta.counts.Counts {
if bytes.Equal(protocol.LocalDeviceID[:], c.DeviceID) && c.LocalFlags == needFlag {

View File

@@ -68,6 +68,12 @@ const (
// KeyTypeVersion <version hash> = Vector
KeyTypeVersion byte = 15
// KeyTypePendingFolder <int32 device ID> <folder ID as string> = ObservedFolder
KeyTypePendingFolder byte = 16
// KeyTypePendingDevice <device ID in wire format> = ObservedDevice
KeyTypePendingDevice byte = 17
)
type keyer interface {
@@ -96,6 +102,7 @@ type keyer interface {
// index IDs
GenerateIndexIDKey(key, device, folder []byte) (indexIDKey, error)
FolderFromIndexIDKey(key []byte) ([]byte, bool)
// Mtimes
GenerateMtimesKey(key, folder []byte) (mtimesKey, error)
@@ -108,6 +115,14 @@ type keyer interface {
// Version vectors
GenerateVersionKey(key []byte, hash []byte) versionKey
// Pending (unshared) folders and devices
GeneratePendingFolderKey(key, device, folder []byte) (pendingFolderKey, error)
FolderFromPendingFolderKey(key []byte) []byte
DeviceFromPendingFolderKey(key []byte) ([]byte, bool)
GeneratePendingDeviceKey(key, device []byte) pendingDeviceKey
DeviceFromPendingDeviceKey(key []byte) []byte
}
// defaultKeyer implements our key scheme. It needs folder and device
@@ -289,6 +304,10 @@ func (k defaultKeyer) GenerateIndexIDKey(key, device, folder []byte) (indexIDKey
return key, nil
}
func (k defaultKeyer) FolderFromIndexIDKey(key []byte) ([]byte, bool) {
return k.folderIdx.Val(binary.BigEndian.Uint32(key[keyPrefixLen+keyDeviceLen:]))
}
type mtimesKey []byte
func (k defaultKeyer) GenerateMtimesKey(key, folder []byte) (mtimesKey, error) {
@@ -341,6 +360,41 @@ func (k versionKey) Hash() []byte {
return k[keyPrefixLen:]
}
type pendingFolderKey []byte
func (k defaultKeyer) GeneratePendingFolderKey(key, device, folder []byte) (pendingFolderKey, error) {
deviceID, err := k.deviceIdx.ID(device)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyDeviceLen+len(folder))
key[0] = KeyTypePendingFolder
binary.BigEndian.PutUint32(key[keyPrefixLen:], deviceID)
copy(key[keyPrefixLen+keyDeviceLen:], folder)
return key, nil
}
func (k defaultKeyer) FolderFromPendingFolderKey(key []byte) []byte {
return key[keyPrefixLen+keyDeviceLen:]
}
func (k defaultKeyer) DeviceFromPendingFolderKey(key []byte) ([]byte, bool) {
return k.deviceIdx.Val(binary.BigEndian.Uint32(key[keyPrefixLen:]))
}
type pendingDeviceKey []byte
func (k defaultKeyer) GeneratePendingDeviceKey(key, device []byte) pendingDeviceKey {
key = resize(key, keyPrefixLen+len(device))
key[0] = KeyTypePendingDevice
copy(key[keyPrefixLen:], device)
return key
}
func (k defaultKeyer) DeviceFromPendingDeviceKey(key []byte) []byte {
return key[keyPrefixLen:]
}
// resize returns a byte slice of the specified size, reusing bs if possible
func resize(bs []byte, size int) []byte {
if cap(bs) < size {

View File

@@ -9,8 +9,6 @@ package db
import (
"bytes"
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestDeviceKey(t *testing.T) {
@@ -18,7 +16,7 @@ func TestDeviceKey(t *testing.T) {
dev := []byte("device67890123456789012345678901")
name := []byte("name")
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
key, err := db.keyer.GenerateDeviceFileKey(nil, fld, dev, name)
@@ -50,7 +48,7 @@ func TestGlobalKey(t *testing.T) {
fld := []byte("folder6789012345678901234567890123456789012345678901234567890123")
name := []byte("name")
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
key, err := db.keyer.GenerateGlobalVersionKey(nil, fld, name)
@@ -67,7 +65,7 @@ func TestGlobalKey(t *testing.T) {
func TestSequenceKey(t *testing.T) {
fld := []byte("folder6789012345678901234567890123456789012345678901234567890123")
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
defer db.Close()
const seq = 1234567890

View File

@@ -10,6 +10,7 @@ import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
@@ -19,10 +20,12 @@ import (
"github.com/dchest/siphash"
"github.com/greatroar/blobloom"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture/v4"
@@ -65,14 +68,13 @@ type Lowlevel struct {
gcKeyCount int
indirectGCInterval time.Duration
recheckInterval time.Duration
oneFileSetCreated chan struct{}
evLogger events.Logger
}
func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel {
spec := util.Spec()
func NewLowlevel(backend backend.Backend, evLogger events.Logger, opts ...Option) (*Lowlevel, error) {
// Only log restarts in debug mode.
spec.EventHook = func(e suture.Event) {
l.Debugln(e)
}
spec := svcutil.SpecWithDebugLogger(l)
db := &Lowlevel{
Supervisor: suture.New("db.Lowlevel", spec),
Backend: backend,
@@ -81,20 +83,25 @@ func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel {
gcMut: sync.NewRWMutex(),
indirectGCInterval: indirectGCDefaultInterval,
recheckInterval: recheckDefaultInterval,
oneFileSetCreated: make(chan struct{}),
evLogger: evLogger,
}
for _, opt := range opts {
opt(db)
}
db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx)
db.Add(util.AsService(db.gcRunner, "db.Lowlevel/gcRunner"))
db.Add(svcutil.AsService(db.gcRunner, "db.Lowlevel/gcRunner"))
if path := db.needsRepairPath(); path != "" {
if _, err := os.Lstat(path); err == nil {
l.Infoln("Database was marked for repair - this may take a while")
db.checkRepair()
if err := db.checkRepair(); err != nil {
db.handleFailure(err)
return nil, err
}
os.Remove(path)
}
}
return db
return db, nil
}
type Option func(*Lowlevel)
@@ -556,6 +563,26 @@ func (db *Lowlevel) setIndexID(device, folder []byte, id protocol.IndexID) error
return db.Put(key, bs)
}
func (db *Lowlevel) dropFolderIndexIDs(folder []byte) error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
if err := t.deleteKeyPrefixMatching([]byte{KeyTypeIndexID}, func(key []byte) bool {
keyFolder, ok := t.keyer.FolderFromIndexIDKey(key)
if !ok {
l.Debugf("Deleting IndexID with missing FolderIdx: %v", key)
return true
}
return bytes.Equal(keyFolder, folder)
}); err != nil {
return err
}
return t.Commit()
}
func (db *Lowlevel) dropMtimes(folder []byte) error {
key, err := db.keyer.GenerateMtimesKey(nil, folder)
if err != nil {
@@ -803,29 +830,22 @@ func (b *bloomFilter) hash(id []byte) uint64 {
}
// checkRepair checks folder metadata and sequences for miscellaneous errors.
func (db *Lowlevel) checkRepair() {
func (db *Lowlevel) checkRepair() error {
for _, folder := range db.ListFolders() {
_ = db.getMetaAndCheck(folder)
if _, err := db.getMetaAndCheck(folder); err != nil {
return err
}
}
return nil
}
func (db *Lowlevel) getMetaAndCheck(folder string) *metadataTracker {
func (db *Lowlevel) getMetaAndCheck(folder string) (*metadataTracker, error) {
db.gcMut.RLock()
defer db.gcMut.RUnlock()
var err error
defer func() {
if err != nil && !backend.IsClosed(err) {
l.Warnf("Fatal error: %v", err)
obfuscateAndPanic(err)
}
}()
var fixed int
fixed, err = db.checkLocalNeed([]byte(folder))
fixed, err := db.checkLocalNeed([]byte(folder))
if err != nil {
err = fmt.Errorf("checking local need: %w", err)
return nil
return nil, fmt.Errorf("checking local need: %w", err)
}
if fixed != 0 {
l.Infof("Repaired %d local need entries for folder %v in database", fixed, folder)
@@ -833,24 +853,22 @@ func (db *Lowlevel) getMetaAndCheck(folder string) *metadataTracker {
meta, err := db.recalcMeta(folder)
if err != nil {
err = fmt.Errorf("recalculating metadata: %w", err)
return nil
return nil, fmt.Errorf("recalculating metadata: %w", err)
}
fixed, err = db.repairSequenceGCLocked(folder, meta)
if err != nil {
err = fmt.Errorf("repairing sequences: %w", err)
return nil
return nil, fmt.Errorf("repairing sequences: %w", err)
}
if fixed != 0 {
l.Infof("Repaired %d sequence entries for folder %v in database", fixed, folder)
}
return meta
return meta, nil
}
func (db *Lowlevel) loadMetadataTracker(folder string) *metadataTracker {
meta := newMetadataTracker(db.keyer)
func (db *Lowlevel) loadMetadataTracker(folder string) (*metadataTracker, error) {
meta := newMetadataTracker(db.keyer, db.evLogger)
if err := meta.fromDB(db, []byte(folder)); err != nil {
if err == errMetaInconsistent {
l.Infof("Stored folder metadata for %q is inconsistent; recalculating", folder)
@@ -862,7 +880,9 @@ func (db *Lowlevel) loadMetadataTracker(folder string) *metadataTracker {
}
curSeq := meta.Sequence(protocol.LocalDeviceID)
if metaOK := db.verifyLocalSequence(curSeq, folder); !metaOK {
if metaOK, err := db.verifyLocalSequence(curSeq, folder); err != nil {
return nil, fmt.Errorf("verifying sequences: %w", err)
} else if !metaOK {
l.Infof("Stored folder metadata for %q is out of date after crash; recalculating", folder)
return db.getMetaAndCheck(folder)
}
@@ -872,13 +892,13 @@ func (db *Lowlevel) loadMetadataTracker(folder string) *metadataTracker {
return db.getMetaAndCheck(folder)
}
return meta
return meta, nil
}
func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) {
folder := []byte(folderStr)
meta := newMetadataTracker(db.keyer)
meta := newMetadataTracker(db.keyer, db.evLogger)
if err := db.checkGlobals(folder); err != nil {
return nil, fmt.Errorf("checking globals: %w", err)
}
@@ -932,7 +952,7 @@ func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) {
// Verify the local sequence number from actual sequence entries. Returns
// true if it was all good, or false if a fixup was necessary.
func (db *Lowlevel) verifyLocalSequence(curSeq int64, folder string) bool {
func (db *Lowlevel) verifyLocalSequence(curSeq int64, folder string) (bool, error) {
// Walk the sequence index from the current (supposedly) highest
// sequence number and raise the alarm if we get anything. This recovers
// from the occasion where we have written sequence entries to disk but
@@ -945,20 +965,18 @@ func (db *Lowlevel) verifyLocalSequence(curSeq int64, folder string) bool {
t, err := db.newReadOnlyTransaction()
if err != nil {
l.Warnf("Fatal error: %v", err)
obfuscateAndPanic(err)
return false, err
}
ok := true
if err := t.withHaveSequence([]byte(folder), curSeq+1, func(fi protocol.FileIntf) bool {
ok = false // we got something, which we should not have
return false
}); err != nil && !backend.IsClosed(err) {
l.Warnf("Fatal error: %v", err)
obfuscateAndPanic(err)
}); err != nil {
return false, err
}
t.close()
return ok
return ok, nil
}
// repairSequenceGCLocked makes sure the sequence numbers in the sequence keys
@@ -1158,6 +1176,17 @@ func (db *Lowlevel) needsRepairPath() string {
return path + needsRepairSuffix
}
func (db *Lowlevel) checkErrorForRepair(err error) {
if errors.Is(err, errEntryFromGlobalMissing) || errors.Is(err, errEmptyGlobal) {
// Inconsistency error, mark db for repair on next start.
if path := db.needsRepairPath(); path != "" {
if fd, err := os.Create(path); err == nil {
fd.Close()
}
}
}
}
// unchanged checks if two files are the same and thus don't need to be updated.
// Local flags or the invalid bit might change without the version
// being bumped.
@@ -1165,8 +1194,15 @@ func unchanged(nf, ef protocol.FileIntf) bool {
return ef.FileVersion().Equal(nf.FileVersion()) && ef.IsInvalid() == nf.IsInvalid() && ef.FileLocalFlags() == nf.FileLocalFlags()
}
func (db *Lowlevel) handleFailure(err error) {
db.checkErrorForRepair(err)
if shouldReportFailure(err) {
db.evLogger.Log(events.Failure, err)
}
}
var ldbPathRe = regexp.MustCompile(`(open|write|read) .+[\\/].+[\\/]index[^\\/]+[\\/][^\\/]+: `)
func obfuscateAndPanic(err error) {
panic(ldbPathRe.ReplaceAllString(err.Error(), "$1 x: "))
func shouldReportFailure(err error) bool {
return !ldbPathRe.MatchString(err.Error())
}

View File

@@ -9,10 +9,12 @@ package db
import (
"bytes"
"errors"
"fmt"
"math/bits"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -28,8 +30,9 @@ type countsMap struct {
type metadataTracker struct {
keyer keyer
countsMap
mut sync.RWMutex
dirty bool
mut sync.RWMutex
dirty bool
evLogger events.Logger
}
type metaKey struct {
@@ -39,13 +42,14 @@ type metaKey struct {
const needFlag uint32 = 1 << 31 // Last bit, as early ones are local flags
func newMetadataTracker(keyer keyer) *metadataTracker {
func newMetadataTracker(keyer keyer, evLogger events.Logger) *metadataTracker {
return &metadataTracker{
keyer: keyer,
mut: sync.NewRWMutex(),
countsMap: countsMap{
indexes: make(map[metaKey]int),
},
evLogger: evLogger,
}
}
@@ -296,18 +300,22 @@ func (m *metadataTracker) removeFileLocked(dev protocol.DeviceID, flag uint32, f
// the created timestamp to zero. Next time we start up the metadata
// will be seen as infinitely old and recalculated from scratch.
if cp.Deleted < 0 {
m.evLogger.Log(events.Failure, fmt.Sprintf("meta deleted count for flag 0x%x dropped below zero", flag))
cp.Deleted = 0
m.counts.Created = 0
}
if cp.Files < 0 {
m.evLogger.Log(events.Failure, fmt.Sprintf("meta files count for flag 0x%x dropped below zero", flag))
cp.Files = 0
m.counts.Created = 0
}
if cp.Directories < 0 {
m.evLogger.Log(events.Failure, fmt.Sprintf("meta directories count for flag 0x%x dropped below zero", flag))
cp.Directories = 0
m.counts.Created = 0
}
if cp.Symlinks < 0 {
m.evLogger.Log(events.Failure, fmt.Sprintf("meta deleted count for flag 0x%x dropped below zero", flag))
cp.Symlinks = 0
m.counts.Created = 0
}

View File

@@ -11,7 +11,7 @@ import (
"sort"
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -52,7 +52,7 @@ func TestEachFlagBit(t *testing.T) {
func TestMetaDevices(t *testing.T) {
d1 := protocol.DeviceID{1}
d2 := protocol.DeviceID{2}
meta := newMetadataTracker(nil)
meta := newMetadataTracker(nil, events.NoopLogger)
meta.addFile(d1, protocol.FileInfo{Sequence: 1})
meta.addFile(d1, protocol.FileInfo{Sequence: 2, LocalFlags: 1})
@@ -85,7 +85,7 @@ func TestMetaDevices(t *testing.T) {
func TestMetaSequences(t *testing.T) {
d1 := protocol.DeviceID{1}
meta := newMetadataTracker(nil)
meta := newMetadataTracker(nil, events.NoopLogger)
meta.addFile(d1, protocol.FileInfo{Sequence: 1})
meta.addFile(d1, protocol.FileInfo{Sequence: 2, RawInvalid: true})
@@ -105,11 +105,11 @@ func TestMetaSequences(t *testing.T) {
}
func TestRecalcMeta(t *testing.T) {
ldb := NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
// Add some files
s1 := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, "fake"), ldb)
s1 := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, "fake"), ldb)
files := []protocol.FileInfo{
{Name: "a", Size: 1000},
{Name: "b", Size: 2000},
@@ -161,7 +161,7 @@ func TestRecalcMeta(t *testing.T) {
}
// Create a new fileset, which will realize the inconsistency and recalculate
s2 := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, "fake"), ldb)
s2 := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, "fake"), ldb)
// Verify local/global size
snap = s2.Snapshot()

View File

@@ -9,12 +9,10 @@ package db
import (
"testing"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestNamespacedInt(t *testing.T) {
ldb := NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
n1 := NewNamespacedKV(ldb, "foo")
@@ -62,7 +60,7 @@ func TestNamespacedInt(t *testing.T) {
}
func TestNamespacedTime(t *testing.T) {
ldb := NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
n1 := NewNamespacedKV(ldb, "foo")
@@ -86,7 +84,7 @@ func TestNamespacedTime(t *testing.T) {
}
func TestNamespacedString(t *testing.T) {
ldb := NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
n1 := NewNamespacedKV(ldb, "foo")
@@ -109,7 +107,7 @@ func TestNamespacedString(t *testing.T) {
}
func TestNamespacedReset(t *testing.T) {
ldb := NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
n1 := NewNamespacedKV(ldb, "foo")

172
lib/db/observed.go Normal file
View File

@@ -0,0 +1,172 @@
// Copyright (C) 2020 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 db
import (
"time"
"github.com/syncthing/syncthing/lib/protocol"
)
func (db *Lowlevel) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error {
key := db.keyer.GeneratePendingDeviceKey(nil, device[:])
od := ObservedDevice{
Time: time.Now().Round(time.Second),
Name: name,
Address: address,
}
bs, err := od.Marshal()
if err != nil {
return err
}
return db.Put(key, bs)
}
func (db *Lowlevel) RemovePendingDevice(device protocol.DeviceID) {
key := db.keyer.GeneratePendingDeviceKey(nil, device[:])
if err := db.Delete(key); err != nil {
l.Warnf("Failed to remove pending device entry: %v", err)
}
}
// PendingDevices enumerates all entries. Invalid ones are dropped from the database
// after a warning log message, as a side-effect.
func (db *Lowlevel) PendingDevices() (map[protocol.DeviceID]ObservedDevice, error) {
iter, err := db.NewPrefixIterator([]byte{KeyTypePendingDevice})
if err != nil {
return nil, err
}
defer iter.Release()
res := make(map[protocol.DeviceID]ObservedDevice)
for iter.Next() {
keyDev := db.keyer.DeviceFromPendingDeviceKey(iter.Key())
deviceID, err := protocol.DeviceIDFromBytes(keyDev)
var od ObservedDevice
if err != nil {
goto deleteKey
}
if err = od.Unmarshal(iter.Value()); err != nil {
goto deleteKey
}
res[deviceID] = od
continue
deleteKey:
// Deleting invalid entries is the only possible "repair" measure and
// appropriate for the importance of pending entries. They will come back
// soon if still relevant.
l.Infof("Invalid pending device entry, deleting from database: %x", iter.Key())
if err := db.Delete(iter.Key()); err != nil {
return nil, err
}
}
return res, nil
}
func (db *Lowlevel) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) error {
key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id))
if err != nil {
return err
}
of := ObservedFolder{
Time: time.Now().Round(time.Second),
Label: label,
}
bs, err := of.Marshal()
if err != nil {
return err
}
return db.Put(key, bs)
}
// RemovePendingFolderForDevice removes entries for specific folder / device combinations.
func (db *Lowlevel) RemovePendingFolderForDevice(id string, device protocol.DeviceID) {
key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id))
if err != nil {
return
}
if err := db.Delete(key); err != nil {
l.Warnf("Failed to remove pending folder entry: %v", err)
}
}
// RemovePendingFolder removes all entries matching a specific folder ID.
func (db *Lowlevel) RemovePendingFolder(id string) {
iter, err := db.NewPrefixIterator([]byte{KeyTypePendingFolder})
if err != nil {
l.Infof("Could not iterate through pending folder entries: %v", err)
return
}
defer iter.Release()
for iter.Next() {
if id != string(db.keyer.FolderFromPendingFolderKey(iter.Key())) {
continue
}
if err := db.Delete(iter.Key()); err != nil {
l.Warnf("Failed to remove pending folder entry: %v", err)
}
}
}
// Consolidated information about a pending folder
type PendingFolder struct {
OfferedBy map[protocol.DeviceID]ObservedFolder `json:"offeredBy"`
}
func (db *Lowlevel) PendingFolders() (map[string]PendingFolder, error) {
return db.PendingFoldersForDevice(protocol.EmptyDeviceID)
}
// PendingFoldersForDevice enumerates only entries matching the given device ID, unless it
// is EmptyDeviceID. Invalid ones are dropped from the database after a warning log
// message, as a side-effect.
func (db *Lowlevel) PendingFoldersForDevice(device protocol.DeviceID) (map[string]PendingFolder, error) {
var err error
prefixKey := []byte{KeyTypePendingFolder}
if device != protocol.EmptyDeviceID {
prefixKey, err = db.keyer.GeneratePendingFolderKey(nil, device[:], nil)
if err != nil {
return nil, err
}
}
iter, err := db.NewPrefixIterator(prefixKey)
if err != nil {
return nil, err
}
defer iter.Release()
res := make(map[string]PendingFolder)
for iter.Next() {
keyDev, ok := db.keyer.DeviceFromPendingFolderKey(iter.Key())
deviceID, err := protocol.DeviceIDFromBytes(keyDev)
var of ObservedFolder
var folderID string
if !ok || err != nil {
goto deleteKey
}
if folderID = string(db.keyer.FolderFromPendingFolderKey(iter.Key())); len(folderID) < 1 {
goto deleteKey
}
if err = of.Unmarshal(iter.Value()); err != nil {
goto deleteKey
}
if _, ok := res[folderID]; !ok {
res[folderID] = PendingFolder{
OfferedBy: map[protocol.DeviceID]ObservedFolder{},
}
}
res[folderID].OfferedBy[deviceID] = of
continue
deleteKey:
// Deleting invalid entries is the only possible "repair" measure and
// appropriate for the importance of pending entries. They will come back
// soon if still relevant.
l.Infof("Invalid pending folder entry, deleting from database: %x", iter.Key())
if err := db.Delete(iter.Key()); err != nil {
return nil, err
}
}
return res, nil
}

View File

@@ -719,7 +719,7 @@ func (db *schemaUpdater) updateSchemaTo14(_ int) error {
var key, gk []byte
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
meta := newMetadataTracker(db.keyer)
meta := newMetadataTracker(db.keyer, db.evLogger)
meta.counts.Created = 0 // Recalculate metadata afterwards
t, err := db.newReadWriteTransaction(meta.CommitHook(folder))

View File

@@ -13,9 +13,7 @@
package db
import (
"errors"
"fmt"
"os"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/fs"
@@ -38,14 +36,33 @@ type FileSet struct {
// continue iteration, false to stop.
type Iterator func(f protocol.FileIntf) bool
func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet {
return &FileSet{
func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) (*FileSet, error) {
select {
case <-db.oneFileSetCreated:
default:
close(db.oneFileSetCreated)
}
meta, err := db.loadMetadataTracker(folder)
if err != nil {
db.handleFailure(err)
return nil, err
}
s := &FileSet{
folder: folder,
fs: fs,
db: db,
meta: db.loadMetadataTracker(folder),
meta: meta,
updateMutex: sync.NewMutex(),
}
if id := s.IndexID(protocol.LocalDeviceID); id == 0 {
// No index ID set yet. We create one now.
id = protocol.NewIndexID()
err := s.db.setIndexID(protocol.LocalDeviceID[:], []byte(s.folder), id)
if err != nil && !backend.IsClosed(err) {
fatalError(err, fmt.Sprintf("%s Creating new IndexID", s.folder), s.db)
}
}
return s, nil
}
func (s *FileSet) Drop(device protocol.DeviceID) {
@@ -357,16 +374,6 @@ func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {
} else if err != nil {
fatalError(err, opStr, s.db)
}
if id == 0 && device == protocol.LocalDeviceID {
// No index ID set yet. We create one now.
id = protocol.NewIndexID()
err := s.db.setIndexID(device[:], []byte(s.folder), id)
if backend.IsClosed(err) {
return 0
} else if err != nil {
fatalError(err, opStr, s.db)
}
}
return id
}
@@ -381,7 +388,7 @@ func (s *FileSet) SetIndexID(device protocol.DeviceID, id protocol.IndexID) {
}
}
func (s *FileSet) MtimeFS() *fs.MtimeFS {
func (s *FileSet) MtimeFS() fs.Filesystem {
opStr := fmt.Sprintf("%s MtimeFS()", s.folder)
l.Debugf(opStr)
prefix, err := s.db.keyer.GenerateMtimesKey(nil, []byte(s.folder))
@@ -419,6 +426,7 @@ func DropFolder(db *Lowlevel, folder string) {
db.dropFolder,
db.dropMtimes,
db.dropFolderMeta,
db.dropFolderIndexIDs,
db.folderIdx.Delete,
}
for _, drop := range droppers {
@@ -432,7 +440,14 @@ func DropFolder(db *Lowlevel, folder string) {
// DropDeltaIndexIDs removes all delta index IDs from the database.
// This will cause a full index transmission on the next connection.
// Must be called before using FileSets, i.e. before NewFileSet is called for
// the first time.
func DropDeltaIndexIDs(db *Lowlevel) {
select {
case <-db.oneFileSetCreated:
panic("DropDeltaIndexIDs must not be called after NewFileSet for the same Lowlevel")
default:
}
opStr := "DropDeltaIndexIDs"
l.Debugf(opStr)
dbi, err := db.NewPrefixIterator([]byte{KeyTypeIndexID})
@@ -488,14 +503,7 @@ func nativeFileIterator(fn Iterator) Iterator {
}
func fatalError(err error, opStr string, db *Lowlevel) {
if errors.Is(err, errEntryFromGlobalMissing) || errors.Is(err, errEmptyGlobal) {
// Inconsistency error, mark db for repair on next start.
if path := db.needsRepairPath(); path != "" {
if fd, err := os.Create(path); err == nil {
fd.Close()
}
}
}
db.checkErrorForRepair(err)
l.Warnf("Fatal error: %v: %v", opStr, err)
obfuscateAndPanic(err)
panic(ldbPathRe.ReplaceAllString(err.Error(), "$1 x: "))
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -142,10 +143,10 @@ func setBlocksHash(files fileList) {
}
func TestGlobalSet(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local0 := fileList{
protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)},
@@ -448,10 +449,10 @@ func TestGlobalSet(t *testing.T) {
}
func TestNeedWithInvalid(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
localHave := fileList{
protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)},
@@ -488,11 +489,11 @@ func TestNeedWithInvalid(t *testing.T) {
}
func TestUpdateToInvalid(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
f := db.NewBlockFinder(ldb)
localHave := fileList{
@@ -545,10 +546,10 @@ func TestUpdateToInvalid(t *testing.T) {
}
func TestInvalidAvailability(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
remote0Have := fileList{
protocol.FileInfo{Name: "both", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)},
@@ -587,10 +588,10 @@ func TestInvalidAvailability(t *testing.T) {
}
func TestGlobalReset(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
{Name: "a", Sequence: 1, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -626,10 +627,10 @@ func TestGlobalReset(t *testing.T) {
}
func TestNeed(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -667,10 +668,10 @@ func TestNeed(t *testing.T) {
}
func TestSequence(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local1 := []protocol.FileInfo{
{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -698,10 +699,10 @@ func TestSequence(t *testing.T) {
}
func TestListDropFolder(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s0 := db.NewFileSet("test0", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s0 := newFileSet(t, "test0", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local1 := []protocol.FileInfo{
{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -709,7 +710,7 @@ func TestListDropFolder(t *testing.T) {
}
replace(s0, protocol.LocalDeviceID, local1)
s1 := db.NewFileSet("test1", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s1 := newFileSet(t, "test1", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local2 := []protocol.FileInfo{
{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}},
{Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}},
@@ -749,10 +750,10 @@ func TestListDropFolder(t *testing.T) {
}
func TestGlobalNeedWithInvalid(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test1", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test1", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
rem0 := fileList{
protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)},
@@ -792,10 +793,10 @@ func TestGlobalNeedWithInvalid(t *testing.T) {
}
func TestLongPath(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
var b bytes.Buffer
for i := 0; i < 100; i++ {
@@ -833,13 +834,13 @@ func BenchmarkUpdateOneFile(b *testing.B) {
if err != nil {
b.Fatal(err)
}
ldb := db.NewLowlevel(be)
ldb := newLowlevel(b, be)
defer func() {
ldb.Close()
os.RemoveAll("testdata/benchmarkupdate.db")
}()
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(b, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
replace(m, protocol.LocalDeviceID, local0)
l := local0[4:5]
@@ -852,10 +853,10 @@ func BenchmarkUpdateOneFile(b *testing.B) {
}
func TestIndexID(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
// The Index ID for some random device is zero by default.
id := s.IndexID(remoteDevice0)
@@ -885,9 +886,9 @@ func TestIndexID(t *testing.T) {
}
func TestDropFiles(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
m := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local0 := fileList{
protocol.FileInfo{Name: "a", Sequence: 1, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)},
@@ -948,10 +949,10 @@ func TestDropFiles(t *testing.T) {
}
func TestIssue4701(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
localHave := fileList{
protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -990,11 +991,11 @@ func TestIssue4701(t *testing.T) {
}
func TestWithHaveSequence(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
// The files must not be in alphabetical order
localHave := fileList{
@@ -1028,11 +1029,11 @@ func TestStressWithHaveSequence(t *testing.T) {
t.Skip("Takes a long time")
}
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
var localHave []protocol.FileInfo
for i := 0; i < 100; i++ {
@@ -1073,11 +1074,11 @@ loop:
}
func TestIssue4925(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
localHave := fileList{
protocol.FileInfo{Name: "dir"},
@@ -1100,12 +1101,12 @@ func TestIssue4925(t *testing.T) {
}
func TestMoveGlobalBack(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
file := "foo"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
localHave := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}, Blocks: genBlocks(1), ModifiedS: 10, Size: 1}}
remote0Have := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}, {ID: remoteDevice0.Short(), Value: 1}}}, Blocks: genBlocks(2), ModifiedS: 0, Size: 2}}
@@ -1169,12 +1170,12 @@ func TestMoveGlobalBack(t *testing.T) {
// needed files.
// https://github.com/syncthing/syncthing/issues/5007
func TestIssue5007(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
file := "foo"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
fs := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}}
@@ -1199,12 +1200,12 @@ func TestIssue5007(t *testing.T) {
// TestNeedDeleted checks that a file that doesn't exist locally isn't needed
// when the global file is deleted.
func TestNeedDeleted(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
file := "foo"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
fs := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}, Deleted: true}}
@@ -1237,11 +1238,11 @@ func TestNeedDeleted(t *testing.T) {
}
func TestReceiveOnlyAccounting(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := protocol.DeviceID{1}
remote := protocol.DeviceID{2}
@@ -1342,12 +1343,12 @@ func TestReceiveOnlyAccounting(t *testing.T) {
}
func TestNeedAfterUnignore(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
folder := "test"
file := "foo"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
remID := remoteDevice0.Short()
@@ -1376,9 +1377,9 @@ func TestNeedAfterUnignore(t *testing.T) {
func TestRemoteInvalidNotAccounted(t *testing.T) {
// Remote files with the invalid bit should not count.
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
files := []protocol.FileInfo{
{Name: "a", Size: 1234, Sequence: 42, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}}, // valid, should count
@@ -1396,10 +1397,10 @@ func TestRemoteInvalidNotAccounted(t *testing.T) {
}
func TestNeedWithNewerInvalid(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("default", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "default", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
rem0ID := remoteDevice0.Short()
rem1ID := remoteDevice1.Short()
@@ -1437,11 +1438,11 @@ func TestNeedWithNewerInvalid(t *testing.T) {
}
func TestNeedAfterDeviceRemove(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
file := "foo"
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
fs := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}}
@@ -1466,9 +1467,9 @@ func TestNeedAfterDeviceRemove(t *testing.T) {
func TestCaseSensitive(t *testing.T) {
// Normal case sensitive lookup should work
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
{Name: filepath.FromSlash("D1/f1"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -1504,9 +1505,9 @@ func TestSequenceIndex(t *testing.T) {
// Set up a db and a few files that we will manipulate.
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
{Name: filepath.FromSlash("banana"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
@@ -1598,11 +1599,11 @@ func TestSequenceIndex(t *testing.T) {
}
func TestIgnoreAfterReceiveOnly(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
file := "foo"
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
fs := fileList{{
Name: file,
@@ -1629,11 +1630,11 @@ func TestIgnoreAfterReceiveOnly(t *testing.T) {
// https://github.com/syncthing/syncthing/issues/6650
func TestUpdateWithOneFileTwice(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
file := "foo"
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
fs := fileList{{
Name: file,
@@ -1667,10 +1668,10 @@ func TestUpdateWithOneFileTwice(t *testing.T) {
// https://github.com/syncthing/syncthing/issues/6668
func TestNeedRemoteOnly(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
remote0Have := fileList{
protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)},
@@ -1685,10 +1686,10 @@ func TestNeedRemoteOnly(t *testing.T) {
// https://github.com/syncthing/syncthing/issues/6784
func TestNeedRemoteAfterReset(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
files := fileList{
protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)},
@@ -1711,10 +1712,10 @@ func TestNeedRemoteAfterReset(t *testing.T) {
// https://github.com/syncthing/syncthing/issues/6850
func TestIgnoreLocalChanged(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
// Add locally changed file
files := fileList{
@@ -1745,10 +1746,10 @@ func TestIgnoreLocalChanged(t *testing.T) {
// an Index (as opposed to an IndexUpdate), and we don't want to loose the index
// ID when that happens.
func TestNoIndexIDResetOnDrop(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
ldb := newLowlevelMemory(t)
defer ldb.Close()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
s.SetIndexID(remoteDevice0, 1)
s.Drop(remoteDevice0)
@@ -1757,6 +1758,32 @@ func TestNoIndexIDResetOnDrop(t *testing.T) {
}
}
func TestConcurrentIndexID(t *testing.T) {
done := make(chan struct{})
var ids [2]protocol.IndexID
setID := func(s *db.FileSet, i int) {
ids[i] = s.IndexID(protocol.LocalDeviceID)
done <- struct{}{}
}
max := 100
if testing.Short() {
max = 10
}
for i := 0; i < max; i++ {
ldb := newLowlevelMemory(t)
s := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb)
go setID(s, 0)
go setID(s, 1)
<-done
<-done
ldb.Close()
if ids[0] != ids[1] {
t.Fatalf("IDs differ after %v rounds", i)
}
}
}
func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo) {
fs.Drop(device)
fs.Update(device, files)
@@ -1811,3 +1838,25 @@ func checkNeed(t testing.TB, s *db.FileSet, dev protocol.DeviceID, expected []pr
t.Errorf("Count incorrect (%v): expected %v, got %v", dev, exp, counts)
}
}
func newLowlevel(t testing.TB, backend backend.Backend) *db.Lowlevel {
t.Helper()
ll, err := db.NewLowlevel(backend, events.NoopLogger)
if err != nil {
t.Fatal(err)
}
return ll
}
func newLowlevelMemory(t testing.TB) *db.Lowlevel {
return newLowlevel(t, backend.OpenMemory())
}
func newFileSet(t testing.TB, folder string, fs fs.Filesystem, ll *db.Lowlevel) *db.FileSet {
t.Helper()
fset, err := db.NewFileSet(folder, fs, ll)
if err != nil {
t.Fatal(err)
}
return fset
}

View File

@@ -8,12 +8,10 @@ package db
import (
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestSmallIndex(t *testing.T) {
db := NewLowlevel(backend.OpenMemory())
db := newLowlevelMemory(t)
idx := newSmallIndex(db, []byte{12, 34})
// ID zero should be unallocated

View File

@@ -7,18 +7,22 @@ import (
fmt "fmt"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/gogo/protobuf/proto"
github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
protocol "github.com/syncthing/syncthing/lib/protocol"
_ "github.com/syncthing/syncthing/proto/ext"
_ "google.golang.org/protobuf/types/known/timestamppb"
io "io"
math "math"
math_bits "math/bits"
time "time"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
var _ = time.Kitchen
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
@@ -395,6 +399,83 @@ func (m *VersionListDeprecated) XXX_DiscardUnknown() {
var xxx_messageInfo_VersionListDeprecated proto.InternalMessageInfo
type ObservedFolder struct {
Time time.Time `protobuf:"bytes,1,opt,name=time,proto3,stdtime" json:"time" xml:"time"`
Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label" xml:"label"`
}
func (m *ObservedFolder) Reset() { *m = ObservedFolder{} }
func (m *ObservedFolder) String() string { return proto.CompactTextString(m) }
func (*ObservedFolder) ProtoMessage() {}
func (*ObservedFolder) Descriptor() ([]byte, []int) {
return fileDescriptor_5465d80e8cba02e3, []int{9}
}
func (m *ObservedFolder) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ObservedFolder) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_ObservedFolder.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *ObservedFolder) XXX_Merge(src proto.Message) {
xxx_messageInfo_ObservedFolder.Merge(m, src)
}
func (m *ObservedFolder) XXX_Size() int {
return m.ProtoSize()
}
func (m *ObservedFolder) XXX_DiscardUnknown() {
xxx_messageInfo_ObservedFolder.DiscardUnknown(m)
}
var xxx_messageInfo_ObservedFolder proto.InternalMessageInfo
type ObservedDevice struct {
Time time.Time `protobuf:"bytes,1,opt,name=time,proto3,stdtime" json:"time" xml:"time"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name" xml:"name"`
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address" xml:"address"`
}
func (m *ObservedDevice) Reset() { *m = ObservedDevice{} }
func (m *ObservedDevice) String() string { return proto.CompactTextString(m) }
func (*ObservedDevice) ProtoMessage() {}
func (*ObservedDevice) Descriptor() ([]byte, []int) {
return fileDescriptor_5465d80e8cba02e3, []int{10}
}
func (m *ObservedDevice) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ObservedDevice) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_ObservedDevice.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *ObservedDevice) XXX_Merge(src proto.Message) {
xxx_messageInfo_ObservedDevice.Merge(m, src)
}
func (m *ObservedDevice) XXX_Size() int {
return m.ProtoSize()
}
func (m *ObservedDevice) XXX_DiscardUnknown() {
xxx_messageInfo_ObservedDevice.DiscardUnknown(m)
}
var xxx_messageInfo_ObservedDevice proto.InternalMessageInfo
func init() {
proto.RegisterType((*FileVersion)(nil), "db.FileVersion")
proto.RegisterType((*VersionList)(nil), "db.VersionList")
@@ -405,93 +486,103 @@ func init() {
proto.RegisterType((*CountsSet)(nil), "db.CountsSet")
proto.RegisterType((*FileVersionDeprecated)(nil), "db.FileVersionDeprecated")
proto.RegisterType((*VersionListDeprecated)(nil), "db.VersionListDeprecated")
proto.RegisterType((*ObservedFolder)(nil), "db.ObservedFolder")
proto.RegisterType((*ObservedDevice)(nil), "db.ObservedDevice")
}
func init() { proto.RegisterFile("lib/db/structs.proto", fileDescriptor_5465d80e8cba02e3) }
var fileDescriptor_5465d80e8cba02e3 = []byte{
// 1289 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x6f, 0xdc, 0x44,
0x14, 0x5e, 0x67, 0x7f, 0x64, 0x77, 0x76, 0x93, 0x36, 0x2e, 0xad, 0x4c, 0x81, 0x9d, 0x65, 0x9a,
0x4a, 0x0b, 0x48, 0x1b, 0x29, 0x55, 0x2b, 0x54, 0x09, 0xaa, 0xba, 0x51, 0xdb, 0x54, 0xa5, 0x45,
0x93, 0xaa, 0x20, 0x2e, 0xab, 0xb5, 0x77, 0x92, 0x58, 0x75, 0xec, 0xc5, 0xe3, 0x34, 0xdd, 0xde,
0xb8, 0x20, 0x71, 0x43, 0x15, 0x07, 0x84, 0x10, 0xea, 0x89, 0x3f, 0x81, 0xbf, 0xa1, 0xc7, 0x1c,
0x11, 0x07, 0x4b, 0x4d, 0x2e, 0xb0, 0xc7, 0x3d, 0x21, 0x4e, 0x68, 0xde, 0x8c, 0xc7, 0xb3, 0x8d,
0x8a, 0xda, 0x92, 0x9b, 0xdf, 0xf7, 0xbe, 0xf7, 0x6c, 0xbf, 0xf9, 0xde, 0x9b, 0x87, 0xde, 0x0a,
0x03, 0x6f, 0x65, 0xe8, 0xad, 0xf0, 0x34, 0xd9, 0xf5, 0x53, 0xde, 0x1b, 0x25, 0x71, 0x1a, 0xdb,
0x73, 0x43, 0xef, 0xec, 0xb9, 0x84, 0x8d, 0x62, 0xbe, 0x02, 0x80, 0xb7, 0xbb, 0xb9, 0xb2, 0x15,
0x6f, 0xc5, 0x60, 0xc0, 0x93, 0x24, 0x9e, 0x3d, 0x23, 0xc2, 0xe1, 0xd1, 0x8f, 0xc3, 0x15, 0x8f,
0x8d, 0x14, 0xde, 0x60, 0x8f, 0x52, 0xf9, 0x48, 0x7e, 0x99, 0x43, 0xcd, 0xeb, 0x41, 0xc8, 0xee,
0xb3, 0x84, 0x07, 0x71, 0x64, 0xdf, 0x46, 0xf3, 0x0f, 0xe5, 0xa3, 0x63, 0x75, 0xac, 0x6e, 0x73,
0xf5, 0x64, 0x2f, 0x4f, 0xd0, 0xbb, 0xcf, 0xfc, 0x34, 0x4e, 0xdc, 0xce, 0xb3, 0x0c, 0x97, 0x26,
0x19, 0xce, 0x89, 0xd3, 0x0c, 0x2f, 0x3c, 0xda, 0x09, 0x2f, 0x13, 0x65, 0x13, 0x9a, 0x7b, 0xec,
0x4b, 0x68, 0x7e, 0xc8, 0x42, 0x96, 0xb2, 0xa1, 0x33, 0xd7, 0xb1, 0xba, 0x75, 0xf7, 0x5d, 0x11,
0xa7, 0x20, 0x1d, 0xa7, 0x6c, 0x42, 0x73, 0x8f, 0x7d, 0x51, 0xc4, 0x3d, 0x0c, 0x7c, 0xc6, 0x9d,
0x72, 0xa7, 0xdc, 0x6d, 0xb9, 0xef, 0xc8, 0x38, 0x80, 0xa6, 0x19, 0x6e, 0xa9, 0x38, 0x61, 0x43,
0x18, 0x38, 0x6c, 0x8a, 0x4e, 0x04, 0xd1, 0xc3, 0x41, 0x18, 0x0c, 0xfb, 0x79, 0x78, 0x05, 0xc2,
0x3f, 0x98, 0x64, 0x78, 0x51, 0xb9, 0xd6, 0x74, 0x96, 0x53, 0x90, 0x65, 0x06, 0x26, 0xf4, 0x05,
0x1a, 0xf9, 0xc6, 0x42, 0x4d, 0x55, 0x9c, 0xdb, 0x01, 0x4f, 0xed, 0x10, 0xd5, 0xd5, 0xdf, 0x71,
0xc7, 0xea, 0x94, 0xbb, 0xcd, 0xd5, 0x13, 0xbd, 0xa1, 0xd7, 0x33, 0x6a, 0xe8, 0x5e, 0x11, 0x05,
0x3a, 0xc8, 0x70, 0x93, 0x0e, 0xf6, 0x14, 0xc6, 0x27, 0x19, 0xd6, 0x71, 0x47, 0x0a, 0xf6, 0x64,
0x7f, 0xd9, 0xe4, 0x52, 0xcd, 0xbc, 0x5c, 0xf9, 0xf1, 0x29, 0x2e, 0x91, 0xbf, 0x11, 0x5a, 0x12,
0x2f, 0x58, 0x8f, 0x36, 0xe3, 0x7b, 0xc9, 0x6e, 0xe4, 0x0f, 0x44, 0x91, 0x3e, 0x44, 0x95, 0x68,
0xb0, 0xc3, 0xe0, 0x9c, 0x1a, 0xee, 0x99, 0x49, 0x86, 0xc1, 0x9e, 0x66, 0x18, 0x41, 0x76, 0x61,
0x10, 0x0a, 0x98, 0xe0, 0xf2, 0xe0, 0x31, 0x73, 0xca, 0x1d, 0xab, 0x5b, 0x96, 0x5c, 0x61, 0x6b,
0xae, 0x30, 0x08, 0x05, 0xcc, 0xbe, 0x82, 0xd0, 0x4e, 0x3c, 0x0c, 0x36, 0x03, 0x36, 0xec, 0x73,
0xa7, 0x0a, 0x11, 0x9d, 0x49, 0x86, 0x1b, 0x39, 0xba, 0x31, 0xcd, 0xf0, 0x09, 0x08, 0xd3, 0x08,
0xa1, 0x85, 0xd7, 0xfe, 0xcd, 0x42, 0x4d, 0x9d, 0xc1, 0x1b, 0x3b, 0xad, 0x8e, 0xd5, 0xad, 0xb8,
0x3f, 0x58, 0xa2, 0x2c, 0x7f, 0x64, 0xf8, 0xc2, 0x56, 0x90, 0x6e, 0xef, 0x7a, 0x3d, 0x3f, 0xde,
0x59, 0xe1, 0xe3, 0xc8, 0x4f, 0xb7, 0x83, 0x68, 0xcb, 0x78, 0x32, 0x45, 0xdb, 0xdb, 0xd8, 0x8e,
0x93, 0x74, 0x7d, 0x6d, 0x92, 0x61, 0xfd, 0x51, 0xee, 0x78, 0x9a, 0xe1, 0x93, 0x33, 0xef, 0x77,
0xc7, 0xe4, 0xa7, 0xfd, 0xe5, 0x37, 0x49, 0x4c, 0x8d, 0xb4, 0xa6, 0xf8, 0x1b, 0xff, 0x5f, 0xfc,
0x97, 0x51, 0x9d, 0xb3, 0xaf, 0x77, 0x59, 0xe4, 0x33, 0x07, 0x41, 0x15, 0xdb, 0x42, 0x05, 0x39,
0x36, 0xcd, 0xf0, 0xa2, 0xac, 0xbd, 0x02, 0x08, 0xd5, 0x3e, 0xfb, 0x2e, 0x5a, 0xe4, 0xe3, 0x9d,
0x30, 0x88, 0x1e, 0xf4, 0xd3, 0x41, 0xb2, 0xc5, 0x52, 0x67, 0x09, 0x4e, 0xb9, 0x3b, 0xc9, 0xf0,
0x82, 0xf2, 0xdc, 0x03, 0x87, 0xd6, 0xf1, 0x0c, 0x4a, 0xe8, 0x2c, 0xcb, 0xbe, 0x86, 0x9a, 0x5e,
0x18, 0xfb, 0x0f, 0x78, 0x7f, 0x7b, 0xc0, 0xb7, 0x1d, 0xbb, 0x63, 0x75, 0x5b, 0x2e, 0x11, 0x65,
0x95, 0xf0, 0xcd, 0x01, 0xdf, 0xd6, 0x65, 0x2d, 0x20, 0x42, 0x0d, 0xbf, 0xfd, 0x29, 0x6a, 0xb0,
0xc8, 0x4f, 0xc6, 0x23, 0xd1, 0xd0, 0xa7, 0x20, 0x05, 0x08, 0x43, 0x83, 0x5a, 0x18, 0x1a, 0x21,
0xb4, 0xf0, 0xda, 0x2e, 0xaa, 0xa4, 0xe3, 0x11, 0x83, 0x59, 0xb0, 0xb8, 0x7a, 0xa6, 0x28, 0xae,
0x16, 0xf7, 0x78, 0xc4, 0xa4, 0x3a, 0x05, 0x4f, 0xab, 0x53, 0x18, 0x84, 0x02, 0x66, 0x5f, 0x47,
0xcd, 0x11, 0x4b, 0x76, 0x02, 0x2e, 0x5b, 0xb0, 0xd2, 0xb1, 0xba, 0x0b, 0xee, 0xf2, 0x24, 0xc3,
0x26, 0x3c, 0xcd, 0xf0, 0x12, 0x44, 0x1a, 0x18, 0xa1, 0x26, 0xc3, 0xbe, 0x65, 0x68, 0x34, 0xe2,
0x4e, 0xb3, 0x63, 0x75, 0xab, 0x30, 0x27, 0xb4, 0x20, 0xee, 0xf0, 0x23, 0x3a, 0xbb, 0xc3, 0xc9,
0x3f, 0x19, 0x2e, 0x07, 0x51, 0x4a, 0x0d, 0x9a, 0xbd, 0x89, 0x64, 0x95, 0xfa, 0xd0, 0x63, 0x0b,
0x90, 0xea, 0xc6, 0x41, 0x86, 0x5b, 0x74, 0xb0, 0xe7, 0x0a, 0xc7, 0x46, 0xf0, 0x98, 0x89, 0x42,
0x79, 0xb9, 0xa1, 0x0b, 0xa5, 0x91, 0x3c, 0xf1, 0x93, 0xfd, 0xe5, 0x99, 0x30, 0x5a, 0x04, 0xd9,
0x6b, 0xa8, 0x19, 0xc6, 0xfe, 0x20, 0xec, 0x6f, 0x86, 0x83, 0x2d, 0xee, 0xfc, 0x39, 0x0f, 0x3f,
0x0f, 0xa7, 0x08, 0xf8, 0x75, 0x01, 0xeb, 0x8f, 0x2e, 0x20, 0x42, 0x0d, 0xbf, 0x7d, 0x13, 0xb5,
0x94, 0x44, 0xa5, 0x16, 0xfe, 0x9a, 0x87, 0x93, 0x84, 0x1a, 0x2a, 0x87, 0x52, 0xc3, 0x92, 0xa9,
0x6c, 0x29, 0x07, 0x93, 0x61, 0x8e, 0xf7, 0xda, 0xeb, 0x8c, 0x77, 0x8a, 0xe6, 0xd5, 0x94, 0x75,
0xe6, 0x21, 0xee, 0xe3, 0x83, 0x0c, 0x23, 0x3a, 0xd8, 0x5b, 0x97, 0xa8, 0xc8, 0xa2, 0x08, 0x3a,
0x8b, 0xb2, 0xc5, 0xac, 0x34, 0x98, 0x34, 0xe7, 0x89, 0x8e, 0x89, 0xe2, 0xbe, 0x29, 0x8d, 0x3a,
0xa4, 0x86, 0x8e, 0x89, 0xe2, 0xcf, 0x67, 0xc4, 0x21, 0x3b, 0x66, 0x06, 0x25, 0x74, 0x96, 0xa5,
0x46, 0xef, 0x17, 0xa8, 0x01, 0x47, 0x01, 0xb3, 0xff, 0x16, 0xaa, 0xc9, 0x6e, 0x50, 0x93, 0xff,
0x54, 0xa1, 0x60, 0x20, 0x09, 0x09, 0xbb, 0xef, 0xa9, 0x09, 0xa1, 0xa8, 0xd3, 0x0c, 0x37, 0x8b,
0x93, 0x26, 0x54, 0xc1, 0xe4, 0x57, 0x0b, 0x9d, 0x5e, 0x8f, 0x86, 0x41, 0xc2, 0xfc, 0x54, 0xd5,
0x93, 0xf1, 0xbb, 0x51, 0x38, 0x3e, 0x9e, 0x56, 0x3d, 0xb6, 0x43, 0x26, 0x3f, 0x57, 0x50, 0xed,
0x5a, 0xbc, 0x1b, 0xa5, 0xdc, 0xbe, 0x88, 0xaa, 0x9b, 0x41, 0xc8, 0x38, 0x5c, 0x39, 0x55, 0x17,
0x4f, 0x32, 0x2c, 0x01, 0xfd, 0x93, 0x60, 0xe9, 0x1e, 0x91, 0x4e, 0xfb, 0x33, 0xd4, 0x94, 0xff,
0x19, 0x27, 0x01, 0xe3, 0xd0, 0xfd, 0x55, 0xf7, 0x23, 0xf1, 0x25, 0x06, 0xac, 0xbf, 0xc4, 0xc0,
0x74, 0x22, 0x93, 0x68, 0x5f, 0x45, 0x75, 0x35, 0xdb, 0x38, 0xdc, 0x67, 0x55, 0xf7, 0x3c, 0xcc,
0x55, 0x85, 0x15, 0x73, 0x55, 0x01, 0x3a, 0x8b, 0xa6, 0xd8, 0x9f, 0x14, 0xc2, 0xad, 0x40, 0x86,
0x73, 0xff, 0x25, 0xdc, 0x3c, 0x5e, 0xeb, 0xb7, 0x87, 0xaa, 0xde, 0x38, 0x65, 0xf9, 0xe5, 0xe8,
0x88, 0x3a, 0x00, 0x50, 0x1c, 0xb6, 0xb0, 0x08, 0x95, 0xe8, 0xcc, 0x4d, 0x50, 0x7b, 0xcd, 0x9b,
0x60, 0x03, 0x35, 0xe4, 0x2e, 0xd3, 0x0f, 0x86, 0x70, 0x09, 0xb4, 0xdc, 0x4b, 0x07, 0x19, 0xae,
0xcb, 0xfd, 0x04, 0x6e, 0xc6, 0xba, 0x24, 0xac, 0x0f, 0x75, 0xa2, 0x1c, 0x10, 0xdd, 0xa2, 0x99,
0x54, 0xf3, 0x84, 0xc4, 0xcc, 0x41, 0x62, 0xbf, 0xc9, 0x1c, 0x51, 0x0d, 0xf2, 0xad, 0x85, 0x1a,
0x52, 0x1e, 0x1b, 0x2c, 0xb5, 0xaf, 0xa2, 0x9a, 0x0f, 0x86, 0xea, 0x10, 0x24, 0x76, 0x23, 0xe9,
0x2e, 0x1a, 0x43, 0x32, 0x74, 0xad, 0xc0, 0x24, 0x54, 0xc1, 0x62, 0xa8, 0xf8, 0x09, 0x1b, 0xe4,
0x3b, 0x63, 0x59, 0x0e, 0x15, 0x05, 0xe9, 0xb3, 0x51, 0x36, 0xa1, 0xb9, 0x87, 0x7c, 0x37, 0x87,
0x4e, 0x1b, 0x5b, 0xd8, 0x1a, 0x1b, 0x25, 0x4c, 0x2e, 0x4a, 0xc7, 0xbb, 0xd3, 0xae, 0xa2, 0x9a,
0xac, 0x23, 0x7c, 0x5e, 0xcb, 0x3d, 0x2b, 0x7e, 0x49, 0x22, 0x47, 0x36, 0x53, 0x85, 0x8b, 0x7f,
0xca, 0x07, 0x5e, 0xb9, 0x18, 0x94, 0x2f, 0x1b, 0x71, 0xc5, 0x50, 0xbb, 0x34, 0xab, 0xd3, 0x57,
0x1d, 0xb0, 0x64, 0x0f, 0x9d, 0x36, 0x76, 0x56, 0xa3, 0x14, 0x5f, 0x1e, 0xd9, 0x5e, 0xdf, 0x7e,
0x61, 0x7b, 0x2d, 0xc8, 0xee, 0xfb, 0xaa, 0x28, 0x2f, 0x5f, 0x5c, 0x5f, 0xdc, 0x54, 0xdd, 0x1b,
0xcf, 0x9e, 0xb7, 0x4b, 0xfb, 0xcf, 0xdb, 0xa5, 0x67, 0x07, 0x6d, 0x6b, 0xff, 0xa0, 0x6d, 0x7d,
0x7f, 0xd8, 0x2e, 0x3d, 0x3d, 0x6c, 0x5b, 0xfb, 0x87, 0xed, 0xd2, 0xef, 0x87, 0xed, 0xd2, 0x57,
0xe7, 0x5f, 0x61, 0x49, 0x1b, 0x7a, 0x5e, 0x0d, 0x4e, 0xe8, 0xc2, 0xbf, 0x01, 0x00, 0x00, 0xff,
0xff, 0xfc, 0x01, 0x79, 0xc2, 0x02, 0x0d, 0x00, 0x00,
// 1415 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0xdb, 0xc6,
0x12, 0x36, 0x2d, 0xf9, 0x87, 0x56, 0xb2, 0x13, 0x33, 0x2f, 0x01, 0x9f, 0xdf, 0x7b, 0x5a, 0xbd,
0x8d, 0x03, 0xa8, 0x2d, 0x20, 0x03, 0x0e, 0x62, 0x14, 0x01, 0xda, 0x20, 0x8c, 0xe1, 0xc4, 0x41,
0x9a, 0x14, 0xeb, 0x20, 0x2d, 0x7a, 0x11, 0xf8, 0x63, 0x2d, 0x13, 0xa1, 0x48, 0x95, 0x4b, 0xdb,
0x51, 0x6e, 0xbd, 0x14, 0xe8, 0x2d, 0x08, 0x7a, 0x28, 0x8a, 0xa2, 0xc8, 0xa9, 0x7f, 0x42, 0xff,
0x82, 0x1e, 0x72, 0xf4, 0xb1, 0xe8, 0x81, 0x45, 0xec, 0x4b, 0xab, 0xa3, 0x4e, 0x45, 0x4f, 0xc5,
0xce, 0x2e, 0x97, 0x54, 0x8c, 0x14, 0x49, 0xea, 0x1b, 0xe7, 0x9b, 0x6f, 0x46, 0xe4, 0xec, 0x37,
0xb3, 0x23, 0xf4, 0xaf, 0x30, 0x70, 0x57, 0x7d, 0x77, 0x95, 0xa7, 0xc9, 0x9e, 0x97, 0xf2, 0xce,
0x20, 0x89, 0xd3, 0xd8, 0x9c, 0xf6, 0xdd, 0xe5, 0x8b, 0x09, 0x1b, 0xc4, 0x7c, 0x15, 0x00, 0x77,
0x6f, 0x67, 0xb5, 0x17, 0xf7, 0x62, 0x30, 0xe0, 0x49, 0x12, 0x97, 0x71, 0x2f, 0x8e, 0x7b, 0x21,
0x2b, 0x58, 0x69, 0xd0, 0x67, 0x3c, 0x75, 0xfa, 0x03, 0x45, 0xb8, 0x20, 0xf2, 0xc3, 0xa3, 0x17,
0x87, 0xab, 0x2e, 0xcb, 0xf1, 0x1a, 0x7b, 0x94, 0xca, 0x47, 0xf2, 0xfd, 0x34, 0xaa, 0x6f, 0x06,
0x21, 0x7b, 0xc0, 0x12, 0x1e, 0xc4, 0x91, 0x79, 0x07, 0xcd, 0xed, 0xcb, 0x47, 0xcb, 0x68, 0x19,
0xed, 0xfa, 0xda, 0xd9, 0x4e, 0x9e, 0xa0, 0xf3, 0x80, 0x79, 0x69, 0x9c, 0xd8, 0xad, 0xe7, 0x19,
0x9e, 0x1a, 0x65, 0x38, 0x27, 0x8e, 0x33, 0xbc, 0xf0, 0xa8, 0x1f, 0x5e, 0x25, 0xca, 0x26, 0x34,
0xf7, 0x98, 0xeb, 0x68, 0xce, 0x67, 0x21, 0x4b, 0x99, 0x6f, 0x4d, 0xb7, 0x8c, 0xf6, 0xbc, 0xfd,
0x5f, 0x11, 0xa7, 0x20, 0x1d, 0xa7, 0x6c, 0x42, 0x73, 0x8f, 0x79, 0x45, 0xc4, 0xed, 0x07, 0x1e,
0xe3, 0x56, 0xa5, 0x55, 0x69, 0x37, 0xec, 0xff, 0xc8, 0x38, 0x80, 0xc6, 0x19, 0x6e, 0xa8, 0x38,
0x61, 0x43, 0x18, 0x38, 0x4c, 0x8a, 0xce, 0x04, 0xd1, 0xbe, 0x13, 0x06, 0x7e, 0x37, 0x0f, 0xaf,
0x42, 0xf8, 0x3b, 0xa3, 0x0c, 0x2f, 0x2a, 0xd7, 0x86, 0xce, 0x72, 0x0e, 0xb2, 0x4c, 0xc0, 0x84,
0xbe, 0x44, 0x23, 0x5f, 0x18, 0xa8, 0xae, 0x8a, 0x73, 0x27, 0xe0, 0xa9, 0x19, 0xa2, 0x79, 0xf5,
0x75, 0xdc, 0x32, 0x5a, 0x95, 0x76, 0x7d, 0xed, 0x4c, 0xc7, 0x77, 0x3b, 0xa5, 0x1a, 0xda, 0xd7,
0x44, 0x81, 0x8e, 0x32, 0x5c, 0xa7, 0xce, 0x81, 0xc2, 0xf8, 0x28, 0xc3, 0x3a, 0xee, 0x44, 0xc1,
0x9e, 0x1e, 0xae, 0x94, 0xb9, 0x54, 0x33, 0xaf, 0x56, 0xbf, 0x79, 0x86, 0xa7, 0xc8, 0x1f, 0x08,
0x2d, 0x89, 0x1f, 0xd8, 0x8a, 0x76, 0xe2, 0xfb, 0xc9, 0x5e, 0xe4, 0x39, 0xa2, 0x48, 0xef, 0xa2,
0x6a, 0xe4, 0xf4, 0x19, 0x9c, 0x53, 0xcd, 0xbe, 0x30, 0xca, 0x30, 0xd8, 0xe3, 0x0c, 0x23, 0xc8,
0x2e, 0x0c, 0x42, 0x01, 0x13, 0x5c, 0x1e, 0x3c, 0x66, 0x56, 0xa5, 0x65, 0xb4, 0x2b, 0x92, 0x2b,
0x6c, 0xcd, 0x15, 0x06, 0xa1, 0x80, 0x99, 0xd7, 0x10, 0xea, 0xc7, 0x7e, 0xb0, 0x13, 0x30, 0xbf,
0xcb, 0xad, 0x19, 0x88, 0x68, 0x8d, 0x32, 0x5c, 0xcb, 0xd1, 0xed, 0x71, 0x86, 0xcf, 0x40, 0x98,
0x46, 0x08, 0x2d, 0xbc, 0xe6, 0x8f, 0x06, 0xaa, 0xeb, 0x0c, 0xee, 0xd0, 0x6a, 0xb4, 0x8c, 0x76,
0xd5, 0xfe, 0xda, 0x10, 0x65, 0xf9, 0x25, 0xc3, 0x97, 0x7b, 0x41, 0xba, 0xbb, 0xe7, 0x76, 0xbc,
0xb8, 0xbf, 0xca, 0x87, 0x91, 0x97, 0xee, 0x06, 0x51, 0xaf, 0xf4, 0x54, 0x16, 0x6d, 0x67, 0x7b,
0x37, 0x4e, 0xd2, 0xad, 0x8d, 0x51, 0x86, 0xf5, 0x4b, 0xd9, 0xc3, 0x71, 0x86, 0xcf, 0x4e, 0xfc,
0xbe, 0x3d, 0x24, 0xdf, 0x1e, 0xae, 0xbc, 0x4d, 0x62, 0x5a, 0x4a, 0x5b, 0x16, 0x7f, 0xed, 0x9f,
0x8b, 0xff, 0x2a, 0x9a, 0xe7, 0xec, 0xf3, 0x3d, 0x16, 0x79, 0xcc, 0x42, 0x50, 0xc5, 0xa6, 0x50,
0x41, 0x8e, 0x8d, 0x33, 0xbc, 0x28, 0x6b, 0xaf, 0x00, 0x42, 0xb5, 0xcf, 0xbc, 0x87, 0x16, 0xf9,
0xb0, 0x1f, 0x06, 0xd1, 0xc3, 0x6e, 0xea, 0x24, 0x3d, 0x96, 0x5a, 0x4b, 0x70, 0xca, 0xed, 0x51,
0x86, 0x17, 0x94, 0xe7, 0x3e, 0x38, 0xb4, 0x8e, 0x27, 0x50, 0x42, 0x27, 0x59, 0xe6, 0x0d, 0x54,
0x77, 0xc3, 0xd8, 0x7b, 0xc8, 0xbb, 0xbb, 0x0e, 0xdf, 0xb5, 0xcc, 0x96, 0xd1, 0x6e, 0xd8, 0x44,
0x94, 0x55, 0xc2, 0xb7, 0x1c, 0xbe, 0xab, 0xcb, 0x5a, 0x40, 0x84, 0x96, 0xfc, 0xe6, 0x87, 0xa8,
0xc6, 0x22, 0x2f, 0x19, 0x0e, 0x44, 0x43, 0x9f, 0x83, 0x14, 0x20, 0x0c, 0x0d, 0x6a, 0x61, 0x68,
0x84, 0xd0, 0xc2, 0x6b, 0xda, 0xa8, 0x9a, 0x0e, 0x07, 0x0c, 0x66, 0xc1, 0xe2, 0xda, 0x85, 0xa2,
0xb8, 0x5a, 0xdc, 0xc3, 0x01, 0x93, 0xea, 0x14, 0x3c, 0xad, 0x4e, 0x61, 0x10, 0x0a, 0x98, 0xb9,
0x89, 0xea, 0x03, 0x96, 0xf4, 0x03, 0x2e, 0x5b, 0xb0, 0xda, 0x32, 0xda, 0x0b, 0xf6, 0xca, 0x28,
0xc3, 0x65, 0x78, 0x9c, 0xe1, 0x25, 0x88, 0x2c, 0x61, 0x84, 0x96, 0x19, 0xe6, 0xed, 0x92, 0x46,
0x23, 0x6e, 0xd5, 0x5b, 0x46, 0x7b, 0x06, 0xe6, 0x84, 0x16, 0xc4, 0x5d, 0x7e, 0x42, 0x67, 0x77,
0x39, 0xf9, 0x33, 0xc3, 0x95, 0x20, 0x4a, 0x69, 0x89, 0x66, 0xee, 0x20, 0x59, 0xa5, 0x2e, 0xf4,
0xd8, 0x02, 0xa4, 0xba, 0x79, 0x94, 0xe1, 0x06, 0x75, 0x0e, 0x6c, 0xe1, 0xd8, 0x0e, 0x1e, 0x33,
0x51, 0x28, 0x37, 0x37, 0x74, 0xa1, 0x34, 0x92, 0x27, 0x7e, 0x7a, 0xb8, 0x32, 0x11, 0x46, 0x8b,
0x20, 0x73, 0x03, 0xd5, 0xc3, 0xd8, 0x73, 0xc2, 0xee, 0x4e, 0xe8, 0xf4, 0xb8, 0xf5, 0xdb, 0x1c,
0x7c, 0x3c, 0x9c, 0x22, 0xe0, 0x9b, 0x02, 0xd6, 0x2f, 0x5d, 0x40, 0x84, 0x96, 0xfc, 0xe6, 0x2d,
0xd4, 0x50, 0x12, 0x95, 0x5a, 0xf8, 0x7d, 0x0e, 0x4e, 0x12, 0x6a, 0xa8, 0x1c, 0x4a, 0x0d, 0x4b,
0x65, 0x65, 0x4b, 0x39, 0x94, 0x19, 0xe5, 0xf1, 0x3e, 0xfb, 0x26, 0xe3, 0x9d, 0xa2, 0x39, 0x35,
0x65, 0xad, 0x39, 0x88, 0x7b, 0xff, 0x28, 0xc3, 0x88, 0x3a, 0x07, 0x5b, 0x12, 0x15, 0x59, 0x14,
0x41, 0x67, 0x51, 0xb6, 0x98, 0x95, 0x25, 0x26, 0xcd, 0x79, 0xa2, 0x63, 0xa2, 0xb8, 0x5b, 0x96,
0xc6, 0x3c, 0xa4, 0x86, 0x8e, 0x89, 0xe2, 0x8f, 0x27, 0xc4, 0x21, 0x3b, 0x66, 0x02, 0x25, 0x74,
0x92, 0xa5, 0x46, 0xef, 0x27, 0xa8, 0x06, 0x47, 0x01, 0xb3, 0xff, 0x36, 0x9a, 0x95, 0xdd, 0xa0,
0x26, 0xff, 0xb9, 0x42, 0xc1, 0x40, 0x12, 0x12, 0xb6, 0xff, 0xa7, 0x26, 0x84, 0xa2, 0x8e, 0x33,
0x5c, 0x2f, 0x4e, 0x9a, 0x50, 0x05, 0x93, 0x1f, 0x0c, 0x74, 0x7e, 0x2b, 0xf2, 0x83, 0x84, 0x79,
0xa9, 0xaa, 0x27, 0xe3, 0xf7, 0xa2, 0x70, 0x78, 0x3a, 0xad, 0x7a, 0x6a, 0x87, 0x4c, 0xbe, 0xab,
0xa2, 0xd9, 0x1b, 0xf1, 0x5e, 0x94, 0x72, 0xf3, 0x0a, 0x9a, 0xd9, 0x09, 0x42, 0xc6, 0xe1, 0xca,
0x99, 0xb1, 0xf1, 0x28, 0xc3, 0x12, 0xd0, 0x1f, 0x09, 0x96, 0xee, 0x11, 0xe9, 0x34, 0x3f, 0x42,
0x75, 0xf9, 0x9d, 0x71, 0x12, 0x30, 0x0e, 0xdd, 0x3f, 0x63, 0xbf, 0x27, 0xde, 0xa4, 0x04, 0xeb,
0x37, 0x29, 0x61, 0x3a, 0x51, 0x99, 0x68, 0x5e, 0x47, 0xf3, 0x6a, 0xb6, 0x71, 0xb8, 0xcf, 0x66,
0xec, 0x4b, 0x30, 0x57, 0x15, 0x56, 0xcc, 0x55, 0x05, 0xe8, 0x2c, 0x9a, 0x62, 0x7e, 0x50, 0x08,
0xb7, 0x0a, 0x19, 0x2e, 0xfe, 0x9d, 0x70, 0xf3, 0x78, 0xad, 0xdf, 0x0e, 0x9a, 0x71, 0x87, 0x29,
0xcb, 0x2f, 0x47, 0x4b, 0xd4, 0x01, 0x80, 0xe2, 0xb0, 0x85, 0x45, 0xa8, 0x44, 0x27, 0x6e, 0x82,
0xd9, 0x37, 0xbc, 0x09, 0xb6, 0x51, 0x4d, 0xee, 0x32, 0xdd, 0xc0, 0x87, 0x4b, 0xa0, 0x61, 0xaf,
0x1f, 0x65, 0x78, 0x5e, 0xee, 0x27, 0x70, 0x33, 0xce, 0x4b, 0xc2, 0x96, 0xaf, 0x13, 0xe5, 0x80,
0xe8, 0x16, 0xcd, 0xa4, 0x9a, 0x27, 0x24, 0x56, 0x1e, 0x24, 0xe6, 0xdb, 0xcc, 0x11, 0xd5, 0x20,
0x5f, 0x1a, 0xa8, 0x26, 0xe5, 0xb1, 0xcd, 0x52, 0xf3, 0x3a, 0x9a, 0xf5, 0xc0, 0x50, 0x1d, 0x82,
0xc4, 0x6e, 0x24, 0xdd, 0x45, 0x63, 0x48, 0x86, 0xae, 0x15, 0x98, 0x84, 0x2a, 0x58, 0x0c, 0x15,
0x2f, 0x61, 0x4e, 0xbe, 0x33, 0x56, 0xe4, 0x50, 0x51, 0x90, 0x3e, 0x1b, 0x65, 0x13, 0x9a, 0x7b,
0xc8, 0x57, 0xd3, 0xe8, 0x7c, 0x69, 0x0b, 0xdb, 0x60, 0x83, 0x84, 0xc9, 0x45, 0xe9, 0x74, 0x77,
0xda, 0x35, 0x34, 0x2b, 0xeb, 0x08, 0xaf, 0xd7, 0xb0, 0x97, 0xc5, 0x27, 0x49, 0xe4, 0xc4, 0x66,
0xaa, 0x70, 0xf1, 0x4d, 0xf9, 0xc0, 0xab, 0x14, 0x83, 0xf2, 0x55, 0x23, 0xae, 0x18, 0x6a, 0xeb,
0x93, 0x3a, 0x7d, 0xdd, 0x01, 0x4b, 0x0e, 0xd0, 0xf9, 0xd2, 0xce, 0x5a, 0x2a, 0xc5, 0xa7, 0x27,
0xb6, 0xd7, 0x7f, 0xbf, 0xb4, 0xbd, 0x16, 0x64, 0xfb, 0xff, 0xaa, 0x28, 0xaf, 0x5e, 0x5c, 0x4f,
0x6c, 0xaa, 0x4f, 0x0c, 0xb4, 0x78, 0xcf, 0xe5, 0x2c, 0xd9, 0x67, 0xfe, 0x66, 0x1c, 0xfa, 0x2c,
0x31, 0xef, 0xa2, 0xaa, 0xf8, 0x5f, 0xa2, 0x4a, 0xbf, 0xdc, 0x91, 0x7f, 0x5a, 0x3a, 0xf9, 0x9f,
0x96, 0xce, 0xfd, 0xfc, 0x4f, 0x8b, 0xdd, 0x54, 0xbf, 0x07, 0xfc, 0xe2, 0xf2, 0x0f, 0xfa, 0x8c,
0x3c, 0xf9, 0x15, 0x1b, 0x14, 0x70, 0xd1, 0x7c, 0xa1, 0xe3, 0xb2, 0x10, 0xca, 0x5f, 0x93, 0xcd,
0x07, 0x80, 0x16, 0x14, 0x58, 0x84, 0x4a, 0x94, 0xfc, 0x54, 0x7a, 0x25, 0xd9, 0x0a, 0xa7, 0xfe,
0x4a, 0xf9, 0x26, 0x3e, 0xfd, 0x1a, 0x9b, 0xf8, 0x3a, 0x9a, 0x73, 0x7c, 0x3f, 0x61, 0x5c, 0x0e,
0xaf, 0x9a, 0x3c, 0x52, 0x05, 0xe9, 0x02, 0x2b, 0x9b, 0xd0, 0xdc, 0x63, 0xdf, 0x7c, 0xfe, 0xa2,
0x39, 0x75, 0xf8, 0xa2, 0x39, 0xf5, 0xfc, 0xa8, 0x69, 0x1c, 0x1e, 0x35, 0x8d, 0x27, 0xc7, 0xcd,
0xa9, 0x67, 0xc7, 0x4d, 0xe3, 0xf0, 0xb8, 0x39, 0xf5, 0xf3, 0x71, 0x73, 0xea, 0xb3, 0x4b, 0xaf,
0xb1, 0xfe, 0xfa, 0xae, 0x3b, 0x0b, 0x9f, 0x79, 0xf9, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9e,
0xa0, 0xbe, 0xf2, 0x7d, 0x0e, 0x00, 0x00,
}
func (m *FileVersion) Marshal() (dAtA []byte, err error) {
@@ -1031,6 +1122,89 @@ func (m *VersionListDeprecated) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *ObservedFolder) Marshal() (dAtA []byte, err error) {
size := m.ProtoSize()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ObservedFolder) MarshalTo(dAtA []byte) (int, error) {
size := m.ProtoSize()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *ObservedFolder) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Label) > 0 {
i -= len(m.Label)
copy(dAtA[i:], m.Label)
i = encodeVarintStructs(dAtA, i, uint64(len(m.Label)))
i--
dAtA[i] = 0x12
}
n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):])
if err4 != nil {
return 0, err4
}
i -= n4
i = encodeVarintStructs(dAtA, i, uint64(n4))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func (m *ObservedDevice) Marshal() (dAtA []byte, err error) {
size := m.ProtoSize()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ObservedDevice) MarshalTo(dAtA []byte) (int, error) {
size := m.ProtoSize()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *ObservedDevice) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Address) > 0 {
i -= len(m.Address)
copy(dAtA[i:], m.Address)
i = encodeVarintStructs(dAtA, i, uint64(len(m.Address)))
i--
dAtA[i] = 0x1a
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintStructs(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0x12
}
n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):])
if err5 != nil {
return 0, err5
}
i -= n5
i = encodeVarintStructs(dAtA, i, uint64(n5))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func encodeVarintStructs(dAtA []byte, offset int, v uint64) int {
offset -= sovStructs(v)
base := offset
@@ -1270,6 +1444,40 @@ func (m *VersionListDeprecated) ProtoSize() (n int) {
return n
}
func (m *ObservedFolder) ProtoSize() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time)
n += 1 + l + sovStructs(uint64(l))
l = len(m.Label)
if l > 0 {
n += 1 + l + sovStructs(uint64(l))
}
return n
}
func (m *ObservedDevice) ProtoSize() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time)
n += 1 + l + sovStructs(uint64(l))
l = len(m.Name)
if l > 0 {
n += 1 + l + sovStructs(uint64(l))
}
l = len(m.Address)
if l > 0 {
n += 1 + l + sovStructs(uint64(l))
}
return n
}
func sovStructs(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
@@ -2797,6 +3005,274 @@ func (m *VersionListDeprecated) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *ObservedFolder) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ObservedFolder: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ObservedFolder: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthStructs
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Label", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthStructs
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Label = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipStructs(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthStructs
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthStructs
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *ObservedDevice) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ObservedDevice: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ObservedDevice: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthStructs
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthStructs
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthStructs
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Address = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipStructs(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthStructs
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthStructs
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipStructs(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0

View File

@@ -12,6 +12,7 @@ import (
"fmt"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -25,7 +26,8 @@ var (
// A readOnlyTransaction represents a database snapshot.
type readOnlyTransaction struct {
backend.ReadTransaction
keyer keyer
keyer keyer
evLogger events.Logger
}
func (db *Lowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) {
@@ -36,6 +38,7 @@ func (db *Lowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) {
return readOnlyTransaction{
ReadTransaction: tran,
keyer: db.keyer,
evLogger: db.evLogger,
}, nil
}
@@ -800,6 +803,7 @@ func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device, file
if !haveOldGlobal {
// Shouldn't ever happen, but doesn't hurt to handle.
t.evLogger.Log(events.Failure, "encountered empty global while removing item")
return keyBuf, t.Delete(gk)
}
@@ -889,12 +893,19 @@ func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device, file
}
func (t readWriteTransaction) deleteKeyPrefix(prefix []byte) error {
return t.deleteKeyPrefixMatching(prefix, func([]byte) bool { return true })
}
func (t readWriteTransaction) deleteKeyPrefixMatching(prefix []byte, match func(key []byte) bool) error {
dbi, err := t.NewPrefixIterator(prefix)
if err != nil {
return err
}
defer dbi.Release()
for dbi.Next() {
if !match(dbi.Key()) {
continue
}
if err := t.Delete(dbi.Key()); err != nil {
return err
}

View File

@@ -10,10 +10,11 @@ import (
"encoding/json"
"io"
"os"
// "testing"
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
// "github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
// "github.com/syncthing/syncthing/lib/protocol"
)
@@ -69,6 +70,28 @@ func openJSONS(file string) (backend.Backend, error) {
return db, nil
}
func newLowlevel(t testing.TB, backend backend.Backend) *Lowlevel {
t.Helper()
ll, err := NewLowlevel(backend, events.NoopLogger)
if err != nil {
t.Fatal(err)
}
return ll
}
func newLowlevelMemory(t testing.TB) *Lowlevel {
return newLowlevel(t, backend.OpenMemory())
}
func newFileSet(t testing.TB, folder string, fs fs.Filesystem, db *Lowlevel) *FileSet {
t.Helper()
fset, err := NewFileSet(folder, fs, db)
if err != nil {
t.Fatal(err)
}
return fset
}
// The following commented tests were used to generate jsons files to stdout for
// future tests and are kept here for reference (reuse).
@@ -76,7 +99,7 @@ func openJSONS(file string) (backend.Backend, error) {
// local and remote, in the format used in 0.14.48.
// func TestGenerateIgnoredFilesDB(t *testing.T) {
// db := OpenMemory()
// fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// fs.Update(protocol.LocalDeviceID, []protocol.FileInfo{
// { // invalid (ignored) file
// Name: "foo",
@@ -111,7 +134,7 @@ func openJSONS(file string) (backend.Backend, error) {
// format used in 0.14.45.
// func TestGenerateUpdate0to3DB(t *testing.T) {
// db := OpenMemory()
// fs := NewFileSet(update0to3Folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// fs := newFileSet(t, update0to3Folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// for devID, files := range haveUpdate0to3 {
// fs.Update(devID, files)
// }
@@ -119,14 +142,14 @@ func openJSONS(file string) (backend.Backend, error) {
// }
// func TestGenerateUpdateTo10(t *testing.T) {
// db := NewLowlevel(backend.OpenMemory())
// db := newLowlevelMemory(t)
// defer db.Close()
// if err := UpdateSchema(db); err != nil {
// t.Fatal(err)
// }
// fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
// fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
// files := []protocol.FileInfo{
// {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Deleted: true, Sequence: 1},

View File

@@ -24,7 +24,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/syncthing/syncthing/lib/svcutil"
"github.com/thejerf/suture/v4"
)
@@ -52,7 +52,7 @@ const (
func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
c := &localClient{
Supervisor: suture.New("local", util.Spec()),
Supervisor: suture.New("local", svcutil.SpecWithDebugLogger(l)),
myID: id,
addrList: addrList,
evLogger: evLogger,
@@ -81,9 +81,9 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogge
c.beacon = beacon.NewMulticast(addr)
}
c.Add(c.beacon)
c.Add(util.AsService(c.recvAnnouncements, fmt.Sprintf("%s/recv", c)))
c.Add(svcutil.AsService(c.recvAnnouncements, fmt.Sprintf("%s/recv", c)))
c.Add(util.AsService(c.sendLocalAnnouncements, fmt.Sprintf("%s/sendLocal", c)))
c.Add(svcutil.AsService(c.sendLocalAnnouncements, fmt.Sprintf("%s/sendLocal", c)))
return c, nil
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
@@ -47,7 +48,7 @@ type manager struct {
func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate, evLogger events.Logger, lister AddressLister) Manager {
m := &manager{
Supervisor: suture.New("discover.Manager", util.Spec()),
Supervisor: suture.New("discover.Manager", svcutil.SpecWithDebugLogger(l)),
myID: myID,
cfg: cfg,
cert: cert,
@@ -57,7 +58,7 @@ func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate
finders: make(map[string]cachedFinder),
mut: sync.NewRWMutex(),
}
m.Add(util.AsService(m.serve, m.String()))
m.Add(svcutil.AsService(m.serve, m.String()))
return m
}

Some files were not shown because too many files have changed in this diff Show More