Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d842197931 | ||
|
|
4e7510dea9 | ||
|
|
c0f353c0e8 | ||
|
|
e95d005c21 | ||
|
|
11e9d575c8 | ||
|
|
46bbc78e82 | ||
|
|
50a621bc7b | ||
|
|
d0c84916c7 | ||
|
|
6db8dc33f2 | ||
|
|
194501c958 | ||
|
|
27a34609a1 | ||
|
|
ffc14a77c6 | ||
|
|
31119ed61a | ||
|
|
070bf3b776 | ||
|
|
ade8d79d42 | ||
|
|
42917d707d | ||
|
|
1522cf74bc | ||
|
|
3b7a57d108 | ||
|
|
a7d9268e4d | ||
|
|
052dc13487 | ||
|
|
249bcb3a01 | ||
|
|
fbe52faf49 | ||
|
|
8b86171642 | ||
|
|
e19d6e993d | ||
|
|
ef0473c091 | ||
|
|
3406a3ba95 | ||
|
|
7c1ed420a9 | ||
|
|
d7e86af6c6 | ||
|
|
76cf326290 | ||
|
|
6c3e187d1d | ||
|
|
e32a516b5f | ||
|
|
6da83ac9f5 | ||
|
|
adc07eddf6 | ||
|
|
9c88efd55f | ||
|
|
ffcb57580f | ||
|
|
abfbd13f17 | ||
|
|
f63cdbfcfa | ||
|
|
b2d82da20d | ||
|
|
70fddb6523 | ||
|
|
83cfd308b4 | ||
|
|
36acaf5e21 | ||
|
|
572ccfe3e2 | ||
|
|
253049a4cc | ||
|
|
8f199e12b3 | ||
|
|
f6fac3e949 | ||
|
|
0b193b76c2 | ||
|
|
e6f0ed65be | ||
|
|
b13b15758d | ||
|
|
c48eb4241a | ||
|
|
0f8290485e | ||
|
|
3d1edd2492 | ||
|
|
a73db6fd84 | ||
|
|
a05dc6cc47 | ||
|
|
5440d1dc3b | ||
|
|
f13e6ca631 | ||
|
|
81553b4da7 | ||
|
|
07618f8674 | ||
|
|
1555a4da7f | ||
|
|
a20a5f61f0 | ||
|
|
4bcc38cf63 | ||
|
|
05f25e600e | ||
|
|
a744dee94c | ||
|
|
78bd0341a8 | ||
|
|
b5de49917c | ||
|
|
c845e245a1 | ||
|
|
4a787986cd | ||
|
|
78a41828fc | ||
|
|
bd0c9913cf | ||
|
|
d904dfa191 | ||
|
|
fa40ccece1 | ||
|
|
7919310dc6 | ||
|
|
7669af578a | ||
|
|
739e99c4d9 | ||
|
|
7502997e7e | ||
|
|
4470cd5aaa | ||
|
|
466e8a5cd0 | ||
|
|
4142a431b5 | ||
|
|
5565afdd9f | ||
|
|
0db3b7a530 | ||
|
|
b37ecc3cf4 | ||
|
|
ec5a5d5218 | ||
|
|
7980c8cea2 | ||
|
|
e9b68a224c | ||
|
|
8fd6b1d428 | ||
|
|
4198b5061f | ||
|
|
b0a525a504 | ||
|
|
25d904dc37 |
@@ -61,6 +61,7 @@ Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||
chenrui <rui@meetup.com>
|
||||
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
||||
Chris Tonkinson <chris@masterbran.ch>
|
||||
@@ -87,6 +88,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>
|
||||
@@ -211,9 +213,11 @@ Phill Luby (pluby) <phill.luby@newredo.com>
|
||||
Pier Paolo Ramon <ramonpierre@gmail.com>
|
||||
Piotr Bejda (piobpl) <piotrb10@gmail.com>
|
||||
Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.github.com>
|
||||
Quentin Hibon <qh.public@yahoo.com>
|
||||
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>
|
||||
|
||||
@@ -666,11 +666,14 @@ func shouldBuildSyso(dir string) (string, error) {
|
||||
},
|
||||
},
|
||||
"StringFileInfo": M{
|
||||
"FileDescription": "Open Source Continuous File Synchronization",
|
||||
"LegalCopyright": "The Syncthing Authors",
|
||||
"FileVersion": version,
|
||||
"ProductVersion": version,
|
||||
"ProductName": "Syncthing",
|
||||
"CompanyName": "The Syncthing Authors",
|
||||
"FileDescription": "Syncthing - Open Source Continuous File Synchronization",
|
||||
"FileVersion": version,
|
||||
"InternalName": "syncthing",
|
||||
"LegalCopyright": "The Syncthing Authors",
|
||||
"OriginalFilename": "syncthing",
|
||||
"ProductName": "Syncthing",
|
||||
"ProductVersion": version,
|
||||
},
|
||||
"IconPath": "assets/logo.ico",
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ func main() {
|
||||
mux.Handle("/", cr)
|
||||
|
||||
if *dsn != "" {
|
||||
mux.HandleFunc("/failure", handleFailureFn(*dsn))
|
||||
mux.HandleFunc("/newcrash/failure", handleFailureFn(*dsn))
|
||||
}
|
||||
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -186,10 +186,10 @@ func main() {
|
||||
}
|
||||
|
||||
wrapper := config.Wrap("config", config.New(id), id, events.NoopLogger)
|
||||
wrapper.SetOptions(config.OptionsConfiguration{
|
||||
NATLeaseM: natLease,
|
||||
NATRenewalM: natRenewal,
|
||||
NATTimeoutS: natTimeout,
|
||||
wrapper.Modify(func(cfg *config.Configuration) {
|
||||
cfg.Options.NATLeaseM = natLease
|
||||
cfg.Options.NATRenewalM = natRenewal
|
||||
cfg.Options.NATTimeoutS = natTimeout
|
||||
})
|
||||
natSvc := nat.NewService(id, wrapper)
|
||||
mapping := mapping{natSvc.NewMapping(nat.TCP, addr.IP, addr.Port)}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-2
@@ -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)
|
||||
|
||||
+70
-54
@@ -39,12 +39,13 @@ 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"
|
||||
"github.com/thejerf/suture/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -323,7 +324,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 +361,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 +371,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 +379,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 +387,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 +425,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
|
||||
@@ -602,15 +603,25 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
evLogger := events.NewLogger()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go evLogger.Serve(ctx)
|
||||
defer cancel()
|
||||
|
||||
cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
|
||||
// earlyService is a supervisor that runs the services needed for or
|
||||
// before app startup; the event logger, and the config service.
|
||||
spec := svcutil.SpecWithDebugLogger(l)
|
||||
earlyService := suture.New("early", spec)
|
||||
earlyService.ServeBackground(ctx)
|
||||
|
||||
evLogger := events.NewLogger()
|
||||
earlyService.Add(evLogger)
|
||||
|
||||
cfgWrapper, 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())
|
||||
}
|
||||
if cfgService, ok := cfgWrapper.(suture.Service); ok {
|
||||
earlyService.Add(cfgService)
|
||||
}
|
||||
|
||||
// Candidate builds should auto upgrade. Make sure the option is set,
|
||||
@@ -618,20 +629,20 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
// environment variable is set.
|
||||
|
||||
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
|
||||
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
|
||||
if opts := cfg.Options(); opts.AutoUpgradeIntervalH == 0 || opts.AutoUpgradeIntervalH > 24 {
|
||||
opts.AutoUpgradeIntervalH = 12
|
||||
// Set the option into the config as well, as the auto upgrade
|
||||
// loop expects to read a valid interval from there.
|
||||
cfg.SetOptions(opts)
|
||||
cfg.Save()
|
||||
}
|
||||
// We don't tweak the user's choice of upgrading to pre-releases or
|
||||
// not, as otherwise they cannot step off the candidate channel.
|
||||
cfgWrapper.Modify(func(cfg *config.Configuration) {
|
||||
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
|
||||
if cfg.Options.AutoUpgradeIntervalH == 0 || cfg.Options.AutoUpgradeIntervalH > 24 {
|
||||
cfg.Options.AutoUpgradeIntervalH = 12
|
||||
// Set the option into the config as well, as the auto upgrade
|
||||
// loop expects to read a valid interval from there.
|
||||
}
|
||||
// We don't tweak the user's choice of upgrading to pre-releases or
|
||||
// not, as otherwise they cannot step off the candidate channel.
|
||||
})
|
||||
}
|
||||
|
||||
dbFile := locations.Get(locations.Database)
|
||||
ldb, err := syncthing.OpenDBBackend(dbFile, cfg.Options().DatabaseTuning)
|
||||
ldb, err := syncthing.OpenDBBackend(dbFile, cfgWrapper.Options().DatabaseTuning)
|
||||
if err != nil {
|
||||
l.Warnln("Error opening database:", err)
|
||||
os.Exit(1)
|
||||
@@ -642,7 +653,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
// later after App is initialised.
|
||||
|
||||
autoUpgradePossible := autoUpgradePossible(runtimeOptions)
|
||||
if autoUpgradePossible && cfg.Options().AutoUpgradeEnabled() {
|
||||
if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() {
|
||||
// try to do upgrade directly and log the error if relevant.
|
||||
release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
|
||||
if err == nil {
|
||||
@@ -656,14 +667,14 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
}
|
||||
} else {
|
||||
l.Infof("Upgraded to %q, exiting now.", release.Tag)
|
||||
os.Exit(util.ExitUpgrade.AsInt())
|
||||
os.Exit(svcutil.ExitUpgrade.AsInt())
|
||||
}
|
||||
}
|
||||
|
||||
if runtimeOptions.unpaused {
|
||||
setPauseState(cfg, false)
|
||||
setPauseState(cfgWrapper, false)
|
||||
} else if runtimeOptions.paused {
|
||||
setPauseState(cfg, true)
|
||||
setPauseState(cfgWrapper, true)
|
||||
}
|
||||
|
||||
appOpts := runtimeOptions.Options
|
||||
@@ -681,10 +692,14 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
appOpts.DBIndirectGCInterval = dur
|
||||
}
|
||||
|
||||
app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
|
||||
app, err := syncthing.New(cfgWrapper, 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)
|
||||
go autoUpgrade(cfgWrapper, app, evLogger)
|
||||
}
|
||||
|
||||
setupSignalHandling(app)
|
||||
@@ -697,31 +712,31 @@ 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)
|
||||
go standbyMonitor(app, cfgWrapper)
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
os.Exit(util.ExitError.AsInt())
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
|
||||
cleanConfigDirectory()
|
||||
|
||||
if cfg.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
|
||||
if cfgWrapper.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
|
||||
// Can potentially block if the utility we are invoking doesn't
|
||||
// fork, and just execs, hence keep it in its own routine.
|
||||
go func() { _ = openURL(cfg.GUI().URL()) }()
|
||||
go func() { _ = openURL(cfgWrapper.GUI().URL()) }()
|
||||
}
|
||||
|
||||
status := app.Wait()
|
||||
|
||||
if status == util.ExitError {
|
||||
if status == svcutil.ExitError {
|
||||
l.Warnln("Syncthing stopped with error:", app.Error())
|
||||
}
|
||||
|
||||
@@ -740,7 +755,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 +764,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 +801,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 +851,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 +921,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
|
||||
}
|
||||
}
|
||||
@@ -984,23 +999,24 @@ func showPaths(options RuntimeOptions) {
|
||||
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
|
||||
}
|
||||
|
||||
func setPauseState(cfg config.Wrapper, paused bool) {
|
||||
raw := cfg.RawCopy()
|
||||
for i := range raw.Devices {
|
||||
raw.Devices[i].Paused = paused
|
||||
}
|
||||
for i := range raw.Folders {
|
||||
raw.Folders[i].Paused = paused
|
||||
}
|
||||
if _, err := cfg.Replace(raw); err != nil {
|
||||
func setPauseState(cfgWrapper config.Wrapper, paused bool) {
|
||||
_, err := cfgWrapper.Modify(func(cfg *config.Configuration) {
|
||||
for i := range cfg.Devices {
|
||||
cfg.Devices[i].Paused = paused
|
||||
}
|
||||
for i := range cfg.Folders {
|
||||
cfg.Folders[i].Paused = paused
|
||||
}
|
||||
})
|
||||
if 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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,12 +14,12 @@ 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/hashicorp/golang-lru v0.5.1
|
||||
github.com/jackpal/gateway v1.0.6
|
||||
github.com/jackpal/go-nat-pmp v1.0.2
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
@@ -37,8 +36,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 +47,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
|
||||
|
||||
@@ -212,6 +212,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
@@ -399,8 +400,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 +456,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 +583,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=
|
||||
|
||||
@@ -41,6 +41,12 @@ ul+h5 {
|
||||
float: none; /* issue #1197 */
|
||||
}
|
||||
|
||||
#advancedAccordion input.form-control[type="checkbox"] {
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-width: none;
|
||||
min-width: 250px;
|
||||
|
||||
@@ -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.": "Така се предоставя изключително лесен достъп (четене, редактиране и изтриване) до всеки файл, на компютъра Ви.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "елемента",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папката \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} желае да сподели папката \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} желае да сподели папката \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "Elements",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vol compartir la carpeta \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vol compartir la carpeta \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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. ",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "položky",
|
||||
"seconds": "sekund",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce sdílet složku „{{folder}}“.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce sdílet složku „{{folderlabel}}“ ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce sdílet složku „{{folderlabel}}“ ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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,9 +324,9 @@
|
||||
"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 interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Interval i sekunder for kørsel af oprydning i versionskatalog. Nul vil deaktivere periodisk rengøring.",
|
||||
"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.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den maksimale tid, en version skal gemmes (i dage; sæt lig med 0 for at beholde gamle versioner for altid).",
|
||||
"The number of days must be a number and cannot be blank.": "Antallet af dage 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",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "filer",
|
||||
"seconds": "sekunder",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen “{{folder}}”.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker at dele mappen “{{folderlabel}}” ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker at dele mappen “{{folderlabel}}” ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "Elemente",
|
||||
"seconds": "Sekunden",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte den Ordner \"{{folder}}\" teilen.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte den Ordner \"{{folderlabel}}\" ({{folder}}) teilen."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte den Ordner \"{{folderlabel}}\" ({{folder}}) teilen.",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.": "Αυτό μπορεί εύκολα να δώσει πρόσβαση ανάγνωσης και επεξεργασίας αρχείων του υπολογιστή σας σε χάκερς.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "εγγραφές",
|
||||
"seconds": "δευτερόλεπτα",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}».",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "Η συσκευή {{device}} επιθυμεί να διαμοιράσει τον φάκελο \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "Η συσκευή {{device}} επιθυμεί να διαμοιράσει τον φάκελο \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "items",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "items",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "items",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "eroj",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas komunigi dosierujon \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas komunigi dosierujon \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas komunigi dosierujon \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "Elementos",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quiere compartir la carpeta \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quiere compartir la carpeta \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "Elementos",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quiere compartir la carpeta \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quiere compartir la carpeta \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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!",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "Elementuak",
|
||||
"seconds": "segunduak",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}}k \"{{folder}}\" partekatze hontan gomitatzen zaitu.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}}k \"{{folderlabel}}\" ({{folder}}) hontan gomitatzen zaitu."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}}k \"{{folderlabel}}\" ({{folder}}) hontan gomitatzen zaitu.",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} -ek gailu hau birsar lezake."
|
||||
}
|
||||
@@ -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",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "kohteet",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} haluaa jakaa kansion \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} haluaa jakaa kansion \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "élément(s)",
|
||||
"seconds": "secondes",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} pourrait ré-enrôler cet appareil."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "items",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wol map \"{{folder}}\" diele.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wol map \"{{folderlabel}}\" ({{folder}}) diele."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wol map \"{{folderlabel}}\" ({{folder}}) diele.",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "elem",
|
||||
"seconds": "másodperc",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} szeretné megosztani a mappát: „{{folder}}”.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} szeretné megosztani a mappát: „{{folderlabel}}” ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} szeretné megosztani a mappát: „{{folderlabel}}” ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} újra bevezetheti ezt az eszközt."
|
||||
}
|
||||
@@ -18,14 +18,14 @@
|
||||
"Advanced": "Avanzato",
|
||||
"Advanced Configuration": "Configurazione Avanzata",
|
||||
"All Data": "Tutti i Dati",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Tutte le cartelle condivise con questo dispositivo devono essere protette da una password, in modo tale che tutti i dati inviati siano illeggibili senza la password fornita.",
|
||||
"Allow Anonymous Usage Reporting?": "Abilitare Statistiche Anonime di Utilizzo?",
|
||||
"Allowed Networks": "Reti Consentite.",
|
||||
"Alphabetic": "Alfabetico",
|
||||
"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.": "Il controllo versione è gestito da un comando esterno. Quest'ultimo deve rimuovere il file dalla cartella condivisa. Se il percorso dell'applicazione contiene spazi, deve essere indicato tra virgolette.",
|
||||
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Il formato delle statistiche anonime di utilizzo è cambiato. Vuoi passare al nuovo formato?",
|
||||
"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?": "Sei sicuro di voler eliminare definitivamente tutti questi file?",
|
||||
"Are you sure you want to remove device {%name%}?": "Sei sicuro di voler rimuovere il dispositivo {{name}}?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Sei sicuro di voler rimuovere la cartella {{label}}?",
|
||||
"Are you sure you want to restore {%count%} files?": "Sei sicuro di voler ripristinare {{count}} file?",
|
||||
@@ -62,16 +62,17 @@
|
||||
"Danger!": "Pericolo!",
|
||||
"Debugging Facilities": "Servizi di Debug",
|
||||
"Default Folder Path": "Percorso Cartella di Default",
|
||||
"Delete Unexpected Items": "Delete Unexpected Items",
|
||||
"Delete Unexpected Items": "Elimina elementi imprevisti",
|
||||
"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.": "Deseleziona le cartelle per interromperne la condivisione con questo dispositivo.",
|
||||
"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",
|
||||
"Device Identification": "Identificazione Dispositivo",
|
||||
"Device Name": "Nome Dispositivo",
|
||||
"Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password",
|
||||
"Device is untrusted, enter encryption password": "Il dispositivo non è attendibile, inserisci la password di crittografia",
|
||||
"Device rate limits": "Limiti di velocità del dispositivo",
|
||||
"Device that last modified the item": "Dispositivo che ha modificato l'elemento per ultimo",
|
||||
"Devices": "Dispositivi",
|
||||
@@ -126,7 +127,7 @@
|
||||
"Folder Label": "Etichetta per la Cartella",
|
||||
"Folder Path": "Percorso Cartella",
|
||||
"Folder Type": "Tipo di Cartella",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Il tipo di cartella \"{{receiveEncrypted}}\" non può essere modificato dopo aver aggiunto la cartella. È necessario rimuovere la cartella, eliminare o decrittografare i dati sul disco e aggiungere nuovamente la cartella.",
|
||||
"Folders": "Cartelle",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Per le seguenti cartelle si è verificato un errore durante l'avvio della ricerca delle modifiche. Sarà ripetuto ogni minuto, quindi gli errori potrebbero risolversi presto. Se persistono, prova a risolvere il problema sottostante e chiedi aiuto se non puoi.",
|
||||
"Full Rescan Interval (s)": "Intervallo di scansione completa (s)",
|
||||
@@ -144,7 +145,7 @@
|
||||
"Help": "Aiuto",
|
||||
"Home page": "Pagina home",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Tuttavia, le impostazioni correnti indicano che potresti non volerla attiva. Abbiamo disattivato la segnalazione automatica degli arresti anomali per te.",
|
||||
"If untrusted, enter encryption password": "If untrusted, enter encryption password",
|
||||
"If untrusted, enter encryption password": "Se non attendibile, immettere la password di crittografia",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vuoi impedire ad altri utenti di questo computer di accedere a Syncthing e attraverso di esso ai tuoi file, prendi in considerazione la configurazione dell'autenticazione.",
|
||||
"Ignore": "Ignora",
|
||||
"Ignore Patterns": "Schemi Esclusione File",
|
||||
@@ -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",
|
||||
@@ -227,15 +228,15 @@
|
||||
"Preview Usage Report": "Anteprima Statistiche di Utilizzo",
|
||||
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
|
||||
"Random": "Casuale",
|
||||
"Receive Encrypted": "Receive Encrypted",
|
||||
"Receive Encrypted": "Ricevi crittografato",
|
||||
"Receive Only": "Ricevi solo",
|
||||
"Received data is already encrypted": "Received data is already encrypted",
|
||||
"Received data is already encrypted": "I dati ricevuti sono già crittografati",
|
||||
"Recent Changes": "Cambiamenti Recenti",
|
||||
"Reduced by ignore patterns": "Ridotto da schemi di esclusione",
|
||||
"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",
|
||||
@@ -259,7 +260,7 @@
|
||||
"Select All": "Seleziona Tutto",
|
||||
"Select a version": "Seleziona una versione",
|
||||
"Select additional devices to share this folder with.": "Seleziona altri dispositivi con cui condividere questa cartella.",
|
||||
"Select additional folders to share with this device.": "Select additional folders to share with this device.",
|
||||
"Select additional folders to share with this device.": "Seleziona altre cartelle da condividere con questo dispositivo.",
|
||||
"Select latest version": "Seleziona l'ultima versione",
|
||||
"Select oldest version": "Seleziona la versione più vecchia",
|
||||
"Select the folders to share with this device.": "Seleziona le cartelle da condividere con questo dispositivo.",
|
||||
@@ -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",
|
||||
@@ -293,7 +294,7 @@
|
||||
"Start Browser": "Avvia Browser",
|
||||
"Statistics": "Statistiche",
|
||||
"Stopped": "Fermato",
|
||||
"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.": "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.",
|
||||
"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.": "Memorizza e sincronizza solo i dati crittografati. Le cartelle su tutti i dispositivi collegati devono essere configurate con la stessa password o essere del tipo \"{{receiveEncrypted}}\".",
|
||||
"Support": "Supporto",
|
||||
"Support Bundle": "Pacchetto di supporto",
|
||||
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
|
||||
@@ -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.": "Vengono utilizzati i seguenti intervalli temporali: per la prima ora viene mantenuta una versione ogni 30 secondi, per il primo giorno viene mantenuta una versione ogni ora, per i primi 30 giorni viene mantenuta una versione al giorno, successivamente viene mantenuta una versione ogni settimana fino al periodo massimo impostato.",
|
||||
"The following items could not be synchronized.": "Non è stato possibile sincronizzare i seguenti elementi.",
|
||||
"The following items were changed locally.": "I seguenti elementi sono stati modificati localmente.",
|
||||
"The following unexpected items were found.": "The following unexpected items were found.",
|
||||
"The following unexpected items were found.": "Sono stati trovati i seguenti elementi imprevisti.",
|
||||
"The interval must be a positive number of seconds.": "L'intervallo deve essere un numero positivo di secondi.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "L'intervallo, in secondi, per l'esecuzione della pulizia nella directory delle versioni. Zero per disabilitare la pulizia periodica.",
|
||||
"The maximum age must be a number and cannot be blank.": "La durata massima dev'essere un numero e non può essere vuoto.",
|
||||
@@ -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.": "Non ci sono cartelle da condividere con questo dispositivo.",
|
||||
"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.",
|
||||
@@ -349,14 +351,14 @@
|
||||
"Unavailable": "Non disponibile",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Non disponibile/Disabilitato dall'amministratore o dal manutentore",
|
||||
"Undecided (will prompt)": "Non deciso (verrà richiesto)",
|
||||
"Unexpected Items": "Unexpected Items",
|
||||
"Unexpected items have been found in this folder.": "Unexpected items have been found in this folder.",
|
||||
"Unexpected Items": "Elementi imprevisti",
|
||||
"Unexpected items have been found in this folder.": "Sono stati trovati elementi imprevisti in questa cartella.",
|
||||
"Unignore": "Non ignorare",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Unshared": "Non Condiviso",
|
||||
"Unshared Devices": "Dispositivi non condivisi",
|
||||
"Unshared Folders": "Unshared Folders",
|
||||
"Untrusted": "Untrusted",
|
||||
"Unshared Folders": "Cartelle non condivise",
|
||||
"Untrusted": "Non attendibile",
|
||||
"Up to Date": "Sincronizzato",
|
||||
"Updated": "Aggiornato",
|
||||
"Upgrade": "Aggiornamento",
|
||||
@@ -392,7 +394,7 @@
|
||||
"You have no ignored folders.": "Non ci sono cartelle ignorate.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Hai modifiche non salvate. Vuoi davvero scartarle?",
|
||||
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Non si dovrebbe mai aggiungere o modificare nulla localmente in una cartella \"{{receiveEncrypted}}\".",
|
||||
"days": "giorni",
|
||||
"directories": "directory",
|
||||
"files": "file",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "elementi",
|
||||
"seconds": "secondi",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vuole condividere la cartella \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vuole condividere la cartella \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vuole condividere la cartella \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} potrebbe reintrodurre questo dispositivo."
|
||||
}
|
||||
@@ -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.": "この設定のままでは、あなたのコンピューターにある任意のファイルを、他者が簡単に盗み見たり書き換えたりすることができます。",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "項目",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} がフォルダー「{{folderlabel}}」 ({{folder}}) を共有するよう求めています。"
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} がフォルダー「{{folderlabel}}」 ({{folder}}) を共有するよう求めています。",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.": "이 설정은 해커가 손쉽게 사용자 컴퓨터의 모든 파일을 읽고 변경할 수 있도록 할 수 있습니다.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "항목",
|
||||
"seconds": "초",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 에서 폴더 \"{{folderLabel}}\" ({{folder}})를 공유하길 원합니다."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 에서 폴더 \"{{folderLabel}}\" ({{folder}})를 공유하길 원합니다.",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "įrašai",
|
||||
"seconds": "sek.",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\"",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} nori dalintis aplanku \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} nori dalintis aplanku \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "elementer",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappa \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker å dele mappa \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker å dele mappa \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "items",
|
||||
"seconds": "seconden",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil map \"{{folder}}\" delen.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wil map \"{{folderlabel}}\" ({{folder}}) delen."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wil map \"{{folderlabel}}\" ({{folder}}) delen.",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} kan dit apparaat mogelijk opnieuw introduceren."
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
"Bugs": "Błędy",
|
||||
"Changelog": "Historia zmian",
|
||||
"Clean out after": "Uporządkuj",
|
||||
"Cleaning Versions": "Cleaning Versions",
|
||||
"Cleaning Versions": "Wyczyść wersje",
|
||||
"Cleanup Interval": "Cleanup Interval",
|
||||
"Click to see discovery failures": "Kliknij aby zobaczyć błędy odnajdywania",
|
||||
"Close": "Zamknij",
|
||||
@@ -49,7 +49,7 @@
|
||||
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
|
||||
"Compression": "Kompresja",
|
||||
"Configured": "Skonfigurowane",
|
||||
"Connected (Unused)": "Connected (Unused)",
|
||||
"Connected (Unused)": "Połączone (nieużywane)",
|
||||
"Connection Error": "Błąd połączenia",
|
||||
"Connection Type": "Rodzaj połączenia",
|
||||
"Connections": "Połączenia",
|
||||
@@ -58,7 +58,7 @@
|
||||
"Copied from original": "Skopiowane z oryginału",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Wszelkie prawa zastrzeżone © 2014-2019 dla twórców:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Ustawienie wzorów ignorowania, nadpisze istniejący plik w {{path}}.",
|
||||
"Currently Shared With Devices": "Currently Shared With Devices",
|
||||
"Currently Shared With Devices": "Aktualnie udostępniane między urządzeniami",
|
||||
"Danger!": "Niebezpieczne!",
|
||||
"Debugging Facilities": "Odpluskwianie",
|
||||
"Default Folder Path": "Domyślna ścieżka folderu",
|
||||
@@ -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",
|
||||
@@ -83,7 +84,7 @@
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).",
|
||||
"Discard": "Odrzuć",
|
||||
"Disconnected": "Rozłączony",
|
||||
"Disconnected (Unused)": "Disconnected (Unused)",
|
||||
"Disconnected (Unused)": "Rozłączono (nieużywane)",
|
||||
"Discovered": "Odkryte",
|
||||
"Discovery": "Odnajdywanie",
|
||||
"Discovery Failures": "Błędy odnajdowania",
|
||||
@@ -95,8 +96,8 @@
|
||||
"Downloaded": "Pobrane",
|
||||
"Downloading": "Pobieranie",
|
||||
"Edit": "Edytuj",
|
||||
"Edit Device": "Edit Device",
|
||||
"Edit Folder": "Edit Folder",
|
||||
"Edit Device": "Edytuj urządzenie",
|
||||
"Edit Folder": "Edytuj folder",
|
||||
"Editing {%path%}.": "Edytowanie {{path}}.",
|
||||
"Enable Crash Reporting": "Włącz raportowanie błędów",
|
||||
"Enable NAT traversal": "Włącz trawersowanie NAT",
|
||||
@@ -211,7 +212,7 @@
|
||||
"Pause": "Zatrzymaj",
|
||||
"Pause All": "Zatrzymaj wszystkie",
|
||||
"Paused": "Zatrzymany",
|
||||
"Paused (Unused)": "Paused (Unused)",
|
||||
"Paused (Unused)": "Wstrzymane (nieużywane)",
|
||||
"Pending changes": "Oczekujące zmiany",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Okresowe skanowanie w podanym przedziale czasu i wyłączone obserwowanie zmian",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Okresowe skanowanie w podanym przedziale czasu i włączone obserwowanie zmian",
|
||||
@@ -222,12 +223,12 @@
|
||||
"Please wait": "Proszę czekać",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefiks wskazujący, że plik może zostać usunięty w przypadku zapobiegania usunięciu katalogu",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefiks wskazujący, że wzorzec powinien być dopasowany bez rozróżniania wielkości liter",
|
||||
"Preparing to Sync": "Preparing to Sync",
|
||||
"Preparing to Sync": "Przygotowanie do synchronizacji",
|
||||
"Preview": "Podgląd",
|
||||
"Preview Usage Report": "Podgląd raportu użycia.",
|
||||
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
|
||||
"Random": "Losowo",
|
||||
"Receive Encrypted": "Receive Encrypted",
|
||||
"Receive Encrypted": "Odbieraj zaszyfrowane",
|
||||
"Receive Only": "Tylko odbieraj",
|
||||
"Received data is already encrypted": "Received data is already encrypted",
|
||||
"Recent Changes": "Ostatnie zmiany",
|
||||
@@ -270,7 +271,7 @@
|
||||
"Share Folder": "Udostępnij folder",
|
||||
"Share Folders With Device": "Udostępnij foldery między urządzeniami",
|
||||
"Share this folder?": "Udostępnić ten folder?",
|
||||
"Shared Folders": "Shared Folders",
|
||||
"Shared Folders": "Udostępnione foldery",
|
||||
"Shared With": "Współdzielony z",
|
||||
"Sharing": "Współdzielenie",
|
||||
"Show ID": "Pokaż ID",
|
||||
@@ -308,7 +309,7 @@
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing nie może przetworzyć twojego zapytania. Proszę przeładuj stronę lub zrestartuj Syncthing, jeśli problem pozostanie.",
|
||||
"Take me back": "Zabierz mnie z powrotem",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Adres GUI jest nadpisywany przez opcje uruchamiania. Zmiany tutaj nie będą obowiązywać, dopóki ta opcja uruchamiania jest używana.",
|
||||
"The Syncthing Authors": "The Syncthing Authors",
|
||||
"The Syncthing Authors": "Autorzy Syncthing",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Interfejs administracyjny Syncthing jest skonfigurowany w sposób pozwalający na zdalny dostęp bez hasła.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Zebrane statystyki są publicznie dostępne pod poniższym linkiem.",
|
||||
"The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.",
|
||||
@@ -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.",
|
||||
@@ -355,8 +357,8 @@
|
||||
"Unknown": "Nieznany",
|
||||
"Unshared": "Nieudostępnione",
|
||||
"Unshared Devices": "Unshared Devices",
|
||||
"Unshared Folders": "Unshared Folders",
|
||||
"Untrusted": "Untrusted",
|
||||
"Unshared Folders": "Nieudostępnione foldery",
|
||||
"Untrusted": "Niezaufane",
|
||||
"Up to Date": "Aktualny",
|
||||
"Updated": "Zaktualizowano",
|
||||
"Upgrade": "Aktualizacja",
|
||||
@@ -372,8 +374,8 @@
|
||||
"Versions Path": "Ścieżka wersji",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Wersje zostają automatycznie usunięte jeżeli są starsze niż maksymalny wiek lub przekraczają liczbę dopuszczalnych wersji.",
|
||||
"Waiting to Clean": "Waiting to Clean",
|
||||
"Waiting to Scan": "Waiting to Scan",
|
||||
"Waiting to Sync": "Waiting to Sync",
|
||||
"Waiting to Scan": "Oczekiwanie na skanowanie",
|
||||
"Waiting to Sync": "Oczekiwanie na synchronizację",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka jest nadrzędnym folderem istniejącego folderu \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ten folder jest nadfolderem istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka to podkatalog istniejącego folderu \"{{otherFolder}}\".",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "pozycji",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\"",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce udostępnić folder \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce udostępnić folder \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "itens",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quer compartilhar a pasta \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quer compartilhar a pasta \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "itens",
|
||||
"seconds": "segundos",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quer partilhar a pasta \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quer partilhar a pasta \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} poderá reintroduzir este dispositivo"
|
||||
}
|
||||
@@ -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.": "Это может дать доступ хакерам для чтения и изменения любых файлов на вашем компьютере.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "элементы",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой «{{folder}}».",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хочет поделиться папкой «{{folderlabel}}» ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хочет поделиться папкой «{{folderlabel}}» ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "položiek",
|
||||
"seconds": "sekúnd",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce zdieľať adresár \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce zdieľať adresár \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce zdieľať adresár \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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,10 +294,10 @@
|
||||
"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",
|
||||
"Sync Protocol Listen Addresses": "Lyssnaradresser för synkroniseringsprotokoll",
|
||||
"Syncing": "Synkroniserar",
|
||||
"Syncthing has been shut down.": "Syncthing har stängts.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:",
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "objekt",
|
||||
"seconds": "sekunder",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela mapp \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vill dela mapp \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vill dela mapp \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} kan återinföra denna enhet."
|
||||
}
|
||||
@@ -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.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "öğe",
|
||||
"seconds": "saniye",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}}, \"{{folder}}\" klasörünü paylaşmak istiyor.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}}, \"{{folderlabel}}\" ({{folder}}) klasörünü paylaşmak istiyor."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}}, \"{{folderlabel}}\" ({{folder}}) klasörünü paylaşmak istiyor.",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} bu cihazı yeniden tanıtabilir."
|
||||
}
|
||||
@@ -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.": "Це легко може дати хакерам доступ до читання та зміни будь-яких файлів на вашому комп'ютері.",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "елементи",
|
||||
"seconds": "секунд",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хоче поділитися директорією \"{{folderLabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хоче поділитися директорією \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -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.": "外部命令接管了版本控制。该外部命令必须自行从共享文件夹中删除该文件。如果此应用程序的路径包含空格,应该用半角引号括起来。",
|
||||
"Anonymous Usage Reporting": "匿名使用报告",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new 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?": "确认要永久删除这些文件吗?",
|
||||
"Are you sure you want to remove device {%name%}?": "您确定要移除设备 {{name}} 吗?",
|
||||
"Are you sure you want to remove folder {%label%}?": "您确定要移除文件夹 {{label}} 吗?",
|
||||
"Are you sure you want to restore {%count%} files?": "您确定要恢复这 {{count}} 个文件吗?",
|
||||
@@ -66,12 +66,13 @@
|
||||
"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",
|
||||
"Device Identification": "设备标识",
|
||||
"Device Name": "设备名",
|
||||
"Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password",
|
||||
"Device is untrusted, enter encryption password": "设备不可信,请输入加密密码",
|
||||
"Device rate limits": "设备速率限制",
|
||||
"Device that last modified the item": "最近修改该项的设备",
|
||||
"Devices": "设备",
|
||||
@@ -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.": "这会让骇客能够轻而易举地访问及修改您的文件。",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "条目",
|
||||
"seconds": "秒",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将 “{{folder}}” 文件夹共享给您。",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要共享 \"{{folderlabel}}\" ({{folder}}) 文件夹给您。"
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要共享 \"{{folderlabel}}\" ({{folder}}) 文件夹给您。",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
{
|
||||
"A device with that ID is already added.": "您已添加過擁有相同 ID 的設備",
|
||||
"A negative number of days doesn't make sense.": "負數天數不符合邏輯。",
|
||||
"A new major version may not be compatible with previous versions.": "重大更新可能與之前的版本之間無法兼容",
|
||||
"API Key": "API Key",
|
||||
"About": "關於",
|
||||
"Action": "操作",
|
||||
"Actions": "操作",
|
||||
"Add": "新增",
|
||||
"Add Device": "新增設備",
|
||||
"Add Folder": "新增資料夾",
|
||||
"Add Remote Device": "新增遠程設備",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "將這個設備上那些,跟本機有著共同文件夾的「遠程設備」,都添加到本機的「遠程設備」列表。",
|
||||
"Add new folder?": "新增新文件夾?",
|
||||
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "另外,完整重新掃瞄的間隔將增大(時間 60,以新的默認 1 小時為例)。你也可以在選擇「否」後手動配置每個文件夾的時間。",
|
||||
"Address": "地址",
|
||||
"Addresses": "地址列表",
|
||||
"Advanced": "高級",
|
||||
"Advanced Configuration": "高級配置",
|
||||
"All Data": "所有資料",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.",
|
||||
"Allow Anonymous Usage Reporting?": "允許匿名使用報告?",
|
||||
"Allowed Networks": "允許的網絡",
|
||||
"Alphabetic": "字母順序",
|
||||
"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.": "外部命令接管了版本控制。該外部命令必須自行從共享文件夾中刪除該文件。如果此應用程序的路徑包含空格,應該用半角引號括起來。",
|
||||
"Anonymous Usage Reporting": "匿名使用報告",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new 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 remove device {%name%}?": "您確定要移除設備 {{name}} 嗎?",
|
||||
"Are you sure you want to remove folder {%label%}?": "您確定要移除文件夾 {{label}} 嗎?",
|
||||
"Are you sure you want to restore {%count%} files?": "您確定要恢復這 {{count}} 個文件嗎?",
|
||||
"Are you sure you want to upgrade?": "你確定要升級嗎?",
|
||||
"Auto Accept": "自動接受",
|
||||
"Automatic Crash Reporting": "自動發送崩潰報告",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自動升級現在提供了穩定版本和候選發佈版的選項。",
|
||||
"Automatic upgrades": "自動升級",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "候選發佈版會一直啟用自動升級。",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "在本機默認文件夾中,自動地創建或共享這個設備共享出來的所有文件夾。",
|
||||
"Available debug logging facilities:": "可用的調試日誌功能:",
|
||||
"Be careful!": "小心!",
|
||||
"Bugs": "問題回報",
|
||||
"Changelog": "更新日誌",
|
||||
"Clean out after": "在該時間後清除",
|
||||
"Cleaning Versions": "清除版本",
|
||||
"Cleanup Interval": "清除間隔",
|
||||
"Click to see discovery failures": "點擊查看設備發現錯誤",
|
||||
"Close": "關閉",
|
||||
"Command": "命令",
|
||||
"Comment, when used at the start of a line": "註釋,在行首使用",
|
||||
"Compression": "壓縮",
|
||||
"Configured": "已配置",
|
||||
"Connected (Unused)": "已連接(未使用)",
|
||||
"Connection Error": "連接出錯",
|
||||
"Connection Type": "連接類型",
|
||||
"Connections": "連接",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Syncthing 現在可以持續監視更改了。這將檢測磁盤上的更改,然後對有修改的路徑發起掃瞄。這樣的好處是更改可以更快地傳播,且需要的完整掃瞄會更少。",
|
||||
"Copied from elsewhere": "從其他設備複製",
|
||||
"Copied from original": "從源複製",
|
||||
"Copyright © 2014-2019 the following Contributors:": "版權所有©2014-2019以下貢獻者:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "正在創建忽略模式,覆蓋位於 {{path}} 的已有文件。",
|
||||
"Currently Shared With Devices": "當前設備已共享",
|
||||
"Danger!": "危險!",
|
||||
"Debugging Facilities": "調試功能",
|
||||
"Default Folder Path": "默認文件夾路徑",
|
||||
"Delete Unexpected Items": "刪除不需要項目",
|
||||
"Deleted": "已刪除",
|
||||
"Deselect All": "取消全選",
|
||||
"Deselect devices to stop sharing this folder with.": "反選設備以停止共享此文件夾",
|
||||
"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",
|
||||
"Device Identification": "設備標識",
|
||||
"Device Name": "設備名",
|
||||
"Device is untrusted, enter encryption password": "設備不受信任,請輸入加密密碼",
|
||||
"Device rate limits": "設備速率限制",
|
||||
"Device that last modified the item": "最近修改該項的設備",
|
||||
"Devices": "設備",
|
||||
"Disable Crash Reporting": "禁用自動發送崩潰報告",
|
||||
"Disabled": "已禁用",
|
||||
"Disabled periodic scanning and disabled watching for changes": "已禁用定期掃瞄和更改監視",
|
||||
"Disabled periodic scanning and enabled watching for changes": "已禁用定期掃瞄並啟用更改監視",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "已禁用定期掃瞄但設置更改監視失敗,正在以每 1m 一次重試:",
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "禁用比較和同步文件權限。 適用於不存在或自定義權限的系統(例如FAT,exFAT,Synology,Android)。",
|
||||
"Discard": "丟棄",
|
||||
"Disconnected": "連接已斷開",
|
||||
"Disconnected (Unused)": "斷開連接(未使用)",
|
||||
"Discovered": "已發現",
|
||||
"Discovery": "設備發現",
|
||||
"Discovery Failures": "設備發現錯誤",
|
||||
"Do not restore": "不要恢復",
|
||||
"Do not restore all": "不要全部恢復",
|
||||
"Do you want to enable watching for changes for all your folders?": "您想要啟用對監視您所有文件夾的更改嗎?",
|
||||
"Documentation": "文檔",
|
||||
"Download Rate": "下載速度",
|
||||
"Downloaded": "已下載",
|
||||
"Downloading": "下載中",
|
||||
"Edit": "選項",
|
||||
"Edit Device": "編輯設備",
|
||||
"Edit Folder": "編輯文件夾",
|
||||
"Editing {%path%}.": "正在編輯 {{path}}。",
|
||||
"Enable Crash Reporting": "啟用自動發送崩潰報告",
|
||||
"Enable NAT traversal": "啟用 NAT 遍歷",
|
||||
"Enable Relaying": "開啟中繼",
|
||||
"Enabled": "已啟用",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "輸入一個非負數(例如「2.35」)並選擇單位。%表示占磁盤總容量的百分比。",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "輸入一個非特權的端口號 (1024 - 65535)。",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "輸入以半角逗號分隔的(\"tcp://ip:port\", \"tcp://host:port\")設備地址列表,或者輸入「dynamic」以自動發現設備地址。",
|
||||
"Enter ignore patterns, one per line.": "請輸入忽略模式,每行一條。",
|
||||
"Enter up to three octal digits.": "最多輸入三個8進制數字",
|
||||
"Error": "錯誤",
|
||||
"External File Versioning": "外部版本控制",
|
||||
"Failed Items": "失敗的項目",
|
||||
"Failed to setup, retrying": "設置失敗,正在重試。",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "如果本機沒有配置IPv6,則無法連接IPv6服務器是正常的。",
|
||||
"File Pull Order": "文件拉取順序",
|
||||
"File Versioning": "版本控制",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "當文件被 Syncthing 替換或刪除時,將被移動到 .stversions 目錄。",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "當某個文件在其他設備被替換或刪除時,本設備將在 .stversions 目錄中保留該文件的備份,並在文件名中加入時間戳信息。",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "在其它設備中對該文件夾內文件的修改並不會被同步到本機,但是在本機上對其的修改,則會被同步到集群中的其它設備。",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "文件將從集群同步,但本地所作的任何更改都不會被發送到其他設備。",
|
||||
"Filesystem Watcher Errors": "文件系統監視器錯誤",
|
||||
"Filter by date": "按日期篩選",
|
||||
"Filter by name": "按名稱篩選",
|
||||
"Folder": "文件夾",
|
||||
"Folder ID": "文件夾 ID",
|
||||
"Folder Label": "文件夾標籤",
|
||||
"Folder Path": "文件夾路徑",
|
||||
"Folder Type": "文件夾類型",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.",
|
||||
"Folders": "文件夾",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "開始監視下列文件夾時發生錯誤。由於每分鐘都會重試,所以錯誤可能很快就消失。如果它們仍存在,請試著修復潛在問題,如不會則請求幫助。",
|
||||
"Full Rescan Interval (s)": "完整掃瞄間隔",
|
||||
"GUI": "圖形用戶界面",
|
||||
"GUI Authentication Password": "圖形管理界面密碼",
|
||||
"GUI Authentication User": "圖形管理界面用戶名",
|
||||
"GUI Authentication: Set User and Password": "GUI身份驗證:設置用戶和密碼",
|
||||
"GUI Listen Address": "GUI 監聽地址",
|
||||
"GUI Theme": "GUI 主題",
|
||||
"General": "常規",
|
||||
"Generate": "生成",
|
||||
"Global Discovery": "全球發現",
|
||||
"Global Discovery Servers": "全球發現服務器",
|
||||
"Global State": "全局狀態",
|
||||
"Help": "幫助",
|
||||
"Home page": "主頁",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "我們已經為您關閉了自動崩潰報告發送功能,因為您當前的設置顯示您可能並不想啟用該功能。",
|
||||
"If untrusted, enter encryption password": "如果不受信任,請輸入加密密碼",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "如果要阻止此計算機上的其他用戶訪問Syncthing並通過它訪問文件,請考慮設置身份驗證。",
|
||||
"Ignore": "忽略",
|
||||
"Ignore Patterns": "忽略模式",
|
||||
"Ignore Permissions": "忽略文件權限",
|
||||
"Ignored Devices": "已忽略的設備",
|
||||
"Ignored Folders": "已忽略的文件夾",
|
||||
"Ignored at": "已忽略於",
|
||||
"Incoming Rate Limit (KiB/s)": "下載速率限制 (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "錯誤的配置可能損壞您文件夾內的內容,使得 Syncthing 無法工作。",
|
||||
"Introduced By": "介紹自",
|
||||
"Introducer": "作為中介",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "對本條件取反(例如:不要排除某項)",
|
||||
"Keep Versions": "保留版本數量",
|
||||
"LDAP": "LDAP",
|
||||
"Largest First": "大文件優先",
|
||||
"Last Scan": "最後掃瞄",
|
||||
"Last seen": "最後可見",
|
||||
"Latest Change": "最後更改",
|
||||
"Learn more": "瞭解更多",
|
||||
"Limit": "限制",
|
||||
"Listeners": "偵聽程序",
|
||||
"Loading data...": "正在載入數據…",
|
||||
"Loading...": "正在載入…",
|
||||
"Local Additions": "從本地添加",
|
||||
"Local Discovery": "本地發現",
|
||||
"Local State": "本地狀態",
|
||||
"Local State (Total)": "本地狀態匯總",
|
||||
"Locally Changed Items": "本地更改的項目",
|
||||
"Log": "日誌",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "已暫停日誌跟蹤。滾動到底部以繼續。",
|
||||
"Logs": "日誌",
|
||||
"Major Upgrade": "重大更新",
|
||||
"Mass actions": "批量操作",
|
||||
"Maximum Age": "最長保留時間",
|
||||
"Metadata Only": "僅元數據",
|
||||
"Minimum Free Disk Space": "最低可用磁盤空間",
|
||||
"Mod. Device": "修改設備",
|
||||
"Mod. Time": "修改時間",
|
||||
"Move to top of queue": "移動到隊列頂端",
|
||||
"Multi level wildcard (matches multiple directory levels)": "多級通配符(用以匹配多層文件夾)",
|
||||
"Never": "從未",
|
||||
"New Device": "新設備",
|
||||
"New Folder": "新文件夾",
|
||||
"Newest First": "新文件優先",
|
||||
"No": "否",
|
||||
"No File Versioning": "不啟用版本控制",
|
||||
"No files will be deleted as a result of this operation.": "此操作結果不會刪除任何文件。",
|
||||
"No upgrades": "無更新",
|
||||
"Not shared": "未分享",
|
||||
"Notice": "提示",
|
||||
"OK": "確定",
|
||||
"Off": "關閉",
|
||||
"Oldest First": "舊文件優先",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "可選的文件夾說明性標籤。在不同設備上可以不一致。",
|
||||
"Options": "選項",
|
||||
"Out of Sync": "未同步",
|
||||
"Out of Sync Items": "未同步的項目",
|
||||
"Outgoing Rate Limit (KiB/s)": "上傳速度限制 (KiB/s)",
|
||||
"Override Changes": "撤銷改變",
|
||||
"Path": "路徑",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "文件夾在本地的路徑。如果不存在,則會被創建。波浪線符號(~)是如下路徑的縮略符:",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "用於創建自動接受新文件夾的路徑,同時也是通過 UI 添加新文件夾時的默認值。波浪號 (~) 擴展成 {{tilde}}。",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "歷史版本儲存路徑(留空則會默認存儲在共享文件夾中的 .stversions 目錄)。",
|
||||
"Pause": "暫停",
|
||||
"Pause All": "全部暫停",
|
||||
"Paused": "已暫停",
|
||||
"Paused (Unused)": "已暫停(未使用)",
|
||||
"Pending changes": "待定的更改",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "正以給定的間隔定期掃瞄並已禁用更改監視",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "正以給定的間隔定期掃瞄並已啟用更改監視",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "正以給定的間隔定期掃瞄但設置更改監視失敗,正在以每 1m 一次重試:",
|
||||
"Permissions": "權限",
|
||||
"Please consult the release notes before performing a major upgrade.": "請在進行重大更新前查看發佈說明。",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "請在設置對話框中設置 GUI 驗證用戶及其密碼。",
|
||||
"Please wait": "請稍候",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "表示如果刪除了阻止目錄則文件可被刪除的前綴",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "此前綴表示,後面的模式在匹配時不區分大小寫",
|
||||
"Preparing to Sync": "準備同步",
|
||||
"Preview": "預覽",
|
||||
"Preview Usage Report": "預覽使用報告",
|
||||
"Quick guide to supported patterns": "支持的通配符的簡單教程:",
|
||||
"Random": "隨機順序",
|
||||
"Receive Encrypted": "Receive Encrypted",
|
||||
"Receive Only": "僅接收",
|
||||
"Received data is already encrypted": "Received data is already encrypted",
|
||||
"Recent Changes": "最近更改",
|
||||
"Reduced by ignore patterns": "已由忽略模式縮減",
|
||||
"Release Notes": "發佈說明",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "發佈候選版包含最新的特性和修復。它們跟傳統的 Syncthing 雙周發佈版類似。",
|
||||
"Remote Devices": "遠程設備",
|
||||
"Remote GUI": "Remote GUI",
|
||||
"Remove": "移除",
|
||||
"Remove Device": "移除設備",
|
||||
"Remove Folder": "移除文件夾",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "必需的文件夾唯一標識。同一個文件夾在集群中的所有設備上ID必須相同。",
|
||||
"Rescan": "重新掃瞄",
|
||||
"Rescan All": "全部重新掃瞄",
|
||||
"Rescans": "重新掃瞄",
|
||||
"Restart": "重啟 Syncthing",
|
||||
"Restart Needed": "需要重啟 Syncthing",
|
||||
"Restarting": "重啟中",
|
||||
"Restore": "恢復",
|
||||
"Restore Versions": "恢復歷史版本",
|
||||
"Resume": "恢復",
|
||||
"Resume All": "全部恢復",
|
||||
"Reused": "復用",
|
||||
"Revert Local Changes": "恢復本地更改",
|
||||
"Save": "保存",
|
||||
"Scan Time Remaining": "掃瞄剩餘時間",
|
||||
"Scanning": "掃瞄中",
|
||||
"See external versioning help for supported templated command line parameters.": "有關受支持的模板命令行參數,請參閱外部版本控制幫助。",
|
||||
"Select All": "全選",
|
||||
"Select a version": "選擇版本",
|
||||
"Select additional devices to share this folder with.": "選擇其他共享此文件夾的設備",
|
||||
"Select additional folders to share with this device.": "選擇其他文件夾與此設備共享。",
|
||||
"Select latest version": "選擇最新的版本",
|
||||
"Select oldest version": "選擇最舊的版本",
|
||||
"Select the folders to share with this device.": "選擇與該設備共享的文件夾。",
|
||||
"Send & Receive": "發送與接收",
|
||||
"Send Only": "僅發送",
|
||||
"Settings": "設置",
|
||||
"Share": "共享",
|
||||
"Share Folder": "共享文件夾",
|
||||
"Share Folders With Device": "將指定文件夾共享給設備",
|
||||
"Share this folder?": "是否共享該文件夾?",
|
||||
"Shared Folders": "共享文件夾",
|
||||
"Shared With": "共享給",
|
||||
"Sharing": "共享",
|
||||
"Show ID": "顯示 ID",
|
||||
"Show QR": "顯示 QR 碼",
|
||||
"Show diff with previous version": "顯示與先前版本的差異",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "在集群狀態中顯示該名稱,而不是設備 ID。將會作為當前設備的可選的默認名稱,報告給所有其他設備。",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "在集群狀態中顯示該名稱,而不是設備 ID。如果設置為空,則會使用目標設備自報的默認名稱。",
|
||||
"Shutdown": "關閉 Syncthing",
|
||||
"Shutdown Complete": "關閉完成",
|
||||
"Simple File Versioning": "簡易版本控制",
|
||||
"Single level wildcard (matches within a directory only)": "單級通配符(僅匹配單層文件夾)",
|
||||
"Size": "大小",
|
||||
"Smallest First": "小文件優先",
|
||||
"Some items could not be restored:": "有些項目無法被恢復:",
|
||||
"Source Code": "源代碼",
|
||||
"Stable releases and release candidates": "穩定版本和發佈候選版",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "穩定版本約延遲兩個星期。這段時間它們將作為發佈候選版來測試。",
|
||||
"Stable releases only": "僅穩定版本",
|
||||
"Staggered File Versioning": "階段版本控制",
|
||||
"Start Browser": "啟動瀏覽器",
|
||||
"Statistics": "統計",
|
||||
"Stopped": "已停止",
|
||||
"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.": "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.",
|
||||
"Support": "支持",
|
||||
"Support Bundle": "支持捆綁包",
|
||||
"Sync Protocol Listen Addresses": "協議監聽地址",
|
||||
"Syncing": "同步中",
|
||||
"Syncthing has been shut down.": "Syncthing 已關閉。",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing 使用了下列軟件或其中的一部分:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing 是個以 MPL v2.0 授權的免費開源軟件。",
|
||||
"Syncthing is restarting.": "Syncthing 正在重啟。",
|
||||
"Syncthing is upgrading.": "Syncthing 正在升級。",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing 現在已經支持將崩潰報告自動發送給開發者。該功能默認開啟。",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing 似乎關閉了,或者您的網絡連接存在故障。重試中…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing 在處理您的請求時似乎遇到了問題。如果問題持續,請刷新頁面,或重啟 Syncthing。",
|
||||
"Take me back": "帶我回去",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "GUI 地址已被啟動選項覆蓋。當覆蓋存在時,此處的更改就不會生效。",
|
||||
"The Syncthing Authors": "Syncthing的作者",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "當前配置允許在不使用密碼的情況下遠程訪問 Syncthing 管理界面。",
|
||||
"The aggregated statistics are publicly available at the URL below.": "全局統計數據公佈於以下 URL。",
|
||||
"The cleanup interval cannot be blank.": "清理間隔不能為空。",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "設置已經保存,但是還未生效。Syncthing 需要重啟以啟用新的設置。",
|
||||
"The device ID cannot be blank.": "設備 ID 不能為空。",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "在這裡所需要輸入的設備 ID,可以在目標設備的「操作->顯示 ID」中看到。空格和橫線可選(將會被忽略)。",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "經過加密的使用報告會每天發送。它用來跟蹤統計使用本軟件的平台,文件夾大小,以及本軟件的版本。如果報告的內容有任何變化,本對話框會再次彈出提示您。",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "輸入的設備 ID 似乎無效。設備 ID 包含字母和數字,長度為 52 或 56,空格和橫線不計在內。",
|
||||
"The folder ID cannot be blank.": "文件夾 ID 不能為空。",
|
||||
"The folder ID must be unique.": "文件夾 ID 不得重複。",
|
||||
"The folder path cannot be blank.": "文件夾路徑不能為空。",
|
||||
"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.": "保留的歷史版本會遵循以下條件:最近一小時內的歷史版本,更新間隔小於三十秒的僅保留一份。最近一天內的歷史版本,更新間隔小於一小時的僅保留一份。最近一個月內的歷史版本,更新間隔小於一天的僅保留一份。距離現在超過一個月且小於最長保留時間的,更新間隔小於一周的僅保留一份。",
|
||||
"The following items could not be synchronized.": "下列項目無法被同步。",
|
||||
"The following items were changed locally.": "下列項目存在本地更改。",
|
||||
"The following unexpected items were found.": "找到以下不需要項目。",
|
||||
"The interval must be a positive number of seconds.": "間隔必須為正數秒。",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "在版本目錄中運行清理的間隔(秒)。0表示禁用定期清除。",
|
||||
"The maximum age must be a number and cannot be blank.": "最長保留時間必須為數字,且不能為空。",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "歷史版本保留的最長天數,0 為永久保存。",
|
||||
"The number of days must be a number and cannot be blank.": "天數必須為數字,且不能為空。",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "文件保存在回收站的天數。零表示永久。",
|
||||
"The number of old versions to keep, per file.": "每個文件保留的版本數量上限。",
|
||||
"The number of versions must be a number and cannot be blank.": "保留版本數量必須為數字,且不能為空。",
|
||||
"The path cannot be blank.": "路徑不能為空。",
|
||||
"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.": "沒有與此設備共享的文件夾。",
|
||||
"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.": "這會讓駭客能夠輕而易舉地訪問及修改您的文件。",
|
||||
"This is a major version upgrade.": "這是一個重大版本更新。",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "此設置控制主(例如索引數據庫)磁盤上需要的可用空間。",
|
||||
"Time": "時間",
|
||||
"Time the item was last modified": "該項最近修改的時間",
|
||||
"Trash Can File Versioning": "回收站式版本控制",
|
||||
"Type": "類型",
|
||||
"UNIX Permissions": "UNIX權限",
|
||||
"Unavailable": "無效",
|
||||
"Unavailable/Disabled by administrator or maintainer": "無效/禁用(由管理員或維護者)",
|
||||
"Undecided (will prompt)": "待定(將提示)",
|
||||
"Unexpected Items": "Unexpected Items",
|
||||
"Unexpected items have been found in this folder.": "在此文件夾中發現了不需要的項目。",
|
||||
"Unignore": "解除忽略",
|
||||
"Unknown": "未知",
|
||||
"Unshared": "未共享",
|
||||
"Unshared Devices": "未共享設備",
|
||||
"Unshared Folders": "未共享的文件夾",
|
||||
"Untrusted": "不信任",
|
||||
"Up to Date": "同步完成",
|
||||
"Updated": "已更新",
|
||||
"Upgrade": "更新",
|
||||
"Upgrade To {%version%}": "升級至版本 {{version}}",
|
||||
"Upgrading": "升級中",
|
||||
"Upload Rate": "上傳速度",
|
||||
"Uptime": "已啟動",
|
||||
"Usage reporting is always enabled for candidate releases.": "發佈候選版總是會啟用使用報告。",
|
||||
"Use HTTPS for GUI": "使用加密連接到圖形管理頁面",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "尚未為GUI身份驗證設置用戶名/密碼。 請考慮進行設置。",
|
||||
"Version": "版本",
|
||||
"Versions": "歷史版本",
|
||||
"Versions Path": "歷史版本路徑",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "超過最長保留時間,或者不滿足下列條件的歷史版本,將會被刪除。",
|
||||
"Waiting to Clean": "等待清除",
|
||||
"Waiting to Scan": "等待掃瞄",
|
||||
"Waiting to Sync": "等待同步",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "警告,該路徑是已有文件夾\"{{otherFolder}}\"的上級目錄。",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "警告,該路徑是已有文件夾\"{{otherFolderLabel}}\" ({{otherFolder}})的上級目錄。",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "警告,該路徑是已有文件夾\"{{otherFolder}}\"的下級目錄。",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "警告,該路徑是已有文件夾\"{{otherFolderLabel}}\" ({{otherFolder}})的下級目錄。",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "警告:如果你在使用外部的監視器如 {{syncthingInotify}},你應該確保它已經取消激活。",
|
||||
"Watch for Changes": "監視更改",
|
||||
"Watching for Changes": "正在監視更改",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "對更改的監視無需定期掃瞄就可以發現大多數更改。",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "若您在本機添加新設備,記住您也必須在這個新設備上添加本機。",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "若你添加了新文件夾,記住文件夾 ID 是用以在不同設備間建立聯繫的。在不同設備間擁有相同 ID 的文件夾將會被同步。且文件夾 ID 區分大小寫。",
|
||||
"Yes": "是",
|
||||
"You can also select one of these nearby devices:": "您也可以從這些附近的設備中選擇:",
|
||||
"You can change your choice at any time in the Settings dialog.": "您可以在任何時候在設置對話框中更改選擇。",
|
||||
"You can read more about the two release channels at the link below.": "您可以從以下鏈接讀取更多關於兩個發行渠道的信息。",
|
||||
"You have no ignored devices.": "你沒有已忽略的設備。",
|
||||
"You have no ignored folders.": "你沒有已忽略的文件夾。",
|
||||
"You have unsaved changes. Do you really want to discard them?": "你有未保存的更改。你真的要丟棄它們嗎?",
|
||||
"You must keep at least one version.": "您必須保留至少一個版本。",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "您絕對不應在\"{{receiveEncrypted}}\"文件夾中本地添加或更改任何內容。",
|
||||
"days": "天",
|
||||
"directories": "目錄",
|
||||
"files": "文件",
|
||||
"full documentation": "完整文檔",
|
||||
"items": "條目",
|
||||
"seconds": "秒",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想將 「{{folder}}」 文件夾共享給您。",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要共享 \"{{folderlabel}}\" ({{folder}}) 文件夾給您。",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} 可能會重新引入此設備。"
|
||||
}
|
||||
@@ -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.": "這能給駭客輕易的來讀取、變更電腦中的任何檔案。",
|
||||
@@ -400,5 +402,6 @@
|
||||
"items": "個項目",
|
||||
"seconds": "seconds",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\"。",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要分享資料夾 \"{{folderlabel}}\" ({{folder}})。"
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要分享資料夾 \"{{folderlabel}}\" ({{folder}})。",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-AU":"English (Australia)","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-AU":"English (Australia)","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-HK":"Chinese (Hong Kong)","zh-TW":"Chinese (Taiwan)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-AU","en-GB","eo","es","es-ES","eu","fi","fr","fy","hu","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","zh-CN","zh-TW"]
|
||||
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-AU","en-GB","eo","es","es-ES","eu","fi","fr","fy","hu","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","zh-CN","zh-HK","zh-TW"]
|
||||
|
||||
+19
-19
@@ -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> <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> <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> <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> <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> <span translate>Ignore</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -554,7 +554,7 @@
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
|
||||
<span class="fas fa-refresh"></span> <span translate>Rescan</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editFolder(folder)">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editFolderExisting(folder)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
@@ -570,7 +570,7 @@
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="setAllFoldersPause(false)" ng-if="isAtleastOneFolderPausedStateSetTo(true)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume All</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanAllFolders()">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanAllFolders()" ng-disabled="!isAtleastOneFolderPausedStateSetTo(false)">
|
||||
<span class="fas fa-refresh"></span> <span translate>Rescan All</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="addFolder()">
|
||||
@@ -821,7 +821,7 @@
|
||||
<button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editDeviceExisting(deviceCfg)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@@ -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 © 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, Choongkyu, 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, Quentin Hibon, 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 />
|
||||
|
||||
@@ -60,12 +60,14 @@ angular.module('syncthing.core')
|
||||
DEVICE_CONNECTED: 'DeviceConnected', // Generated each time a connection to a device has been established
|
||||
DEVICE_DISCONNECTED: 'DeviceDisconnected', // Generated each time a connection to a device has been terminated
|
||||
DEVICE_DISCOVERED: 'DeviceDiscovered', // Emitted when a new device is discovered using local discovery
|
||||
DEVICE_REJECTED: 'DeviceRejected', // Emitted when there is a connection from a device we are not configured to talk to
|
||||
DEVICE_REJECTED: 'DeviceRejected', // DEPRECATED: Emitted when there is a connection from a device we are not configured to talk to
|
||||
PENDING_DEVICES_CHANGED: 'PendingDevicesChanged', // Emitted when pending devices were added / updated (connection from unknown ID) or removed (device is ignored or added)
|
||||
DEVICE_PAUSED: 'DevicePaused', // Emitted when a device has been paused
|
||||
DEVICE_RESUMED: 'DeviceResumed', // Emitted when a device has been resumed
|
||||
DOWNLOAD_PROGRESS: 'DownloadProgress', // Emitted during file downloads for each folder for each file
|
||||
FOLDER_COMPLETION: 'FolderCompletion', //Emitted when the local or remote contents for a folder changes
|
||||
FOLDER_REJECTED: 'FolderRejected', // Emitted when a device sends index information for a folder we do not have, or have but do not share with the device in question
|
||||
FOLDER_REJECTED: 'FolderRejected', // DEPRECATED: Emitted when a device sends index information for a folder we do not have, or have but do not share with the device in question
|
||||
PENDING_FOLDERS_CHANGED: 'PendingFoldersChanged', // Emitted when pending folders were added / updated (offered by some device, but not shared to them) or removed (folder ignored or added or no longer offered from the remote device)
|
||||
FOLDER_SUMMARY: 'FolderSummary', // Emitted when folder contents have changed locally
|
||||
ITEM_FINISHED: 'ItemFinished', // Generated when Syncthing ends synchronizing a file to a newer version
|
||||
ITEM_STARTED: 'ItemStarted', // Generated when Syncthing begins synchronizing a file to a newer version
|
||||
|
||||
@@ -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 = {}
|
||||
@@ -50,6 +52,7 @@ angular.module('syncthing.core')
|
||||
$scope.metricRates = false;
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.currentFolder = {};
|
||||
$scope.currentDevice = {};
|
||||
$scope.ignores = {
|
||||
text: '',
|
||||
error: null,
|
||||
@@ -61,17 +64,8 @@ angular.module('syncthing.core')
|
||||
$scope.metricRates = (window.localStorage["metricRates"] == "true");
|
||||
} catch (exception) { }
|
||||
|
||||
$scope.folderDefaults = {
|
||||
devices: [],
|
||||
type: "sendreceive",
|
||||
rescanIntervalS: 3600,
|
||||
fsWatcherDelayS: 10,
|
||||
fsWatcherEnabled: true,
|
||||
minDiskFree: { value: 1, unit: "%" },
|
||||
maxConflicts: 10,
|
||||
fsync: true,
|
||||
order: "random",
|
||||
fileVersioningSelector: "none",
|
||||
$scope.versioningDefaults = {
|
||||
selector: "none",
|
||||
trashcanClean: 0,
|
||||
versioningCleanupIntervalS: 3600,
|
||||
simpleKeep: 5,
|
||||
@@ -79,8 +73,6 @@ angular.module('syncthing.core')
|
||||
staggeredCleanInterval: 3600,
|
||||
staggeredVersionsPath: "",
|
||||
externalCommand: "",
|
||||
autoNormalize: true,
|
||||
path: "",
|
||||
};
|
||||
|
||||
$scope.localStateTotal = {
|
||||
@@ -120,6 +112,7 @@ angular.module('syncthing.core')
|
||||
refreshSystem();
|
||||
refreshDiscoveryCache();
|
||||
refreshConfig();
|
||||
refreshCluster();
|
||||
refreshConnectionStats();
|
||||
refreshDeviceStats();
|
||||
refreshFolderStats();
|
||||
@@ -242,6 +235,71 @@ angular.module('syncthing.core')
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on(Events.PENDING_DEVICES_CHANGED, function (event, arg) {
|
||||
if (!(arg.data.added || arg.data.removed)) {
|
||||
// Not enough information to update in place, just refresh it completely
|
||||
refreshCluster();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.data.added) {
|
||||
arg.data.added.forEach(function (rejected) {
|
||||
var pendingDevice = {
|
||||
time: arg.time,
|
||||
name: rejected.name,
|
||||
address: rejected.address
|
||||
};
|
||||
console.log("rejected device:", rejected.deviceID, pendingDevice);
|
||||
$scope.pendingDevices[rejected.deviceID] = pendingDevice;
|
||||
});
|
||||
}
|
||||
|
||||
if (arg.data.removed) {
|
||||
arg.data.removed.forEach(function (dev) {
|
||||
console.log("no longer pending device:", dev.deviceID);
|
||||
delete $scope.pendingDevices[dev.deviceID];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on(Events.PENDING_FOLDERS_CHANGED, function (event, arg) {
|
||||
if (!(arg.data.added || arg.data.removed)) {
|
||||
// Not enough information to update in place, just refresh it completely
|
||||
refreshCluster();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.data.added) {
|
||||
arg.data.added.forEach(function (rejected) {
|
||||
var offeringDevice = {
|
||||
time: arg.time,
|
||||
label: rejected.folderLabel
|
||||
};
|
||||
console.log("rejected folder", rejected.folderID, "from device:", rejected.deviceID, offeringDevice);
|
||||
|
||||
var pendingFolder = $scope.pendingFolders[rejected.folderID];
|
||||
if (pendingFolder === undefined) {
|
||||
pendingFolder = {
|
||||
offeredBy: {}
|
||||
};
|
||||
}
|
||||
pendingFolder.offeredBy[rejected.deviceID] = offeringDevice;
|
||||
$scope.pendingFolders[rejected.folderID] = pendingFolder;
|
||||
});
|
||||
}
|
||||
|
||||
if (arg.data.removed) {
|
||||
arg.data.removed.forEach(function (folderDev) {
|
||||
console.log("no longer pending folder", folderDev.folderID, "from device:", folderDev.deviceID);
|
||||
if (folderDev.deviceID === undefined) {
|
||||
delete $scope.pendingFolders[folderDev.folderID];
|
||||
} else if ($scope.pendingFolders[folderDev.folderID]) {
|
||||
delete $scope.pendingFolders[folderDev.folderID].offeredBy[folderDev.deviceID];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('ConfigLoaded', function () {
|
||||
if ($scope.config.options.urAccepted === 0) {
|
||||
// If usage reporting has been neither accepted nor declined,
|
||||
@@ -455,6 +513,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) {
|
||||
@@ -649,7 +717,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
function shouldSetDefaultFolderPath() {
|
||||
return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
|
||||
return $scope.config.defaults.folder.path && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine && !$scope.editingDefaults;
|
||||
}
|
||||
|
||||
function resetRemoteNeed() {
|
||||
@@ -691,8 +759,23 @@ angular.module('syncthing.core')
|
||||
$scope.currentSharing.shared = [];
|
||||
$scope.currentSharing.unrelated = [];
|
||||
$scope.currentSharing.selected = {};
|
||||
if (editing === 'folder') {
|
||||
initShareEditingFolder();
|
||||
}
|
||||
};
|
||||
|
||||
function initShareEditingFolder() {
|
||||
$scope.currentFolder.devices.forEach(function (n) {
|
||||
if (n.deviceID !== $scope.myID) {
|
||||
$scope.currentSharing.shared.push($scope.devices[n.deviceID]);
|
||||
}
|
||||
$scope.currentSharing.selected[n.deviceID] = true;
|
||||
});
|
||||
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
|
||||
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
|
||||
});
|
||||
}
|
||||
|
||||
$scope.refreshFailed = function (page, perpage) {
|
||||
if (!$scope.failed || !$scope.failed.folder) {
|
||||
return;
|
||||
@@ -1012,7 +1095,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 +1110,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 +1173,7 @@ angular.module('syncthing.core')
|
||||
if (matches.length !== 1) {
|
||||
return shortID;
|
||||
}
|
||||
return matches[0].name;
|
||||
return $scope.friendlyNameFromID(matches[0]);
|
||||
};
|
||||
|
||||
$scope.friendlyNameFromID = function (deviceID) {
|
||||
@@ -1161,6 +1240,7 @@ angular.module('syncthing.core')
|
||||
}).error($scope.emitHTTPError);
|
||||
},
|
||||
show: function () {
|
||||
$scope.logging.paused = false;
|
||||
$scope.logging.refreshFacilities();
|
||||
$scope.logging.timer = $timeout($scope.logging.fetch);
|
||||
var textArea = $('#logViewerText');
|
||||
@@ -1418,9 +1498,40 @@ angular.module('syncthing.core')
|
||||
$scope.configInSync = true;
|
||||
};
|
||||
|
||||
$scope.editDevice = function (deviceCfg) {
|
||||
function editDeviceModal() {
|
||||
$scope.currentDevice._addressesStr = $scope.currentDevice.addresses.join(', ');
|
||||
$scope.deviceEditor.$setPristine();
|
||||
$('#editDevice').modal();
|
||||
}
|
||||
|
||||
$scope.editDeviceModalTitle = function() {
|
||||
if ($scope.editingDefaults) {
|
||||
return $translate.instant("Edit Device Defaults");
|
||||
}
|
||||
var title = '';
|
||||
if ($scope.editingExisting) {
|
||||
title += $translate.instant("Edit Device");
|
||||
} else {
|
||||
title += $translate.instant("Add Device");
|
||||
}
|
||||
var name = $scope.deviceName($scope.currentDevice);
|
||||
if (name !== '') {
|
||||
title += ' (' + name + ')';
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
$scope.editDeviceModalIcon = function() {
|
||||
if ($scope.editingDefaults || $scope.editingExisting) {
|
||||
return 'fas fa-pencil-alt';
|
||||
}
|
||||
return 'fas fa-desktop';
|
||||
};
|
||||
|
||||
$scope.editDeviceExisting = function (deviceCfg) {
|
||||
$scope.currentDevice = $.extend({}, deviceCfg);
|
||||
$scope.editingExisting = true;
|
||||
$scope.editingDefaults = false;
|
||||
$scope.willBeReintroducedBy = undefined;
|
||||
if (deviceCfg.introducedBy) {
|
||||
var introducerDevice = $scope.devices[deviceCfg.introducedBy];
|
||||
@@ -1428,34 +1539,36 @@ angular.module('syncthing.core')
|
||||
$scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
|
||||
}
|
||||
}
|
||||
$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.deviceEditor.$setPristine();
|
||||
$('#editDevice').modal();
|
||||
$scope.currentSharing.unrelated = $scope.folderList().filter(function (n) {
|
||||
return !$scope.currentSharing.selected[n.id];
|
||||
});
|
||||
editDeviceModal();
|
||||
};
|
||||
|
||||
$scope.selectAllFolders = function (state) {
|
||||
var folders = $scope.folders;
|
||||
for (var id in folders) {
|
||||
$scope.currentSharing.selected[id] = !!state;
|
||||
};
|
||||
$scope.editDeviceDefaults = function () {
|
||||
$http.get(urlbase + '/config/defaults/device').then(function (p) {
|
||||
$scope.currentDevice = p.data;
|
||||
$scope.editingDefaults = true;
|
||||
editDeviceModal();
|
||||
}, $scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1474,19 +1587,16 @@ angular.module('syncthing.core')
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
$scope.currentDevice = {
|
||||
name: name,
|
||||
deviceID: deviceID,
|
||||
_addressesStr: 'dynamic',
|
||||
compression: 'metadata',
|
||||
introducer: false,
|
||||
pendingFolders: [],
|
||||
ignoredFolders: []
|
||||
};
|
||||
$scope.editingExisting = false;
|
||||
initShareEditing('device');
|
||||
$scope.deviceEditor.$setPristine();
|
||||
$('#editDevice').modal();
|
||||
$http.get(urlbase + '/config/defaults/device').then(function (p) {
|
||||
$scope.currentDevice = p.data;
|
||||
$scope.currentDevice.name = name;
|
||||
$scope.currentDevice.deviceID = deviceID;
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = false;
|
||||
initShareEditing('device');
|
||||
$scope.currentSharing.unrelated = $scope.folderList();
|
||||
editDeviceModal();
|
||||
}, $scope.emitHTTPError);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1511,47 +1621,58 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.saveDevice = function () {
|
||||
$('#editDevice').modal('hide');
|
||||
$scope.saveDeviceConfig($scope.currentDevice);
|
||||
};
|
||||
|
||||
$scope.saveDeviceConfig = function (deviceCfg) {
|
||||
deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
|
||||
$scope.currentDevice.addresses = $scope.currentDevice._addressesStr.split(',').map(function (x) {
|
||||
return x.trim();
|
||||
});
|
||||
delete $scope.currentDevice._addressesStr;
|
||||
if ($scope.editingDefaults) {
|
||||
$scope.config.defaults.device = $scope.currentDevice;
|
||||
} else {
|
||||
setDeviceConfig();
|
||||
}
|
||||
delete $scope.currentSharing;
|
||||
delete $scope.currentDevice;
|
||||
$scope.saveConfig();
|
||||
};
|
||||
|
||||
$scope.devices[deviceCfg.deviceID] = deviceCfg;
|
||||
function setDeviceConfig() {
|
||||
var currentID = $scope.currentDevice.deviceID;
|
||||
$scope.devices[currentID] = $scope.currentDevice;
|
||||
$scope.config.devices = deviceList($scope.devices);
|
||||
|
||||
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) {
|
||||
if ($scope.folders[id].devices[i].deviceID === currentID) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Add device to folder
|
||||
$scope.folders[id].devices.push({
|
||||
deviceID: deviceCfg.deviceID
|
||||
deviceID: currentID,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Remove device from folder
|
||||
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
|
||||
return n.deviceID !== deviceCfg.deviceID;
|
||||
return n.deviceID !== currentID;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.saveConfig();
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
};
|
||||
|
||||
$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();
|
||||
};
|
||||
|
||||
@@ -1680,14 +1801,14 @@ angular.module('syncthing.core')
|
||||
if (!newvalue || !shouldSetDefaultFolderPath()) {
|
||||
return;
|
||||
}
|
||||
$scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
|
||||
$scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
|
||||
});
|
||||
|
||||
$scope.$watch('currentFolder.id', function (newvalue) {
|
||||
if (!newvalue || !shouldSetDefaultFolderPath() || $scope.currentFolder.label) {
|
||||
return;
|
||||
}
|
||||
$scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
|
||||
$scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
|
||||
});
|
||||
|
||||
$scope.fsWatcherToggled = function () {
|
||||
@@ -1714,7 +1835,8 @@ angular.module('syncthing.core')
|
||||
$('#globalChanges').modal();
|
||||
};
|
||||
|
||||
$scope.editFolderModal = function () {
|
||||
function editFolderModal() {
|
||||
initVersioningEditing();
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.folderEditor.$setPristine();
|
||||
$('#editFolder').modal().one('shown.bs.tab', function (e) {
|
||||
@@ -1727,61 +1849,78 @@ angular.module('syncthing.core')
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editFolder = function (folderCfg) {
|
||||
$scope.editingExisting = true;
|
||||
$scope.currentFolder = angular.copy(folderCfg);
|
||||
$scope.editFolderModalTitle = function() {
|
||||
if ($scope.editingDefaults) {
|
||||
return $translate.instant("Edit Folder Defaults");
|
||||
}
|
||||
var title = '';
|
||||
if ($scope.editingExisting) {
|
||||
title += $translate.instant("Edit Folder");
|
||||
} else {
|
||||
title += $translate.instant("Add Folder");
|
||||
}
|
||||
if ($scope.currentFolder.id !== '') {
|
||||
title += ' (' + $scope.folderLabel($scope.currentFolder.id) + ')';
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
$scope.editFolderModalIcon = function() {
|
||||
if ($scope.editingDefaults || $scope.editingExisting) {
|
||||
return 'fas fa-pencil-alt';
|
||||
}
|
||||
return 'fas fa-folder';
|
||||
};
|
||||
|
||||
function editFolder() {
|
||||
if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
|
||||
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
|
||||
} else if (!$scope.currentFolder.path) {
|
||||
// undefined path leads to invalid input field
|
||||
$scope.currentFolder.path = '';
|
||||
}
|
||||
// Cache complete device objects indexed by ID for lookups
|
||||
initShareEditing('folder');
|
||||
$scope.currentFolder.devices.forEach(function (n) {
|
||||
if (n.deviceID !== $scope.myID) {
|
||||
$scope.currentSharing.shared.push($scope.devices[n.deviceID]);
|
||||
}
|
||||
$scope.currentSharing.selected[n.deviceID] = true;
|
||||
});
|
||||
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
|
||||
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
|
||||
});
|
||||
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
|
||||
$scope.currentFolder.trashcanFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "trashcan";
|
||||
$scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
|
||||
$scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
|
||||
$scope.currentFolder.simpleFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "simple";
|
||||
$scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
|
||||
$scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
|
||||
$scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
|
||||
$scope.currentFolder.staggeredFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "staggered";
|
||||
$scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
|
||||
$scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
|
||||
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
|
||||
$scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
|
||||
$scope.currentFolder.externalFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "external";
|
||||
$scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
|
||||
} else {
|
||||
$scope.currentFolder.fileVersioningSelector = "none";
|
||||
}
|
||||
$scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
|
||||
$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;
|
||||
editFolderModal();
|
||||
}
|
||||
|
||||
// staggeredMaxAge can validly be zero, which we should not replace
|
||||
// with the default value of 365. So only set the default if it's
|
||||
// actually undefined.
|
||||
if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
|
||||
$scope.currentFolder.staggeredMaxAge = 365;
|
||||
function initVersioningEditing() {
|
||||
$scope.currentFolder._guiVersioning = angular.copy($scope.versioningDefaults);
|
||||
|
||||
if (!$scope.currentFolder.versioning) {
|
||||
return;
|
||||
}
|
||||
$scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
|
||||
|
||||
var currentVersioning = $scope.currentFolder.versioning;
|
||||
|
||||
$scope.currentFolder._guiVersioning.cleanupIntervalS = +currentVersioning.cleanupIntervalS;
|
||||
|
||||
// Apply parameters currently in use
|
||||
switch (currentVersioning.type) {
|
||||
case "trashcan":
|
||||
$scope.currentFolder._guiVersioning.selector = "trashcan";
|
||||
$scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
|
||||
break;
|
||||
case "simple":
|
||||
$scope.currentFolder._guiVersioning.selector = "simple";
|
||||
$scope.currentFolder._guiVersioning.simpleKeep = +currentVersioning.params.keep;
|
||||
$scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
|
||||
break;
|
||||
case "staggered":
|
||||
$scope.currentFolder._guiVersioning.selector = "staggered";
|
||||
$scope.currentFolder._guiVersioning.staggeredMaxAge = Math.floor(+currentVersioning.params.maxAge / 86400);
|
||||
$scope.currentFolder._guiVersioning.staggeredCleanInterval = +currentVersioning.params.cleanInterval;
|
||||
$scope.currentFolder._guiVersioning.staggeredVersionsPath = currentVersioning.params.versionsPath;
|
||||
break;
|
||||
case "external":
|
||||
$scope.currentFolder._guiVersioning.selector = "external";
|
||||
$scope.currentFolder.externalCommand = currentVersioning.params.command;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editFolderExisting = function(folderCfg) {
|
||||
$scope.editingExisting = true;
|
||||
$scope.currentFolder = angular.copy(folderCfg);
|
||||
|
||||
$scope.ignores.text = 'Loading...';
|
||||
$scope.ignores.error = null;
|
||||
@@ -1798,7 +1937,18 @@ angular.module('syncthing.core')
|
||||
$scope.emitHTTPError(err);
|
||||
});
|
||||
|
||||
$scope.editFolderModal();
|
||||
editFolder();
|
||||
};
|
||||
|
||||
$scope.editFolderDefaults = function() {
|
||||
$http.get(urlbase + '/config/defaults/folder')
|
||||
.success(function (data) {
|
||||
$scope.currentFolder = data;
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = true;
|
||||
editFolder();
|
||||
})
|
||||
.error($scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$scope.selectAllSharedDevices = function (state) {
|
||||
@@ -1817,37 +1967,43 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.addFolder = function () {
|
||||
$http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
||||
initShareEditing('folder');
|
||||
$scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
|
||||
$scope.currentSharing.unrelated = $scope.otherDevices();
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
$scope.editFolderModal();
|
||||
var folderID = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
|
||||
addFolderInit(folderID).then(function() {
|
||||
// Triggers the watch that sets the path
|
||||
$scope.currentFolder.label = $scope.currentFolder.label;
|
||||
editFolderModal();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addFolderAndShare = function (folder, folderLabel, device) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
||||
$scope.currentFolder.id = folder;
|
||||
$scope.currentFolder.label = folderLabel;
|
||||
$scope.currentFolder.viewFlags = {
|
||||
importFromOtherDevice: true
|
||||
};
|
||||
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]
|
||||
$scope.addFolderAndShare = function (folderID, folderLabel, device) {
|
||||
addFolderInit(folderID).then(function() {
|
||||
$scope.currentFolder.viewFlags = {
|
||||
importFromOtherDevice: true
|
||||
};
|
||||
$scope.currentSharing.selected[device] = true;
|
||||
$scope.currentFolder.label = folderLabel;
|
||||
editFolderModal();
|
||||
});
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
$scope.editFolderModal();
|
||||
};
|
||||
|
||||
function addFolderInit(folderID) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = false;
|
||||
return $http.get(urlbase + '/config/defaults/folder').then(function(p) {
|
||||
$scope.currentFolder = p.data;
|
||||
$scope.currentFolder.id = folderID;
|
||||
|
||||
initShareEditing('folder');
|
||||
$scope.currentSharing.unrelated = $scope.currentSharing.unrelated.concat($scope.currentSharing.shared);
|
||||
$scope.currentSharing.shared = [];
|
||||
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
}, $scope.emitHTTPError);
|
||||
}
|
||||
|
||||
$scope.shareFolderWithDevice = function (folder, device) {
|
||||
$scope.folders[folder].devices.push({
|
||||
deviceID: device
|
||||
@@ -1877,55 +2033,60 @@ angular.module('syncthing.core')
|
||||
folderCfg.devices = newDevices;
|
||||
delete $scope.currentSharing;
|
||||
|
||||
if (folderCfg.fileVersioningSelector === "trashcan") {
|
||||
switch (folderCfg._guiVersioning.selector) {
|
||||
case "trashcan":
|
||||
folderCfg.versioning = {
|
||||
'type': 'trashcan',
|
||||
'params': {
|
||||
'cleanoutDays': '' + folderCfg.trashcanClean
|
||||
'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.trashcanFileVersioning;
|
||||
delete folderCfg.trashcanClean;
|
||||
} else if (folderCfg.fileVersioningSelector === "simple") {
|
||||
break;
|
||||
case "simple":
|
||||
folderCfg.versioning = {
|
||||
'type': 'simple',
|
||||
'params': {
|
||||
'keep': '' + folderCfg.simpleKeep,
|
||||
'cleanoutDays': '' + folderCfg.trashcanClean
|
||||
'keep': '' + folderCfg._guiVersioning.simpleKeep,
|
||||
'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.simpleFileVersioning;
|
||||
delete folderCfg.simpleKeep;
|
||||
} else if (folderCfg.fileVersioningSelector === "staggered") {
|
||||
break;
|
||||
case "staggered":
|
||||
folderCfg.versioning = {
|
||||
'type': 'staggered',
|
||||
'params': {
|
||||
'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
|
||||
'cleanInterval': '' + folderCfg.staggeredCleanInterval,
|
||||
'versionsPath': '' + folderCfg.staggeredVersionsPath
|
||||
'maxAge': '' + (folderCfg._guiVersioning.staggeredMaxAge * 86400),
|
||||
'cleanInterval': '' + folderCfg._guiVersioning.staggeredCleanInterval,
|
||||
'versionsPath': '' + folderCfg._guiVersioning.staggeredVersionsPath
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.staggeredFileVersioning;
|
||||
delete folderCfg.staggeredMaxAge;
|
||||
delete folderCfg.staggeredCleanInterval;
|
||||
delete folderCfg.staggeredVersionsPath;
|
||||
} else if (folderCfg.fileVersioningSelector === "external") {
|
||||
break;
|
||||
case "external":
|
||||
folderCfg.versioning = {
|
||||
'type': 'external',
|
||||
'params': {
|
||||
'command': '' + folderCfg.externalCommand
|
||||
'command': '' + folderCfg._guiVersioning.externalCommand
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.externalFileVersioning;
|
||||
delete folderCfg.externalCommand;
|
||||
} else {
|
||||
break;
|
||||
default:
|
||||
delete folderCfg.versioning;
|
||||
}
|
||||
delete folderCfg._guiVersioning;
|
||||
|
||||
if ($scope.editingDefaults) {
|
||||
$scope.config.defaults.folder = folderCfg;
|
||||
$scope.saveConfig();
|
||||
} else {
|
||||
saveFolderExisting(folderCfg);
|
||||
}
|
||||
};
|
||||
|
||||
function saveFolderExisting(folderCfg) {
|
||||
var ignoresLoaded = !$scope.ignores.disabled;
|
||||
var ignores = $scope.ignores.text.split('\n');
|
||||
// Split always returns a minimum 1-length array even for no patterns
|
||||
@@ -1939,7 +2100,11 @@ angular.module('syncthing.core')
|
||||
$scope.folders[folderCfg.id] = folderCfg;
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
|
||||
if (ignoresLoaded && $scope.editingExisting && ignores !== folderCfg.ignores) {
|
||||
function arrayEquals(a, b) {
|
||||
return a.length === b.length && a.every(function(v, i) { return v === b[i] });
|
||||
}
|
||||
|
||||
if (ignoresLoaded && $scope.editingExisting && !arrayEquals(ignores, folderCfg.ignores)) {
|
||||
saveIgnores(ignores);
|
||||
};
|
||||
|
||||
@@ -1952,13 +2117,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 +2498,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 +2615,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 + ')';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<modal id="editDevice" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-desktop'}}" heading="{{editingExisting ? 'Edit Device' : 'Add Device' | translate}} {{currentDevice.name}}" large="yes" closeable="yes">
|
||||
<modal id="editDevice" status="default" icon="{{editDeviceModalIcon()}}" heading="{{editDeviceModalTitle()}}" large="yes" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<form role="form" name="deviceEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<li class="active"><a data-toggle="tab" href="#device-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li ng-if="!editingDefaults"><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li><a data-toggle="tab" href="#device-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div id="device-general" class="tab-pane in active">
|
||||
<div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<label translate for="deviceID">Device ID</label>
|
||||
<div ng-if="!editingExisting">
|
||||
<input name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required="" valid-deviceid list="discovery-list" aria-required="true" />
|
||||
@@ -38,7 +38,7 @@
|
||||
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="device-sharing" class="tab-pane">
|
||||
<div ng-if="!editingDefaults" id="device-sharing" class="tab-pane">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
@@ -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> 
|
||||
<small><a href="#" ng-click="selectAllFolders(true)" translate>Select All</a> 
|
||||
<a href="#" ng-click="selectAllFolders(false)" translate>Deselect All</a></small>
|
||||
<span translate>Deselect folders to stop sharing with this device.</span> 
|
||||
<small><a href="#" ng-click="selectAllSharedFolders(true)" translate>Select All</a> 
|
||||
<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]" /> {{folder.id}}
|
||||
</label>
|
||||
<label ng-if="folder.label.length != 0">
|
||||
<input type="checkbox" ng-model="currentSharing.selected[folder.id]" /> <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> 
|
||||
<small><a href="#" ng-click="selectAllUnrelatedFolders(true)" translate>Select All</a> 
|
||||
<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]"> {{folder.id}}
|
||||
@@ -141,17 +164,14 @@
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid">
|
||||
<span class="fas fa-check"></span> <span translate>Save</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
|
||||
<button ng-if="!editingDefaults" type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
|
||||
<span class="fas fa-qrcode"></span> <span translate>Show QR</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span> <span translate>Close</span>
|
||||
</button>
|
||||
<div ng-if="editingExisting" class="pull-left">
|
||||
<button type="button" class="btn btn-warning btn-sm disabled" ng-if="willBeReintroducedBy" tooltip data-original-title="This device will be reintroduced by {{ willBeReintroducedBy }}">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation" ng-if="!willBeReintroducedBy">
|
||||
<div ng-if="editingExisting && !editingDefaults" class="pull-left">
|
||||
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<p ng-model="currentDevice.name" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<span translate translate-value-name="{{currentDevice.name}}">Are you sure you want to remove device {%name%}?</span>
|
||||
</p>
|
||||
<p ng-if="willBeReintroducedBy" translate translate-value-reintroducer="{{willBeReintroducedBy}}">{%reintroducer%} might reintroduce this device.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-dismiss="modal" ng-click="deleteDevice()">
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<modal id="editFolder" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-folder'}}" heading="{{editingExisting ? 'Edit Folder' : 'Add Folder' | translate}} ({{folderLabel(currentFolder.id)}})" large="yes" closeable="yes">
|
||||
<modal id="editFolder" status="default" icon="{{editFolderModalIcon()}}" heading="{{editFolderModalTitle()}}" large="yes" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<form role="form" name="folderEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
|
||||
<li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li ng-if="!editingDefaults"><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
|
||||
<div id="folder-general" class="tab-pane in active">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty && !editingDefaults}">
|
||||
<label for="folderLabel"><span translate>Folder Label</span></label>
|
||||
<input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<label for="folderID"><span translate>Folder ID</span></label>
|
||||
<input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
|
||||
<p class="help-block">
|
||||
@@ -28,19 +28,21 @@
|
||||
<span translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty && !editingDefaults}">
|
||||
<label translate for="folderPath">Folder Path</label>
|
||||
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required="" aria-required="true" path-is-sub-dir />
|
||||
<input name="folderPath" ng-readonly="editingExisting && !editingDefaults" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" ng-required="!editingDefaults" ng-aria-required="!editingDefaults" path-is-sub-dir />
|
||||
<datalist id="directory-list">
|
||||
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
|
||||
</datalist>
|
||||
<p class="help-block">
|
||||
<span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty && !editingDefaults">The folder path cannot be blank.</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span ng-if="folderPathErrors.isParent && !editingDefaults">
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,7 +90,7 @@
|
||||
<div id="folder-versioning" class="tab-pane">
|
||||
<div class="form-group">
|
||||
<label translate>File Versioning</label> <a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fas fa-question-circle"></span> <span translate>Help</span></a>
|
||||
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
|
||||
<select class="form-control" ng-model="currentFolder._guiVersioning.selector">
|
||||
<option value="none" translate>No File Versioning</option>
|
||||
<option value="trashcan" translate>Trash Can File Versioning</option>
|
||||
<option value="simple" translate>Simple File Versioning</option>
|
||||
@@ -96,46 +98,46 @@
|
||||
<option value="external" translate>External File Versioning</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan' || currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
|
||||
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
|
||||
<label translate for="trashcanClean">Clean out after</label>
|
||||
<div class="input-group">
|
||||
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required="" aria-required="true" min="0" />
|
||||
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.trashcanClean" required="" aria-required="true" min="0" />
|
||||
<div class="input-group-addon" translate>days</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
|
||||
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
|
||||
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
|
||||
<label translate for="simpleKeep">Keep Versions</label>
|
||||
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required="" aria-required="true" min="1" />
|
||||
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
|
||||
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
|
||||
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
|
||||
<p translate class="help-block">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.</p>
|
||||
<label translate for="staggeredMaxAge">Maximum Age</label>
|
||||
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required="" aria-required="true" min="0" />
|
||||
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
|
||||
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector == 'staggered'">
|
||||
<label translate for="staggeredVersionsPath">Versions Path</label>
|
||||
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath" />
|
||||
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder._guiVersioning.staggeredVersionsPath" />
|
||||
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
|
||||
<p translate class="help-block">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.</p>
|
||||
<label translate for="externalCommand">Command</label>
|
||||
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required="" aria-required="true" />
|
||||
@@ -144,21 +146,21 @@
|
||||
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector != 'none'" ng-class="{'has-error': folderEditor.versioningCleanupIntervalS.$invalid && folderEditor.versioningCleanupIntervalS.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector != 'none'" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
|
||||
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
|
||||
<div class="input-group">
|
||||
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder.versioningCleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
|
||||
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
|
||||
<div class="input-group-addon" translate>seconds</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$valid || folderEditor.versioningCleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
|
||||
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.required && folderEditor.versioningCleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.min && folderEditor.versioningCleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="folder-ignores" class="tab-pane">
|
||||
<div ng-if="!editingDefaults" id="folder-ignores" class="tab-pane">
|
||||
<p translate>Enter ignore patterns, one per line.</p>
|
||||
<div ng-class="{'has-error': ignores.error != null}">
|
||||
<textarea class="form-control" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
|
||||
@@ -280,7 +282,7 @@
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span> <span translate>Close</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting">
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting && !editingDefaults">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<div ng-repeat="(key, value) in advancedConfig.gui" ng-init="type = inputTypeFor(key, value)" ng-if="type != 'skip'" class="form-group">
|
||||
<label for="guiInput{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="optionsInput{{$index}}" class="form-control" type="text" ng-model="advancedConfig.gui[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="optionsInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="advancedConfig.gui[key]" />
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="guiInput{{$index}}" class="form-control" type="text" ng-model="advancedConfig.gui[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="guiInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="advancedConfig.gui[key]" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -65,50 +65,113 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-repeat="folder in advancedConfig.folders">
|
||||
<div class="panel-heading" role="tab" id="folder{{$index}}Heading" data-toggle="collapse" data-parent="#advancedAccordion" href="#folder{{$index}}Config" aria-expanded="false" aria-controls="folder{{$index}}Config" style="cursor: pointer;">
|
||||
<h4 ng-if="folder.label.length == 0" class="panel-title" tabindex="0">
|
||||
<span translate>Folder</span> "{{folder.id}}"
|
||||
</h4>
|
||||
<h4 ng-if="folder.label.length != 0" class="panel-title" tabindex="0">
|
||||
<span translate>Folder</span> "{{folder.label}}" ({{folder.id}})
|
||||
</h4>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="advancedFoldersHeading" data-toggle="collapse" data-parent="#advancedAccordion" href="#advancedFolders" aria-expanded="false" aria-controls="advancedFolders" style="cursor: pointer;">
|
||||
<h4 class="panel-title" translate>Folders</h4>
|
||||
</div>
|
||||
<div id="folder{{$index}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="folder{{$index}}Heading">
|
||||
<div id="advancedFolders" class="panel-collapse collapse" role="tabpanel" aria-labelledby="advancedFoldersHeading">
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in folder" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
|
||||
<label for="folder{{$index}}Input{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="optionsInput{{$index}}" class="form-control" type="text" ng-model="folder[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="optionsInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="folder[key]" />
|
||||
<div class="panel panel-default" ng-repeat="folder in advancedConfig.folders" ng-init="folderIndex = $index">
|
||||
<div class="panel-heading" role="tab" id="folder{{folderIndex}}Heading" data-toggle="collapse" data-parent="#advancedFolders" href="#folder{{folderIndex}}Config" aria-expanded="false" aria-controls="folder{{folderIndex}}Config" style="cursor: pointer;">
|
||||
<h4 ng-if="folder.label.length == 0" class="panel-title" tabindex="0">
|
||||
<span translate>Folder</span> "{{folder.id}}"
|
||||
</h4>
|
||||
<h4 ng-if="folder.label.length != 0" class="panel-title" tabindex="0">
|
||||
<span translate>Folder</span> "{{folder.label}}" ({{folder.id}})
|
||||
</h4>
|
||||
</div>
|
||||
<div id="folder{{folderIndex}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="folder{{folderIndex}}Heading">
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in folder" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
|
||||
<label for="folder{{folderIndex}}Input{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="folder{{folderIndex}}Input{{$index}}" class="form-control" type="text" ng-model="folder[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="folder{{folderIndex}}Input{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="folder[key]" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-repeat="device in advancedConfig.devices">
|
||||
<div class="panel-heading" role="tab" id="device{{$index}}Heading" data-toggle="collapse" data-parent="#advancedAccordion" href="#device{{$index}}Config" aria-expanded="false" aria-controls="folder{{$index}}Config" style="cursor: pointer;">
|
||||
<h4 class="panel-title" tabindex="0">
|
||||
<span translate>Device</span> "{{deviceName(device)}}"
|
||||
</h4>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="advancedDevicesHeading" data-toggle="collapse" data-parent="#advancedAccordion" href="#advancedDevices" aria-expanded="false" aria-controls="advancedDevices" style="cursor: pointer;">
|
||||
<h4 class="panel-title" tabindex="0" translate>Devices</h4>
|
||||
</div>
|
||||
<div id="device{{$index}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="device{{$index}}Heading">
|
||||
<div id="advancedDevices" class="panel-collapse collapse" role="tabpanel" aria-labelledby="advancedDevicesHeading">
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in device" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
|
||||
<label for="device{{$index}}Input{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="optionsInput{{$index}}" class="form-control" type="text" ng-model="device[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="optionsInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="device[key]" />
|
||||
<div class="panel panel-default" ng-repeat="device in advancedConfig.devices" ng-init="deviceIndex = $index">
|
||||
<div class="panel-heading" role="tab" id="device{{deviceIndex}}Heading" data-toggle="collapse" data-parent="#advancedDevices" href="#device{{deviceIndex}}Config" aria-expanded="false" aria-controls="device{{deviceIndex}}Config" style="cursor: pointer;">
|
||||
<h4 class="panel-title" tabindex="0">
|
||||
<span translate>Device</span> "{{deviceName(device)}}"
|
||||
</h4>
|
||||
</div>
|
||||
<div id="device{{deviceIndex}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="device{{deviceIndex}}Heading">
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in device" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
|
||||
<label for="device{{deviceIndex}}Input{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="device{{deviceIndex}}Input{{$index}}" class="form-control" type="text" ng-model="device[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="device{{deviceIndex}}Input{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="device[key]" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="advancedDefaultsHeading" data-toggle="collapse" data-parent="#advancedAccordion" href="#advancedDefaults" aria-expanded="false" aria-controls="advancedDefaults" style="cursor: pointer;">
|
||||
<h4 class="panel-title" tabindex="0" translate>Defaults</h4>
|
||||
</div>
|
||||
<div id="advancedDefaults" class="panel-collapse collapse" role="tabpanel" aria-labelledby="advancedDefaultsHeading">
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="advancedDefaultFolderHeading" data-toggle="collapse" data-parent="#advancedDefaults" href="#advancedDefaultFolder" aria-expanded="false" aria-controls="advancedDefaultFolder" style="cursor: pointer;">
|
||||
<h4 class="panel-title" tabindex="0" translate>Default Folder</h4>
|
||||
</div>
|
||||
<div id="advancedDefaultFolder" class="panel-collapse collapse" role="tabpanel" aria-labelledby="advancedDefaultFolderHeading">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in advancedConfig.defaults.folder" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
|
||||
<label for="advancedDefaultFolderInput{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="advancedDefaultFolderInput{{$index}}" class="form-control" type="text" ng-model="advancedConfig.defaults.folder[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="advancedDefaultFolderInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="advancedConfig.defaults.folder[key]" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="advancedDefaultDeviceHeading" data-toggle="collapse" data-parent="#advancedDefaults" href="#advancedDefaultDevice" aria-expanded="false" aria-controls="advancedDefaultDevice" style="cursor: pointer;">
|
||||
<h4 class="panel-title" tabindex="0" translate>Default Device</h4>
|
||||
</div>
|
||||
<div id="advancedDefaultDevice" class="panel-collapse collapse" role="tabpanel" aria-labelledby="advancedDefaultDeviceHeading">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in advancedConfig.defaults.device" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
|
||||
<label for="advancedDefaultDeviceInput{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input ng-if="inputTypeFor(key, value) == 'list'" id="advancedDefaultDeviceInput{{$index}}" class="form-control" type="text" ng-model="advancedConfig.defaults.device[key]" ng-list />
|
||||
<input ng-if="inputTypeFor(key, value) != 'list'" id="advancedDefaultDeviceInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="advancedConfig.defaults.device[key]" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -100,13 +100,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label translate for="urVersion">Default Folder Path</label>
|
||||
<input id="DefaultFolderPath" class="form-control" type="text" ng-model="tmpOptions.defaultFolderPath" />
|
||||
<p class="help-block">
|
||||
<span translate translate-value-tilde="{{system.tilde}}">
|
||||
Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.
|
||||
</span>
|
||||
<div>
|
||||
<label translate>Default Configuration</label>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default btn-secondary" ng-click="editFolderDefaults()">
|
||||
<span translate>Edit Folder Defaults</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-secondary" ng-click="editDeviceDefaults()">
|
||||
<span translate>Edit Device Defaults</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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> <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> <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> <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> <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> <span translate>Ignore</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -566,7 +566,7 @@
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
|
||||
<span class="fas fa-refresh"></span> <span translate>Rescan</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editFolder(folder)">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editFolderExisting(folder)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
@@ -582,7 +582,7 @@
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="setAllFoldersPause(false)" ng-if="isAtleastOneFolderPausedStateSetTo(true)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume All</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanAllFolders()">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanAllFolders()" ng-disabled="!isAtleastOneFolderPausedStateSetTo(false)">
|
||||
<span class="fas fa-refresh"></span> <span translate>Rescan All</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="addFolder()">
|
||||
@@ -833,7 +833,7 @@
|
||||
<button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editDeviceExisting(deviceCfg)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@@ -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 = {}
|
||||
@@ -50,6 +52,7 @@ angular.module('syncthing.core')
|
||||
$scope.metricRates = false;
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.currentFolder = {};
|
||||
$scope.currentDevice = {};
|
||||
$scope.ignores = {
|
||||
text: '',
|
||||
error: null,
|
||||
@@ -61,17 +64,8 @@ angular.module('syncthing.core')
|
||||
$scope.metricRates = (window.localStorage["metricRates"] == "true");
|
||||
} catch (exception) { }
|
||||
|
||||
$scope.folderDefaults = {
|
||||
devices: [],
|
||||
type: "sendreceive",
|
||||
rescanIntervalS: 3600,
|
||||
fsWatcherDelayS: 10,
|
||||
fsWatcherEnabled: true,
|
||||
minDiskFree: { value: 1, unit: "%" },
|
||||
maxConflicts: 10,
|
||||
fsync: true,
|
||||
order: "random",
|
||||
fileVersioningSelector: "none",
|
||||
$scope.versioningDefaults = {
|
||||
selector: "none",
|
||||
trashcanClean: 0,
|
||||
versioningCleanupIntervalS: 3600,
|
||||
simpleKeep: 5,
|
||||
@@ -79,8 +73,6 @@ angular.module('syncthing.core')
|
||||
staggeredCleanInterval: 3600,
|
||||
staggeredVersionsPath: "",
|
||||
externalCommand: "",
|
||||
autoNormalize: true,
|
||||
path: "",
|
||||
};
|
||||
|
||||
$scope.localStateTotal = {
|
||||
@@ -120,6 +112,7 @@ angular.module('syncthing.core')
|
||||
refreshSystem();
|
||||
refreshDiscoveryCache();
|
||||
refreshConfig();
|
||||
refreshCluster();
|
||||
refreshConnectionStats();
|
||||
refreshDeviceStats();
|
||||
refreshFolderStats();
|
||||
@@ -242,6 +235,71 @@ angular.module('syncthing.core')
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on(Events.PENDING_DEVICES_CHANGED, function (event, arg) {
|
||||
if (!(arg.data.added || arg.data.removed)) {
|
||||
// Not enough information to update in place, just refresh it completely
|
||||
refreshCluster();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.data.added) {
|
||||
arg.data.added.forEach(function (rejected) {
|
||||
var pendingDevice = {
|
||||
time: arg.time,
|
||||
name: rejected.name,
|
||||
address: rejected.address
|
||||
};
|
||||
console.log("rejected device:", rejected.deviceID, pendingDevice);
|
||||
$scope.pendingDevices[rejected.deviceID] = pendingDevice;
|
||||
});
|
||||
}
|
||||
|
||||
if (arg.data.removed) {
|
||||
arg.data.removed.forEach(function (dev) {
|
||||
console.log("no longer pending device:", dev.deviceID);
|
||||
delete $scope.pendingDevices[dev.deviceID];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on(Events.PENDING_FOLDERS_CHANGED, function (event, arg) {
|
||||
if (!(arg.data.added || arg.data.removed)) {
|
||||
// Not enough information to update in place, just refresh it completely
|
||||
refreshCluster();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.data.added) {
|
||||
arg.data.added.forEach(function (rejected) {
|
||||
var offeringDevice = {
|
||||
time: arg.time,
|
||||
label: rejected.folderLabel
|
||||
};
|
||||
console.log("rejected folder", rejected.folderID, "from device:", rejected.deviceID, offeringDevice);
|
||||
|
||||
var pendingFolder = $scope.pendingFolders[rejected.folderID];
|
||||
if (pendingFolder === undefined) {
|
||||
pendingFolder = {
|
||||
offeredBy: {}
|
||||
};
|
||||
}
|
||||
pendingFolder.offeredBy[rejected.deviceID] = offeringDevice;
|
||||
$scope.pendingFolders[rejected.folderID] = pendingFolder;
|
||||
});
|
||||
}
|
||||
|
||||
if (arg.data.removed) {
|
||||
arg.data.removed.forEach(function (folderDev) {
|
||||
console.log("no longer pending folder", folderDev.folderID, "from device:", folderDev.deviceID);
|
||||
if (folderDev.deviceID === undefined) {
|
||||
delete $scope.pendingFolders[folderDev.folderID];
|
||||
} else if ($scope.pendingFolders[folderDev.folderID]) {
|
||||
delete $scope.pendingFolders[folderDev.folderID].offeredBy[folderDev.deviceID];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('ConfigLoaded', function () {
|
||||
if ($scope.config.options.urAccepted === 0) {
|
||||
// If usage reporting has been neither accepted nor declined,
|
||||
@@ -455,6 +513,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) {
|
||||
@@ -649,7 +717,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
function shouldSetDefaultFolderPath() {
|
||||
return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
|
||||
return $scope.config.defaults.folder.path && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine && !$scope.editingDefaults;
|
||||
}
|
||||
|
||||
function resetRemoteNeed() {
|
||||
@@ -692,8 +760,26 @@ angular.module('syncthing.core')
|
||||
$scope.currentSharing.unrelated = [];
|
||||
$scope.currentSharing.selected = {};
|
||||
$scope.currentSharing.encryptionPasswords = {};
|
||||
if (editing === 'folder') {
|
||||
initShareEditingFolder();
|
||||
}
|
||||
};
|
||||
|
||||
function initShareEditingFolder() {
|
||||
$scope.currentFolder.devices.forEach(function (n) {
|
||||
if (n.deviceID !== $scope.myID) {
|
||||
$scope.currentSharing.shared.push($scope.devices[n.deviceID]);
|
||||
}
|
||||
if (n.encryptionPassword !== '') {
|
||||
$scope.currentSharing.encryptionPasswords[n.deviceID] = n.encryptionPassword;
|
||||
}
|
||||
$scope.currentSharing.selected[n.deviceID] = true;
|
||||
});
|
||||
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
|
||||
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
|
||||
});
|
||||
}
|
||||
|
||||
$scope.refreshFailed = function (page, perpage) {
|
||||
if (!$scope.failed || !$scope.failed.folder) {
|
||||
return;
|
||||
@@ -1016,7 +1102,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 +1117,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 +1180,7 @@ angular.module('syncthing.core')
|
||||
if (matches.length !== 1) {
|
||||
return shortID;
|
||||
}
|
||||
return matches[0].name;
|
||||
return $scope.friendlyNameFromID(matches[0]);
|
||||
};
|
||||
|
||||
$scope.friendlyNameFromID = function (deviceID) {
|
||||
@@ -1165,6 +1247,7 @@ angular.module('syncthing.core')
|
||||
}).error($scope.emitHTTPError);
|
||||
},
|
||||
show: function () {
|
||||
$scope.logging.paused = false;
|
||||
$scope.logging.refreshFacilities();
|
||||
$scope.logging.timer = $timeout($scope.logging.fetch);
|
||||
var textArea = $('#logViewerText');
|
||||
@@ -1422,9 +1505,40 @@ angular.module('syncthing.core')
|
||||
$scope.configInSync = true;
|
||||
};
|
||||
|
||||
$scope.editDevice = function (deviceCfg) {
|
||||
function editDeviceModal() {
|
||||
$scope.currentDevice._addressesStr = $scope.currentDevice.addresses.join(', ');
|
||||
$scope.deviceEditor.$setPristine();
|
||||
$('#editDevice').modal();
|
||||
}
|
||||
|
||||
$scope.editDeviceModalTitle = function() {
|
||||
if ($scope.editingDefaults) {
|
||||
return $translate.instant("Edit Device Defaults");
|
||||
}
|
||||
var title = '';
|
||||
if ($scope.editingExisting) {
|
||||
title += $translate.instant("Edit Device");
|
||||
} else {
|
||||
title += $translate.instant("Add Device");
|
||||
}
|
||||
var name = $scope.deviceName($scope.currentDevice);
|
||||
if (name !== '') {
|
||||
title += ' (' + name + ')';
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
$scope.editDeviceModalIcon = function() {
|
||||
if ($scope.editingDefaults || $scope.editingExisting) {
|
||||
return 'fas fa-pencil-alt';
|
||||
}
|
||||
return 'fas fa-desktop';
|
||||
};
|
||||
|
||||
$scope.editDeviceExisting = function (deviceCfg) {
|
||||
$scope.currentDevice = $.extend({}, deviceCfg);
|
||||
$scope.editingExisting = true;
|
||||
$scope.editingDefaults = false;
|
||||
$scope.willBeReintroducedBy = undefined;
|
||||
if (deviceCfg.introducedBy) {
|
||||
var introducerDevice = $scope.devices[deviceCfg.introducedBy];
|
||||
@@ -1432,41 +1546,43 @@ angular.module('syncthing.core')
|
||||
$scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
|
||||
}
|
||||
}
|
||||
$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];
|
||||
});
|
||||
editDeviceModal();
|
||||
};
|
||||
|
||||
$scope.deviceEditor.$setPristine();
|
||||
$('#editDevice').modal();
|
||||
$scope.editDeviceDefaults = function () {
|
||||
$http.get(urlbase + '/config/defaults/device').then(function (p) {
|
||||
$scope.currentDevice = p.data;
|
||||
$scope.editingDefaults = true;
|
||||
editDeviceModal();
|
||||
}, $scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1485,19 +1601,16 @@ angular.module('syncthing.core')
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
$scope.currentDevice = {
|
||||
name: name,
|
||||
deviceID: deviceID,
|
||||
_addressesStr: 'dynamic',
|
||||
compression: 'metadata',
|
||||
introducer: false,
|
||||
pendingFolders: [],
|
||||
ignoredFolders: []
|
||||
};
|
||||
$scope.editingExisting = false;
|
||||
initShareEditing('device');
|
||||
$scope.deviceEditor.$setPristine();
|
||||
$('#editDevice').modal();
|
||||
$http.get(urlbase + '/config/defaults/device').then(function (p) {
|
||||
$scope.currentDevice = p.data;
|
||||
$scope.currentDevice.name = name;
|
||||
$scope.currentDevice.deviceID = deviceID;
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = false;
|
||||
initShareEditing('device');
|
||||
$scope.currentSharing.unrelated = $scope.folderList();
|
||||
editDeviceModal();
|
||||
}, $scope.emitHTTPError);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1522,55 +1635,61 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.saveDevice = function () {
|
||||
$('#editDevice').modal('hide');
|
||||
$scope.saveDeviceConfig($scope.currentDevice);
|
||||
};
|
||||
|
||||
$scope.saveDeviceConfig = function (deviceCfg) {
|
||||
deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
|
||||
$scope.currentDevice.addresses = $scope.currentDevice._addressesStr.split(',').map(function (x) {
|
||||
return x.trim();
|
||||
});
|
||||
|
||||
$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) {
|
||||
// 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.currentDevice._addressesStr;
|
||||
if ($scope.editingDefaults) {
|
||||
$scope.config.defaults.device = $scope.currentDevice;
|
||||
} else {
|
||||
setDeviceConfig();
|
||||
}
|
||||
delete $scope.currentSharing;
|
||||
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
|
||||
delete $scope.currentDevice;
|
||||
$scope.saveConfig();
|
||||
};
|
||||
|
||||
$scope.ignoreDevice = function (pendingDevice) {
|
||||
pendingDevice = angular.copy(pendingDevice);
|
||||
function setDeviceConfig() {
|
||||
var currentID = $scope.currentDevice.deviceID;
|
||||
$scope.devices[currentID] = $scope.currentDevice;
|
||||
$scope.config.devices = deviceList($scope.devices);
|
||||
|
||||
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 === currentID) {
|
||||
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: currentID,
|
||||
encryptionPassword: $scope.currentSharing.encryptionPasswords[id],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Remove device from folder
|
||||
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
|
||||
return n.deviceID !== currentID;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
};
|
||||
|
||||
$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();
|
||||
};
|
||||
|
||||
@@ -1699,14 +1818,14 @@ angular.module('syncthing.core')
|
||||
if (!newvalue || !shouldSetDefaultFolderPath()) {
|
||||
return;
|
||||
}
|
||||
$scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
|
||||
$scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
|
||||
});
|
||||
|
||||
$scope.$watch('currentFolder.id', function (newvalue) {
|
||||
if (!newvalue || !shouldSetDefaultFolderPath() || $scope.currentFolder.label) {
|
||||
return;
|
||||
}
|
||||
$scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
|
||||
$scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
|
||||
});
|
||||
|
||||
$scope.fsWatcherToggled = function () {
|
||||
@@ -1733,7 +1852,8 @@ angular.module('syncthing.core')
|
||||
$('#globalChanges').modal();
|
||||
};
|
||||
|
||||
$scope.editFolderModal = function () {
|
||||
function editFolderModal() {
|
||||
initVersioningEditing();
|
||||
$scope.folderPathErrors = {};
|
||||
$scope.folderEditor.$setPristine();
|
||||
$('#editFolder').modal().one('shown.bs.tab', function (e) {
|
||||
@@ -1746,64 +1866,78 @@ angular.module('syncthing.core')
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editFolder = function (folderCfg) {
|
||||
$scope.editingExisting = true;
|
||||
$scope.currentFolder = angular.copy(folderCfg);
|
||||
$scope.editFolderModalTitle = function() {
|
||||
if ($scope.editingDefaults) {
|
||||
return $translate.instant("Edit Folder Defaults");
|
||||
}
|
||||
var title = '';
|
||||
if ($scope.editingExisting) {
|
||||
title += $translate.instant("Edit Folder");
|
||||
} else {
|
||||
title += $translate.instant("Add Folder");
|
||||
}
|
||||
if ($scope.currentFolder.id !== '') {
|
||||
title += ' (' + $scope.folderLabel($scope.currentFolder.id) + ')';
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
$scope.editFolderModalIcon = function() {
|
||||
if ($scope.editingDefaults || $scope.editingExisting) {
|
||||
return 'fas fa-pencil-alt';
|
||||
}
|
||||
return 'fas fa-folder';
|
||||
};
|
||||
|
||||
function editFolder() {
|
||||
if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
|
||||
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
|
||||
} else if (!$scope.currentFolder.path) {
|
||||
// undefined path leads to invalid input field
|
||||
$scope.currentFolder.path = '';
|
||||
}
|
||||
// Cache complete device objects indexed by ID for lookups
|
||||
initShareEditing('folder');
|
||||
$scope.currentFolder.devices.forEach(function (n) {
|
||||
if (n.deviceID !== $scope.myID) {
|
||||
$scope.currentSharing.shared.push($scope.devices[n.deviceID]);
|
||||
}
|
||||
if (n.encryptionPassword !== '') {
|
||||
$scope.currentSharing.encryptionPasswords[n.deviceID] = n.encryptionPassword;
|
||||
}
|
||||
$scope.currentSharing.selected[n.deviceID] = true;
|
||||
});
|
||||
$scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
|
||||
return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
|
||||
});
|
||||
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
|
||||
$scope.currentFolder.trashcanFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "trashcan";
|
||||
$scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
|
||||
$scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
|
||||
$scope.currentFolder.simpleFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "simple";
|
||||
$scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
|
||||
$scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
|
||||
$scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
|
||||
$scope.currentFolder.staggeredFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "staggered";
|
||||
$scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
|
||||
$scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
|
||||
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
|
||||
$scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
|
||||
$scope.currentFolder.externalFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "external";
|
||||
$scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
|
||||
} else {
|
||||
$scope.currentFolder.fileVersioningSelector = "none";
|
||||
}
|
||||
$scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
|
||||
$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;
|
||||
editFolderModal();
|
||||
}
|
||||
|
||||
// staggeredMaxAge can validly be zero, which we should not replace
|
||||
// with the default value of 365. So only set the default if it's
|
||||
// actually undefined.
|
||||
if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
|
||||
$scope.currentFolder.staggeredMaxAge = 365;
|
||||
function initVersioningEditing() {
|
||||
$scope.currentFolder._guiVersioning = angular.copy($scope.versioningDefaults);
|
||||
|
||||
if (!$scope.currentFolder.versioning) {
|
||||
return;
|
||||
}
|
||||
$scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
|
||||
|
||||
var currentVersioning = $scope.currentFolder.versioning;
|
||||
|
||||
$scope.currentFolder._guiVersioning.cleanupIntervalS = +currentVersioning.cleanupIntervalS;
|
||||
|
||||
// Apply parameters currently in use
|
||||
switch (currentVersioning.type) {
|
||||
case "trashcan":
|
||||
$scope.currentFolder._guiVersioning.selector = "trashcan";
|
||||
$scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
|
||||
break;
|
||||
case "simple":
|
||||
$scope.currentFolder._guiVersioning.selector = "simple";
|
||||
$scope.currentFolder._guiVersioning.simpleKeep = +currentVersioning.params.keep;
|
||||
$scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
|
||||
break;
|
||||
case "staggered":
|
||||
$scope.currentFolder._guiVersioning.selector = "staggered";
|
||||
$scope.currentFolder._guiVersioning.staggeredMaxAge = Math.floor(+currentVersioning.params.maxAge / 86400);
|
||||
$scope.currentFolder._guiVersioning.staggeredCleanInterval = +currentVersioning.params.cleanInterval;
|
||||
$scope.currentFolder._guiVersioning.staggeredVersionsPath = currentVersioning.params.versionsPath;
|
||||
break;
|
||||
case "external":
|
||||
$scope.currentFolder._guiVersioning.selector = "external";
|
||||
$scope.currentFolder.externalCommand = currentVersioning.params.command;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editFolderExisting = function(folderCfg) {
|
||||
$scope.editingExisting = true;
|
||||
$scope.currentFolder = angular.copy(folderCfg);
|
||||
|
||||
$scope.ignores.text = 'Loading...';
|
||||
$scope.ignores.error = null;
|
||||
@@ -1820,7 +1954,18 @@ angular.module('syncthing.core')
|
||||
$scope.emitHTTPError(err);
|
||||
});
|
||||
|
||||
$scope.editFolderModal();
|
||||
editFolder();
|
||||
};
|
||||
|
||||
$scope.editFolderDefaults = function() {
|
||||
$http.get(urlbase + '/config/defaults/folder')
|
||||
.success(function (data) {
|
||||
$scope.currentFolder = data;
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = true;
|
||||
editFolder();
|
||||
})
|
||||
.error($scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$scope.selectAllSharedDevices = function (state) {
|
||||
@@ -1839,37 +1984,43 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.addFolder = function () {
|
||||
$http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
||||
initShareEditing('folder');
|
||||
$scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
|
||||
$scope.currentSharing.unrelated = $scope.otherDevices();
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
$scope.editFolderModal();
|
||||
var folderID = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
|
||||
addFolderInit(folderID).then(function() {
|
||||
// Triggers the watch that sets the path
|
||||
$scope.currentFolder.label = $scope.currentFolder.label;
|
||||
editFolderModal();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addFolderAndShare = function (folder, folderLabel, device) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.currentFolder = angular.copy($scope.folderDefaults);
|
||||
$scope.currentFolder.id = folder;
|
||||
$scope.currentFolder.label = folderLabel;
|
||||
$scope.currentFolder.viewFlags = {
|
||||
importFromOtherDevice: true
|
||||
};
|
||||
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]
|
||||
$scope.addFolderAndShare = function (folderID, folderLabel, device) {
|
||||
addFolderInit(folderID).then(function() {
|
||||
$scope.currentFolder.viewFlags = {
|
||||
importFromOtherDevice: true
|
||||
};
|
||||
$scope.currentSharing.selected[device] = true;
|
||||
$scope.currentFolder.label = folderLabel;
|
||||
editFolderModal();
|
||||
});
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
$scope.editFolderModal();
|
||||
};
|
||||
|
||||
function addFolderInit(folderID) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = false;
|
||||
return $http.get(urlbase + '/config/defaults/folder').then(function(p) {
|
||||
$scope.currentFolder = p.data;
|
||||
$scope.currentFolder.id = folderID;
|
||||
|
||||
initShareEditing('folder');
|
||||
$scope.currentSharing.unrelated = $scope.currentSharing.unrelated.concat($scope.currentSharing.shared);
|
||||
$scope.currentSharing.shared = [];
|
||||
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
}, $scope.emitHTTPError);
|
||||
}
|
||||
|
||||
$scope.shareFolderWithDevice = function (folder, device) {
|
||||
$scope.folders[folder].devices.push({
|
||||
deviceID: device
|
||||
@@ -1901,55 +2052,60 @@ angular.module('syncthing.core')
|
||||
folderCfg.devices = newDevices;
|
||||
delete $scope.currentSharing;
|
||||
|
||||
if (folderCfg.fileVersioningSelector === "trashcan") {
|
||||
switch (folderCfg._guiVersioning.selector) {
|
||||
case "trashcan":
|
||||
folderCfg.versioning = {
|
||||
'type': 'trashcan',
|
||||
'params': {
|
||||
'cleanoutDays': '' + folderCfg.trashcanClean
|
||||
'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.trashcanFileVersioning;
|
||||
delete folderCfg.trashcanClean;
|
||||
} else if (folderCfg.fileVersioningSelector === "simple") {
|
||||
break;
|
||||
case "simple":
|
||||
folderCfg.versioning = {
|
||||
'type': 'simple',
|
||||
'params': {
|
||||
'keep': '' + folderCfg.simpleKeep,
|
||||
'cleanoutDays': '' + folderCfg.trashcanClean
|
||||
'keep': '' + folderCfg._guiVersioning.simpleKeep,
|
||||
'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.simpleFileVersioning;
|
||||
delete folderCfg.simpleKeep;
|
||||
} else if (folderCfg.fileVersioningSelector === "staggered") {
|
||||
break;
|
||||
case "staggered":
|
||||
folderCfg.versioning = {
|
||||
'type': 'staggered',
|
||||
'params': {
|
||||
'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
|
||||
'cleanInterval': '' + folderCfg.staggeredCleanInterval,
|
||||
'versionsPath': '' + folderCfg.staggeredVersionsPath
|
||||
'maxAge': '' + (folderCfg._guiVersioning.staggeredMaxAge * 86400),
|
||||
'cleanInterval': '' + folderCfg._guiVersioning.staggeredCleanInterval,
|
||||
'versionsPath': '' + folderCfg._guiVersioning.staggeredVersionsPath
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.staggeredFileVersioning;
|
||||
delete folderCfg.staggeredMaxAge;
|
||||
delete folderCfg.staggeredCleanInterval;
|
||||
delete folderCfg.staggeredVersionsPath;
|
||||
} else if (folderCfg.fileVersioningSelector === "external") {
|
||||
break;
|
||||
case "external":
|
||||
folderCfg.versioning = {
|
||||
'type': 'external',
|
||||
'params': {
|
||||
'command': '' + folderCfg.externalCommand
|
||||
'command': '' + folderCfg._guiVersioning.externalCommand
|
||||
},
|
||||
'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
|
||||
'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
|
||||
};
|
||||
delete folderCfg.externalFileVersioning;
|
||||
delete folderCfg.externalCommand;
|
||||
} else {
|
||||
break;
|
||||
default:
|
||||
delete folderCfg.versioning;
|
||||
}
|
||||
delete folderCfg._guiVersioning;
|
||||
|
||||
if ($scope.editingDefaults) {
|
||||
$scope.config.defaults.folder = folderCfg;
|
||||
$scope.saveConfig();
|
||||
} else {
|
||||
saveFolderExisting(folderCfg);
|
||||
}
|
||||
};
|
||||
|
||||
function saveFolderExisting(folderCfg) {
|
||||
var ignoresLoaded = !$scope.ignores.disabled;
|
||||
var ignores = $scope.ignores.text.split('\n');
|
||||
// Split always returns a minimum 1-length array even for no patterns
|
||||
@@ -1963,7 +2119,11 @@ angular.module('syncthing.core')
|
||||
$scope.folders[folderCfg.id] = folderCfg;
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
|
||||
if (ignoresLoaded && $scope.editingExisting && ignores !== folderCfg.ignores) {
|
||||
function arrayEquals(a, b) {
|
||||
return a.length === b.length && a.every(function(v, i) { return v === b[i] });
|
||||
}
|
||||
|
||||
if (ignoresLoaded && $scope.editingExisting && !arrayEquals(ignores, folderCfg.ignores)) {
|
||||
saveIgnores(ignores);
|
||||
};
|
||||
|
||||
@@ -1976,13 +2136,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();
|
||||
}
|
||||
};
|
||||
@@ -2378,6 +2541,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');
|
||||
};
|
||||
|
||||
@@ -2493,12 +2658,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 +2765,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;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<modal id="editDevice" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-desktop'}}" heading="{{editingExisting ? 'Edit Device' : 'Add Device' | translate}} {{currentDevice.name}}" large="yes" closeable="yes">
|
||||
<modal id="editDevice" status="default" icon="{{editDeviceModalIcon()}}" heading="{{editDeviceModalTitle()}}" large="yes" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<form role="form" name="deviceEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<li class="active"><a data-toggle="tab" href="#device-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li ng-if="!editingDefaults"><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li><a data-toggle="tab" href="#device-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div id="device-general" class="tab-pane in active">
|
||||
<div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<label translate for="deviceID">Device ID</label>
|
||||
<div ng-if="!editingExisting">
|
||||
<input name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required="" valid-deviceid list="discovery-list" aria-required="true" />
|
||||
@@ -38,7 +38,7 @@
|
||||
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="device-sharing" class="tab-pane">
|
||||
<div ng-if="!editingDefaults" id="device-sharing" class="tab-pane">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
@@ -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> 
|
||||
<span translate>Deselect folders to stop sharing with this device.</span> 
|
||||
<small><a href="#" ng-click="selectAllSharedFolders(true)" translate>Select All</a> 
|
||||
<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> 
|
||||
<small><a href="#" ng-click="selectAllUnrelatedFolders(true)" translate>Select All</a> 
|
||||
<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>
|
||||
@@ -148,17 +151,14 @@
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid">
|
||||
<span class="fas fa-check"></span> <span translate>Save</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
|
||||
<button ng-if="!editingDefaults" type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
|
||||
<span class="fas fa-qrcode"></span> <span translate>Show QR</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span> <span translate>Close</span>
|
||||
</button>
|
||||
<div ng-if="editingExisting" class="pull-left">
|
||||
<button type="button" class="btn btn-warning btn-sm disabled" ng-if="willBeReintroducedBy" tooltip data-original-title="This device will be reintroduced by {{ willBeReintroducedBy }}">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation" ng-if="!willBeReintroducedBy">
|
||||
<div ng-if="editingExisting && !editingDefaults" class="pull-left">
|
||||
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<modal id="editFolder" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-folder'}}" heading="{{editingExisting ? 'Edit Folder' : 'Add Folder' | translate}} ({{folderLabel(currentFolder.id)}})" large="yes" closeable="yes">
|
||||
<modal id="editFolder" status="default" icon="{{editFolderModalIcon()}}" heading="{{editFolderModalTitle()}}" large="yes" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<form role="form" name="folderEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
|
||||
<li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li ng-if="!editingDefaults"><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
|
||||
<div id="folder-general" class="tab-pane in active">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty && !editingDefaults}">
|
||||
<label for="folderLabel"><span translate>Folder Label</span></label>
|
||||
<input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<label for="folderID"><span translate>Folder ID</span></label>
|
||||
<input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
|
||||
<p class="help-block">
|
||||
@@ -28,19 +28,21 @@
|
||||
<span translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty && !editingDefaults}">
|
||||
<label translate for="folderPath">Folder Path</label>
|
||||
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required="" aria-required="true" path-is-sub-dir />
|
||||
<input name="folderPath" ng-readonly="editingExisting && !editingDefaults" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" ng-required="!editingDefaults" ng-aria-required="!editingDefaults" path-is-sub-dir />
|
||||
<datalist id="directory-list">
|
||||
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
|
||||
</datalist>
|
||||
<p class="help-block">
|
||||
<span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty && !editingDefaults">The folder path cannot be blank.</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span ng-if="folderPathErrors.isParent && !editingDefaults">
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +56,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 +70,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>
|
||||
@@ -76,7 +78,7 @@
|
||||
<div id="folder-versioning" class="tab-pane">
|
||||
<div class="form-group">
|
||||
<label translate>File Versioning</label> <a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fas fa-question-circle"></span> <span translate>Help</span></a>
|
||||
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
|
||||
<select class="form-control" ng-model="currentFolder._guiVersioning.selector">
|
||||
<option value="none" translate>No File Versioning</option>
|
||||
<option value="trashcan" translate>Trash Can File Versioning</option>
|
||||
<option value="simple" translate>Simple File Versioning</option>
|
||||
@@ -84,46 +86,46 @@
|
||||
<option value="external" translate>External File Versioning</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan' || currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
|
||||
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
|
||||
<label translate for="trashcanClean">Clean out after</label>
|
||||
<div class="input-group">
|
||||
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required="" aria-required="true" min="0" />
|
||||
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.trashcanClean" required="" aria-required="true" min="0" />
|
||||
<div class="input-group-addon" translate>days</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
|
||||
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
|
||||
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
|
||||
<label translate for="simpleKeep">Keep Versions</label>
|
||||
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required="" aria-required="true" min="1" />
|
||||
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
|
||||
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
|
||||
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
|
||||
<p translate class="help-block">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.</p>
|
||||
<label translate for="staggeredMaxAge">Maximum Age</label>
|
||||
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required="" aria-required="true" min="0" />
|
||||
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
|
||||
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector == 'staggered'">
|
||||
<label translate for="staggeredVersionsPath">Versions Path</label>
|
||||
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath" />
|
||||
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder._guiVersioning.staggeredVersionsPath" />
|
||||
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
|
||||
<p translate class="help-block">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.</p>
|
||||
<label translate for="externalCommand">Command</label>
|
||||
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required="" aria-required="true" />
|
||||
@@ -132,21 +134,21 @@
|
||||
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector != 'none'" ng-class="{'has-error': folderEditor.versioningCleanupIntervalS.$invalid && folderEditor.versioningCleanupIntervalS.$dirty}">
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector != 'none'" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
|
||||
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
|
||||
<div class="input-group">
|
||||
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder.versioningCleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
|
||||
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
|
||||
<div class="input-group-addon" translate>seconds</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$valid || folderEditor.versioningCleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
|
||||
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.required && folderEditor.versioningCleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.min && folderEditor.versioningCleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="folder-ignores" class="tab-pane">
|
||||
<div ng-if="!editingDefaults" id="folder-ignores" class="tab-pane">
|
||||
<p translate>Enter ignore patterns, one per line.</p>
|
||||
<div ng-class="{'has-error': ignores.error != null}">
|
||||
<textarea class="form-control" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
|
||||
@@ -271,7 +273,7 @@
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span> <span translate>Close</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting">
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting && !editingDefaults">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+101
-61
@@ -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]
|
||||
@@ -292,7 +294,6 @@ func (s *service) Serve(ctx context.Context) error {
|
||||
Router: restMux,
|
||||
id: s.id,
|
||||
cfg: s.cfg,
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
|
||||
configBuilder.registerConfig("/rest/config")
|
||||
@@ -301,6 +302,8 @@ func (s *service) Serve(ctx context.Context) error {
|
||||
configBuilder.registerDevices("/rest/config/devices")
|
||||
configBuilder.registerFolder("/rest/config/folders/:id")
|
||||
configBuilder.registerDevice("/rest/config/devices/:id")
|
||||
configBuilder.registerDefaultFolder("/rest/config/defaults/folder")
|
||||
configBuilder.registerDefaultDevice("/rest/config/defaults/device")
|
||||
configBuilder.registerOptions("/rest/config/options")
|
||||
configBuilder.registerLDAP("/rest/config/ldap")
|
||||
configBuilder.registerGUI("/rest/config/gui")
|
||||
@@ -472,7 +475,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 +623,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 +673,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,
|
||||
})
|
||||
@@ -682,14 +712,19 @@ func (s *service) getDBBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
prefix := qs.Get("prefix")
|
||||
dirsonly := qs.Get("dirsonly") != ""
|
||||
dirsOnly := qs.Get("dirsonly") != ""
|
||||
|
||||
levels, err := strconv.Atoi(qs.Get("levels"))
|
||||
if err != nil {
|
||||
levels = -1
|
||||
}
|
||||
result, err := s.model.GlobalDirectoryTree(folder, prefix, levels, dirsOnly)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sendJSON(w, s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly))
|
||||
sendJSON(w, result)
|
||||
}
|
||||
|
||||
func (s *service) getDBCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -886,9 +921,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 +950,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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1189,7 +1224,7 @@ func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
folder := qs.Get("folder")
|
||||
|
||||
lines, patterns, err := s.model.GetIgnores(folder)
|
||||
lines, patterns, err := s.model.LoadIgnores(folder)
|
||||
if err != nil && !ignore.IsParseError(err) {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
@@ -1361,9 +1396,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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1373,31 +1408,36 @@ func (s *service) makeDevicePauseHandler(paused bool) http.HandlerFunc {
|
||||
var qs = r.URL.Query()
|
||||
var deviceStr = qs.Get("device")
|
||||
|
||||
var cfgs []config.DeviceConfiguration
|
||||
|
||||
if deviceStr == "" {
|
||||
for _, cfg := range s.cfg.Devices() {
|
||||
cfg.Paused = paused
|
||||
cfgs = append(cfgs, cfg)
|
||||
var msg string
|
||||
var status int
|
||||
_, err := s.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if deviceStr == "" {
|
||||
for i := range cfg.Devices {
|
||||
cfg.Devices[i].Paused = paused
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
device, err := protocol.DeviceIDFromString(deviceStr)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
msg = err.Error()
|
||||
status = 500
|
||||
return
|
||||
}
|
||||
|
||||
cfg, ok := s.cfg.Devices()[device]
|
||||
_, i, ok := cfg.Device(device)
|
||||
if !ok {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
msg = "not found"
|
||||
status = http.StatusNotFound
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Paused = paused
|
||||
cfgs = append(cfgs, cfg)
|
||||
}
|
||||
cfg.Devices[i].Paused = paused
|
||||
})
|
||||
|
||||
if _, err := s.cfg.SetDevices(cfgs); err != nil {
|
||||
if msg != "" {
|
||||
http.Error(w, msg, status)
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -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)
|
||||
@@ -1253,6 +1253,11 @@ func TestConfigChanges(t *testing.T) {
|
||||
defer os.Remove(tmpFile.Name())
|
||||
w := config.Wrap(tmpFile.Name(), cfg, protocol.LocalDeviceID, events.NoopLogger)
|
||||
tmpFile.Close()
|
||||
if cfgService, ok := w.(suture.Service); ok {
|
||||
cfgCtx, cfgCancel := context.WithCancel(context.Background())
|
||||
go cfgService.Serve(cfgCtx)
|
||||
defer cfgCancel()
|
||||
}
|
||||
baseURL, cancel, err := startHTTP(w)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error from getting base URL:", err)
|
||||
@@ -1260,7 +1265,7 @@ func TestConfigChanges(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
cli := &http.Client{
|
||||
Timeout: time.Second,
|
||||
Timeout: time.Minute,
|
||||
}
|
||||
|
||||
do := func(req *http.Request, status int) *http.Response {
|
||||
|
||||
+93
-65
@@ -17,14 +17,12 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
type configMuxBuilder struct {
|
||||
*httprouter.Router
|
||||
id protocol.DeviceID
|
||||
cfg config.Wrapper
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) registerConfig(path string) {
|
||||
@@ -59,14 +57,14 @@ func (c *configMuxBuilder) registerFolders(path string) {
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
var folders []config.FolderConfiguration
|
||||
if err := unmarshalTo(r.Body, &folders); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetFolders(folders)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetFolders(folders)
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -75,19 +73,7 @@ func (c *configMuxBuilder) registerFolders(path string) {
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
var folder config.FolderConfiguration
|
||||
if err := unmarshalTo(r.Body, &folder); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetFolder(folder)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c.finish(w, waiter)
|
||||
c.adjustFolder(w, r, config.FolderConfiguration{}, false)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,14 +83,14 @@ func (c *configMuxBuilder) registerDevices(path string) {
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
var devices []config.DeviceConfiguration
|
||||
if err := unmarshalTo(r.Body, &devices); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetDevices(devices)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetDevices(devices)
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -113,14 +99,14 @@ func (c *configMuxBuilder) registerDevices(path string) {
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
var device config.DeviceConfiguration
|
||||
if err := unmarshalTo(r.Body, &device); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetDevice(device)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetDevice(device)
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -140,7 +126,7 @@ func (c *configMuxBuilder) registerFolder(path string) {
|
||||
})
|
||||
|
||||
c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
c.adjustFolder(w, r, config.FolderConfiguration{})
|
||||
c.adjustFolder(w, r, config.FolderConfiguration{}, false)
|
||||
})
|
||||
|
||||
c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
@@ -149,12 +135,10 @@ func (c *configMuxBuilder) registerFolder(path string) {
|
||||
http.Error(w, "No folder with given ID", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
c.adjustFolder(w, r, folder)
|
||||
c.adjustFolder(w, r, folder, false)
|
||||
})
|
||||
|
||||
c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
waiter, err := c.cfg.RemoveFolder(p.ByName("id"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
@@ -186,18 +170,16 @@ func (c *configMuxBuilder) registerDevice(path string) {
|
||||
})
|
||||
|
||||
c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
c.adjustDevice(w, r, config.DeviceConfiguration{})
|
||||
c.adjustDevice(w, r, config.DeviceConfiguration{}, false)
|
||||
})
|
||||
|
||||
c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
if device, ok := deviceFromParams(w, p); ok {
|
||||
c.adjustDevice(w, r, device)
|
||||
c.adjustDevice(w, r, device, false)
|
||||
}
|
||||
})
|
||||
|
||||
c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
id, err := protocol.DeviceIDFromString(p.ByName("id"))
|
||||
waiter, err := c.cfg.RemoveDevice(id)
|
||||
if err != nil {
|
||||
@@ -208,6 +190,34 @@ func (c *configMuxBuilder) registerDevice(path string) {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) registerDefaultFolder(path string) {
|
||||
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
|
||||
sendJSON(w, c.cfg.DefaultFolder())
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.adjustFolder(w, r, config.FolderConfiguration{}, true)
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.adjustFolder(w, r, c.cfg.DefaultFolder(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) registerDefaultDevice(path string) {
|
||||
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
|
||||
sendJSON(w, c.cfg.DefaultDevice())
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.adjustDevice(w, r, config.DeviceConfiguration{}, true)
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
c.adjustDevice(w, r, c.cfg.DefaultDevice(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) registerOptions(path string) {
|
||||
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
|
||||
sendJSON(w, c.cfg.Options())
|
||||
@@ -251,36 +261,45 @@ func (c *configMuxBuilder) registerGUI(path string) {
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
cfg, err := config.ReadJSON(r.Body, c.id)
|
||||
to, err := config.ReadJSON(r.Body, c.id)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
l.Warnln("Decoding posted config:", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if cfg.GUI.Password, err = checkGUIPassword(c.cfg.GUI().Password, cfg.GUI.Password); err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.Replace(cfg)
|
||||
if err != nil {
|
||||
var errMsg string
|
||||
var status int
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if to.GUI.Password, err = checkGUIPassword(cfg.GUI.Password, to.GUI.Password); err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
errMsg = err.Error()
|
||||
status = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
*cfg = to
|
||||
})
|
||||
if errMsg != "" {
|
||||
http.Error(w, errMsg, status)
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c.finish(w, waiter)
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request, folder config.FolderConfiguration) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request, folder config.FolderConfiguration, defaults bool) {
|
||||
if err := unmarshalTo(r.Body, &folder); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetFolder(folder)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if defaults {
|
||||
cfg.Defaults.Folder = folder
|
||||
} else {
|
||||
cfg.SetFolder(folder)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -288,14 +307,18 @@ func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request,
|
||||
c.finish(w, waiter)
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request, device config.DeviceConfiguration) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request, device config.DeviceConfiguration, defaults bool) {
|
||||
if err := unmarshalTo(r.Body, &device); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetDevice(device)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if defaults {
|
||||
cfg.Defaults.Device = device
|
||||
} else {
|
||||
cfg.SetDevice(device)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -304,13 +327,13 @@ func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request,
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) adjustOptions(w http.ResponseWriter, r *http.Request, opts config.OptionsConfiguration) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
if err := unmarshalTo(r.Body, &opts); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetOptions(opts)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
cfg.Options = opts
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -319,21 +342,26 @@ func (c *configMuxBuilder) adjustOptions(w http.ResponseWriter, r *http.Request,
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui config.GUIConfiguration) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
oldPassword := gui.Password
|
||||
err := unmarshalTo(r.Body, &gui)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if gui.Password, err = checkGUIPassword(oldPassword, gui.Password); err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetGUI(gui)
|
||||
if err != nil {
|
||||
var errMsg string
|
||||
var status int
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if gui.Password, err = checkGUIPassword(oldPassword, gui.Password); err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
errMsg = err.Error()
|
||||
status = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
cfg.GUI = gui
|
||||
})
|
||||
if errMsg != "" {
|
||||
http.Error(w, errMsg, status)
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -341,13 +369,13 @@ func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) adjustLDAP(w http.ResponseWriter, r *http.Request, ldap config.LDAPConfiguration) {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
if err := unmarshalTo(r.Body, &ldap); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.SetLDAP(ldap)
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
cfg.LDAP = ldap
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -28,10 +28,6 @@ func (c *mockedConfig) LDAP() config.LDAPConfiguration {
|
||||
return config.LDAPConfiguration{}
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetLDAP(config.LDAPConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) RawCopy() config.Configuration {
|
||||
cfg := config.Configuration{}
|
||||
util.SetDefaults(&cfg.Options)
|
||||
@@ -42,11 +38,13 @@ func (c *mockedConfig) Options() config.OptionsConfiguration {
|
||||
return config.OptionsConfiguration{}
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Replace(cfg config.Configuration) (config.Waiter, error) {
|
||||
func (c *mockedConfig) Modify(config.ModifyFunction) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Subscribe(cm config.Committer) {}
|
||||
func (c *mockedConfig) Subscribe(cm config.Committer) config.Configuration {
|
||||
return config.Configuration{}
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Unsubscribe(cm config.Committer) {}
|
||||
|
||||
@@ -62,14 +60,6 @@ func (c *mockedConfig) DeviceList() []config.DeviceConfiguration {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetDevice(config.DeviceConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetDevices([]config.DeviceConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Save() error {
|
||||
return nil
|
||||
}
|
||||
@@ -86,14 +76,6 @@ func (c *mockedConfig) ConfigPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetGUI(gui config.GUIConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetOptions(opts config.OptionsConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
|
||||
return config.FolderConfiguration{}, false
|
||||
}
|
||||
@@ -102,14 +84,6 @@ func (c *mockedConfig) FolderList() []config.FolderConfiguration {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetFolder(fld config.FolderConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetFolders(folders []config.FolderConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) RemoveFolder(id string) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
@@ -117,7 +91,6 @@ func (c *mockedConfig) RemoveFolder(id string) (config.Waiter, error) {
|
||||
func (c *mockedConfig) FolderPasswords(device protocol.DeviceID) map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
|
||||
return config.DeviceConfiguration{}, false
|
||||
}
|
||||
@@ -130,10 +103,30 @@ 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
|
||||
}
|
||||
|
||||
func (c *mockedConfig) DefaultFolder() config.FolderConfiguration {
|
||||
return config.FolderConfiguration{}
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetDefaultFolder(config.FolderConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) DefaultDevice() config.DeviceConfiguration {
|
||||
return config.DeviceConfiguration{}
|
||||
}
|
||||
|
||||
func (c *mockedConfig) SetDefaultDevice(config.DeviceConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) GlobalDiscoveryServers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -22,8 +21,8 @@ import (
|
||||
|
||||
type mockedModel struct{}
|
||||
|
||||
func (m *mockedModel) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} {
|
||||
return nil
|
||||
func (m *mockedModel) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly bool) ([]*model.TreeEntry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) Completion(device protocol.DeviceID, folder string) model.FolderCompletion {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -77,7 +80,11 @@ func (m *mockedModel) Availability(folder string, file protocol.FileInfo, block
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) GetIgnores(folder string) ([]string, []string, error) {
|
||||
func (m *mockedModel) LoadIgnores(folder string) ([]string, []string, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) CurrentIgnores(folder string) ([]string, []string, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +121,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 +132,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 +172,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
|
||||
|
||||
+8
-11
@@ -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 {
|
||||
|
||||
+10
-5
@@ -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
|
||||
}
|
||||
|
||||
+17
-12
@@ -41,10 +41,20 @@ func (validationError) String() string {
|
||||
return "validationError"
|
||||
}
|
||||
|
||||
func TestReplaceCommit(t *testing.T) {
|
||||
t.Skip("broken, fails randomly, #3834")
|
||||
func replace(t testing.TB, w Wrapper, to Configuration) {
|
||||
t.Helper()
|
||||
waiter, err := w.Modify(func(cfg *Configuration) {
|
||||
*cfg = to
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waiter.Wait()
|
||||
}
|
||||
|
||||
func TestReplaceCommit(t *testing.T) {
|
||||
w := wrap("/dev/null", Configuration{Version: 0}, device1)
|
||||
defer w.stop()
|
||||
if w.RawCopy().Version != 0 {
|
||||
t.Fatal("Config incorrect")
|
||||
}
|
||||
@@ -52,10 +62,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
// Replace config. We should get back a clean response and the config
|
||||
// should change.
|
||||
|
||||
_, err := w.Replace(Configuration{Version: 1})
|
||||
if err != nil {
|
||||
t.Fatal("Should not have a validation error:", err)
|
||||
}
|
||||
replace(t, w, Configuration{Version: 1})
|
||||
if w.RequiresRestart() {
|
||||
t.Fatal("Should not require restart")
|
||||
}
|
||||
@@ -69,11 +76,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
sub0 := requiresRestart{committed: make(chan struct{}, 1)}
|
||||
w.Subscribe(sub0)
|
||||
|
||||
_, err = w.Replace(Configuration{Version: 2})
|
||||
if err != nil {
|
||||
t.Fatal("Should not have a validation error:", err)
|
||||
}
|
||||
|
||||
replace(t, w, Configuration{Version: 1})
|
||||
<-sub0.committed
|
||||
if !w.RequiresRestart() {
|
||||
t.Fatal("Should require restart")
|
||||
@@ -87,7 +90,9 @@ func TestReplaceCommit(t *testing.T) {
|
||||
|
||||
w.Subscribe(validationError{})
|
||||
|
||||
_, err = w.Replace(Configuration{Version: 3})
|
||||
_, err := w.Modify(func(cfg *Configuration) {
|
||||
*cfg = Configuration{Version: 3}
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should have a validation error")
|
||||
}
|
||||
|
||||
+86
-32
@@ -30,7 +30,7 @@ import (
|
||||
|
||||
const (
|
||||
OldestHandledVersion = 10
|
||||
CurrentVersion = 32
|
||||
CurrentVersion = 34
|
||||
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,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
|
||||
guiPWIsSet := cfg.GUI.User != "" && cfg.GUI.Password != ""
|
||||
cfg.Options.prepare(guiPWIsSet)
|
||||
|
||||
ignoredDevices := cfg.prepareIgnoredDevices(existingDevices)
|
||||
cfg.prepareIgnoredDevices(existingDevices)
|
||||
|
||||
cfg.preparePendingDevices(existingDevices, ignoredDevices)
|
||||
cfg.Defaults.prepare(myID, existingDevices)
|
||||
|
||||
cfg.removeDeprecatedProtocols()
|
||||
|
||||
@@ -250,7 +247,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
|
||||
}
|
||||
|
||||
func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) {
|
||||
// Ensure this device is present in the config
|
||||
for _, device := range cfg.Devices {
|
||||
if device.DeviceID == myID {
|
||||
return
|
||||
@@ -354,31 +350,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*.
|
||||
@@ -402,6 +373,15 @@ func (cfg *Configuration) applyMigrations() {
|
||||
migrationsMut.Unlock()
|
||||
}
|
||||
|
||||
func (cfg *Configuration) Device(id protocol.DeviceID) (DeviceConfiguration, int, bool) {
|
||||
for i, device := range cfg.Devices {
|
||||
if device.DeviceID == id {
|
||||
return device, i, true
|
||||
}
|
||||
}
|
||||
return DeviceConfiguration{}, 0, false
|
||||
}
|
||||
|
||||
// DeviceMap returns a map of device ID to device configuration for the given configuration.
|
||||
func (cfg *Configuration) DeviceMap() map[protocol.DeviceID]DeviceConfiguration {
|
||||
m := make(map[protocol.DeviceID]DeviceConfiguration, len(cfg.Devices))
|
||||
@@ -411,6 +391,44 @@ func (cfg *Configuration) DeviceMap() map[protocol.DeviceID]DeviceConfiguration
|
||||
return m
|
||||
}
|
||||
|
||||
func (cfg *Configuration) SetDevice(device DeviceConfiguration) {
|
||||
cfg.SetDevices([]DeviceConfiguration{device})
|
||||
}
|
||||
|
||||
func (cfg *Configuration) SetDevices(devices []DeviceConfiguration) {
|
||||
inds := make(map[protocol.DeviceID]int, len(cfg.Devices))
|
||||
for i, device := range cfg.Devices {
|
||||
inds[device.DeviceID] = i
|
||||
}
|
||||
filtered := devices[:0]
|
||||
for _, device := range devices {
|
||||
if i, ok := inds[device.DeviceID]; ok {
|
||||
cfg.Devices[i] = device
|
||||
} else {
|
||||
filtered = append(filtered, device)
|
||||
}
|
||||
}
|
||||
cfg.Devices = append(cfg.Devices, filtered...)
|
||||
}
|
||||
|
||||
func (cfg *Configuration) Folder(id string) (FolderConfiguration, int, bool) {
|
||||
for i, folder := range cfg.Folders {
|
||||
if folder.ID == id {
|
||||
return folder, i, true
|
||||
}
|
||||
}
|
||||
return FolderConfiguration{}, 0, false
|
||||
}
|
||||
|
||||
// FolderMap returns a map of folder ID to folder configuration for the given configuration.
|
||||
func (cfg *Configuration) FolderMap() map[string]FolderConfiguration {
|
||||
m := make(map[string]FolderConfiguration, len(cfg.Folders))
|
||||
for _, folder := range cfg.Folders {
|
||||
m[folder.ID] = folder
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FolderPasswords returns the folder passwords set for this device, for
|
||||
// folders that have an encryption password set.
|
||||
func (cfg Configuration) FolderPasswords(device protocol.DeviceID) map[string]string {
|
||||
@@ -427,6 +445,26 @@ nextFolder:
|
||||
return res
|
||||
}
|
||||
|
||||
func (cfg *Configuration) SetFolder(folder FolderConfiguration) {
|
||||
cfg.SetFolders([]FolderConfiguration{folder})
|
||||
}
|
||||
|
||||
func (cfg *Configuration) SetFolders(folders []FolderConfiguration) {
|
||||
inds := make(map[string]int, len(cfg.Folders))
|
||||
for i, folder := range cfg.Folders {
|
||||
inds[folder.ID] = i
|
||||
}
|
||||
filtered := folders[:0]
|
||||
for _, folder := range folders {
|
||||
if i, ok := inds[folder.ID]; ok {
|
||||
cfg.Folders[i] = folder
|
||||
} else {
|
||||
filtered = append(filtered, folder)
|
||||
}
|
||||
}
|
||||
cfg.Folders = append(cfg.Folders, filtered...)
|
||||
}
|
||||
|
||||
func ensureDevicePresent(devices []FolderDeviceConfiguration, myID protocol.DeviceID) []FolderDeviceConfiguration {
|
||||
for _, device := range devices {
|
||||
if device.DeviceID.Equals(myID) {
|
||||
@@ -549,3 +587,19 @@ func getFreePort(host string, ports ...int) (int, error) {
|
||||
c.Close()
|
||||
return addr.Port, nil
|
||||
}
|
||||
|
||||
func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
|
||||
ensureZeroForNodefault(&FolderConfiguration{}, &defaults.Folder)
|
||||
ensureZeroForNodefault(&DeviceConfiguration{}, &defaults.Device)
|
||||
defaults.Folder.prepare(myID, existingDevices)
|
||||
defaults.Device.prepare(nil)
|
||||
}
|
||||
|
||||
func ensureZeroForNodefault(empty interface{}, target interface{}) {
|
||||
util.CopyMatchingTag(empty, target, "nodefault", func(v string) bool {
|
||||
if len(v) > 0 && v != "true" {
|
||||
panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
|
||||
}
|
||||
return len(v) > 0
|
||||
})
|
||||
}
|
||||
|
||||
+318
-52
@@ -24,14 +24,15 @@ 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.
|
||||
Defaults Defaults `protobuf:"bytes,9,opt,name=defaults,proto3" json:"defaults" xml:"defaults"`
|
||||
}
|
||||
|
||||
func (m *Configuration) Reset() { *m = Configuration{} }
|
||||
@@ -67,49 +68,94 @@ func (m *Configuration) XXX_DiscardUnknown() {
|
||||
|
||||
var xxx_messageInfo_Configuration proto.InternalMessageInfo
|
||||
|
||||
type Defaults struct {
|
||||
Folder FolderConfiguration `protobuf:"bytes,1,opt,name=folder,proto3" json:"folder" xml:"folder"`
|
||||
Device DeviceConfiguration `protobuf:"bytes,2,opt,name=device,proto3" json:"device" xml:"device"`
|
||||
}
|
||||
|
||||
func (m *Defaults) Reset() { *m = Defaults{} }
|
||||
func (m *Defaults) String() string { return proto.CompactTextString(m) }
|
||||
func (*Defaults) ProtoMessage() {}
|
||||
func (*Defaults) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_baadf209193dc627, []int{1}
|
||||
}
|
||||
func (m *Defaults) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *Defaults) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_Defaults.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 *Defaults) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Defaults.Merge(m, src)
|
||||
}
|
||||
func (m *Defaults) XXX_Size() int {
|
||||
return m.ProtoSize()
|
||||
}
|
||||
func (m *Defaults) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Defaults.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Defaults proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Configuration)(nil), "config.Configuration")
|
||||
proto.RegisterType((*Defaults)(nil), "config.Defaults")
|
||||
}
|
||||
|
||||
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,
|
||||
// 654 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xcd, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0xc7, 0xed, 0xa6, 0x4d, 0xda, 0xed, 0x17, 0x32, 0x08, 0x5c, 0x3e, 0xbc, 0x61, 0x15, 0x50,
|
||||
0x41, 0xa5, 0x95, 0xca, 0x05, 0x71, 0x23, 0x44, 0x94, 0x0a, 0x24, 0x2a, 0xa3, 0x22, 0xe0, 0x82,
|
||||
0x92, 0x78, 0xeb, 0xae, 0x94, 0xd8, 0x96, 0xbd, 0xae, 0xda, 0x47, 0xe0, 0x86, 0x78, 0x02, 0x4e,
|
||||
0x48, 0xdc, 0x79, 0x88, 0xdc, 0x92, 0x23, 0xa7, 0x95, 0x9a, 0xdc, 0x7c, 0xf4, 0x91, 0x13, 0xda,
|
||||
0x0f, 0xbb, 0xb6, 0x6a, 0xe0, 0x64, 0xcf, 0xfc, 0xff, 0xf3, 0x9b, 0xd5, 0x78, 0xc7, 0xe0, 0xc6,
|
||||
0x80, 0xf4, 0x76, 0xfa, 0xbe, 0x77, 0x44, 0x5c, 0xf5, 0xd8, 0x0e, 0x42, 0x9f, 0xfa, 0x46, 0x5d,
|
||||
0x46, 0x37, 0x5b, 0x05, 0xc3, 0x91, 0x3f, 0x70, 0x70, 0x28, 0x83, 0x38, 0xec, 0x52, 0xe2, 0x7b,
|
||||
0xd2, 0x5d, 0x72, 0x39, 0xf8, 0x84, 0xf4, 0x71, 0x95, 0xeb, 0x6e, 0xc1, 0xe5, 0xc6, 0xa4, 0xca,
|
||||
0x82, 0x0a, 0x96, 0x81, 0xd3, 0x0d, 0xaa, 0x3c, 0xf7, 0x0a, 0x1e, 0x3f, 0xe0, 0x42, 0x54, 0x65,
|
||||
0xdb, 0x28, 0xda, 0x7a, 0x11, 0x0e, 0x4f, 0xb0, 0xa3, 0xa4, 0x25, 0x7c, 0x4a, 0xe5, 0x2b, 0xfa,
|
||||
0xde, 0x00, 0xab, 0xcf, 0x8b, 0xd5, 0x86, 0x0d, 0x1a, 0x27, 0x38, 0x8c, 0x88, 0xef, 0x99, 0x7a,
|
||||
0x53, 0xdf, 0x5c, 0x68, 0x3f, 0x49, 0x18, 0xcc, 0x52, 0x29, 0x83, 0xc6, 0xe9, 0x70, 0xf0, 0x14,
|
||||
0xa9, 0x78, 0xab, 0x4b, 0x69, 0x88, 0x7e, 0x33, 0x58, 0x23, 0x1e, 0x4d, 0xc6, 0xad, 0x95, 0x62,
|
||||
0xde, 0xce, 0xaa, 0x8c, 0x77, 0xa0, 0x21, 0x87, 0x17, 0x99, 0x73, 0xcd, 0xda, 0xe6, 0xf2, 0xee,
|
||||
0xad, 0x6d, 0x35, 0xed, 0x17, 0x22, 0x5d, 0x3a, 0x41, 0x1b, 0x8e, 0x18, 0xd4, 0x78, 0x53, 0x55,
|
||||
0x93, 0x32, 0xb8, 0x22, 0x9a, 0xca, 0x18, 0xd9, 0x99, 0xc0, 0xb9, 0x72, 0xdc, 0x91, 0x59, 0x2b,
|
||||
0x73, 0x3b, 0x22, 0xfd, 0x17, 0xae, 0xaa, 0xc9, 0xb9, 0x32, 0x46, 0x76, 0x26, 0x18, 0x36, 0xa8,
|
||||
0xb9, 0x31, 0x31, 0xe7, 0x9b, 0xfa, 0xe6, 0xf2, 0xae, 0x99, 0x31, 0xf7, 0x0e, 0xf7, 0xcb, 0xc0,
|
||||
0xfb, 0x1c, 0x38, 0x65, 0xb0, 0xb6, 0x77, 0xb8, 0x9f, 0x30, 0xc8, 0x6b, 0x52, 0x06, 0x97, 0x04,
|
||||
0xd3, 0x8d, 0x09, 0xfa, 0x3a, 0x69, 0x71, 0xc9, 0xe6, 0x82, 0xf1, 0x01, 0xcc, 0xf3, 0x2f, 0x6a,
|
||||
0x2e, 0x08, 0xe8, 0x46, 0x06, 0x7d, 0xdd, 0x79, 0x76, 0x50, 0xa6, 0x3e, 0x54, 0xd4, 0x79, 0x2e,
|
||||
0x25, 0x0c, 0x8a, 0xb2, 0x94, 0x41, 0x20, 0xb8, 0x3c, 0xe0, 0x60, 0xa1, 0xda, 0x42, 0x33, 0xde,
|
||||
0x83, 0x86, 0xba, 0x08, 0x66, 0x5d, 0xd0, 0x6f, 0x67, 0xf4, 0x37, 0x32, 0x5d, 0x6e, 0xd0, 0xcc,
|
||||
0xe6, 0xa0, 0x8a, 0x52, 0x06, 0x57, 0x05, 0x5b, 0xc5, 0xc8, 0xce, 0x14, 0xe3, 0x87, 0x0e, 0xd6,
|
||||
0x89, 0xeb, 0xf9, 0x21, 0x76, 0x3e, 0x65, 0x93, 0x6e, 0x88, 0x49, 0x5f, 0xcf, 0x5b, 0xa8, 0xbb,
|
||||
0x25, 0x27, 0xde, 0x3e, 0x56, 0xf0, 0x6b, 0x21, 0x1e, 0xfa, 0x14, 0xef, 0xcb, 0xe2, 0x4e, 0x3e,
|
||||
0xf1, 0x0d, 0xd1, 0xa9, 0x42, 0x44, 0xc9, 0xb8, 0x75, 0xb5, 0x22, 0x9f, 0x8e, 0x5b, 0x95, 0x2c,
|
||||
0x7b, 0x8d, 0x94, 0x62, 0xe3, 0xb3, 0x0e, 0xd6, 0x03, 0xec, 0x39, 0xc4, 0x73, 0xf3, 0xb3, 0x2e,
|
||||
0xfe, 0xf3, 0xac, 0x2f, 0xd5, 0xa4, 0xcd, 0x0e, 0x0e, 0x42, 0xdc, 0xef, 0x52, 0xec, 0x1c, 0x48,
|
||||
0x80, 0x62, 0x26, 0x0c, 0xea, 0x8f, 0x52, 0x06, 0xef, 0x88, 0x43, 0x07, 0x45, 0x6d, 0xcb, 0x1f,
|
||||
0x12, 0x8a, 0x87, 0x01, 0x3d, 0x43, 0xa6, 0x6e, 0xaf, 0x95, 0xb4, 0xc8, 0x38, 0x00, 0x8b, 0x0e,
|
||||
0x3e, 0xea, 0xc6, 0x03, 0x1a, 0x99, 0x4b, 0xe2, 0x93, 0x5c, 0xb9, 0xb8, 0x99, 0x32, 0xdf, 0x46,
|
||||
0x6a, 0x52, 0xb9, 0x33, 0x65, 0x70, 0x4d, 0xdd, 0x47, 0x99, 0x40, 0x76, 0xae, 0xa1, 0x9f, 0x3a,
|
||||
0x58, 0xcc, 0x4a, 0x8d, 0xb7, 0xa0, 0x2e, 0x57, 0x40, 0xac, 0xe8, 0x7f, 0xd6, 0xc9, 0x52, 0x7d,
|
||||
0x54, 0xc9, 0xa5, 0x6d, 0x52, 0x79, 0x0e, 0x95, 0x63, 0x33, 0xe7, 0xca, 0xd0, 0xaa, 0x5d, 0xca,
|
||||
0xa1, 0xb2, 0xe4, 0xd2, 0x2a, 0xa9, 0x7c, 0xfb, 0xd5, 0xe8, 0xdc, 0xd2, 0x26, 0xe7, 0x96, 0x36,
|
||||
0x9a, 0x5a, 0xfa, 0x64, 0x6a, 0xe9, 0x5f, 0x66, 0x96, 0xf6, 0x6d, 0x66, 0xe9, 0x93, 0x99, 0xa5,
|
||||
0xfd, 0x9a, 0x59, 0xda, 0xc7, 0x07, 0x2e, 0xa1, 0xc7, 0x71, 0x6f, 0xbb, 0xef, 0x0f, 0x77, 0xa2,
|
||||
0x33, 0xaf, 0x4f, 0x8f, 0x89, 0xe7, 0x16, 0xde, 0x2e, 0x7e, 0x63, 0xbd, 0xba, 0xf8, 0x67, 0x3d,
|
||||
0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x50, 0xe9, 0xd5, 0x50, 0xb6, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *Configuration) Marshal() (dAtA []byte, err error) {
|
||||
@@ -132,10 +178,20 @@ 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-- {
|
||||
{
|
||||
size, err := m.Defaults.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintConfig(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x4a
|
||||
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
|
||||
}
|
||||
@@ -226,6 +282,49 @@ func (m *Configuration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *Defaults) 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 *Defaults) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.ProtoSize()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Defaults) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size, err := m.Device.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintConfig(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
{
|
||||
size, err := m.Folder.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintConfig(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintConfig(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovConfig(v)
|
||||
base := offset
|
||||
@@ -270,12 +369,27 @@ 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))
|
||||
}
|
||||
}
|
||||
l = m.Defaults.ProtoSize()
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Defaults) ProtoSize() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = m.Folder.ProtoSize()
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
l = m.Device.ProtoSize()
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -536,7 +650,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 +677,160 @@ 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
|
||||
case 9:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Defaults", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.Defaults.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipConfig(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Defaults) 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 ErrIntOverflowConfig
|
||||
}
|
||||
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: Defaults: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Defaults: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Folder", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.Folder.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.Device.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
|
||||
+232
-98
@@ -8,9 +8,11 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/d4l3k/messagediff"
|
||||
"github.com/thejerf/suture/v4"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
@@ -37,52 +40,93 @@ func init() {
|
||||
}
|
||||
|
||||
func TestDefaultValues(t *testing.T) {
|
||||
expected := OptionsConfiguration{
|
||||
RawListenAddresses: []string{"default"},
|
||||
RawGlobalAnnServers: []string{"default"},
|
||||
GlobalAnnEnabled: true,
|
||||
LocalAnnEnabled: true,
|
||||
LocalAnnPort: 21027,
|
||||
LocalAnnMCAddr: "[ff12::8384]:21027",
|
||||
MaxSendKbps: 0,
|
||||
MaxRecvKbps: 0,
|
||||
ReconnectIntervalS: 60,
|
||||
RelaysEnabled: true,
|
||||
RelayReconnectIntervalM: 10,
|
||||
StartBrowser: true,
|
||||
NATEnabled: true,
|
||||
NATLeaseM: 60,
|
||||
NATRenewalM: 30,
|
||||
NATTimeoutS: 10,
|
||||
RestartOnWakeup: true,
|
||||
AutoUpgradeIntervalH: 12,
|
||||
KeepTemporariesH: 24,
|
||||
CacheIgnoredFiles: false,
|
||||
ProgressUpdateIntervalS: 5,
|
||||
LimitBandwidthInLan: false,
|
||||
MinHomeDiskFree: Size{1, "%"},
|
||||
URURL: "https://data.syncthing.net/newdata",
|
||||
URInitialDelayS: 1800,
|
||||
URPostInsecurely: false,
|
||||
ReleasesURL: "https://upgrades.syncthing.net/meta.json",
|
||||
AlwaysLocalNets: []string{},
|
||||
OverwriteRemoteDevNames: false,
|
||||
TempIndexMinBlocks: 10,
|
||||
UnackedNotificationIDs: []string{"authenticationUserAndPassword"},
|
||||
DefaultFolderPath: "~",
|
||||
SetLowPriority: true,
|
||||
CRURL: "https://crash.syncthing.net/newcrash",
|
||||
CREnabled: true,
|
||||
StunKeepaliveStartS: 180,
|
||||
StunKeepaliveMinS: 20,
|
||||
RawStunServers: []string{"default"},
|
||||
AnnounceLANAddresses: true,
|
||||
FeatureFlags: []string{},
|
||||
size, err := ParseSize("1%")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := Configuration{
|
||||
Version: CurrentVersion,
|
||||
Folders: []FolderConfiguration{},
|
||||
Options: OptionsConfiguration{
|
||||
RawListenAddresses: []string{"default"},
|
||||
RawGlobalAnnServers: []string{"default"},
|
||||
GlobalAnnEnabled: true,
|
||||
LocalAnnEnabled: true,
|
||||
LocalAnnPort: 21027,
|
||||
LocalAnnMCAddr: "[ff12::8384]:21027",
|
||||
MaxSendKbps: 0,
|
||||
MaxRecvKbps: 0,
|
||||
ReconnectIntervalS: 60,
|
||||
RelaysEnabled: true,
|
||||
RelayReconnectIntervalM: 10,
|
||||
StartBrowser: true,
|
||||
NATEnabled: true,
|
||||
NATLeaseM: 60,
|
||||
NATRenewalM: 30,
|
||||
NATTimeoutS: 10,
|
||||
RestartOnWakeup: true,
|
||||
AutoUpgradeIntervalH: 12,
|
||||
KeepTemporariesH: 24,
|
||||
CacheIgnoredFiles: false,
|
||||
ProgressUpdateIntervalS: 5,
|
||||
LimitBandwidthInLan: false,
|
||||
MinHomeDiskFree: Size{1, "%"},
|
||||
URURL: "https://data.syncthing.net/newdata",
|
||||
URInitialDelayS: 1800,
|
||||
URPostInsecurely: false,
|
||||
ReleasesURL: "https://upgrades.syncthing.net/meta.json",
|
||||
AlwaysLocalNets: []string{},
|
||||
OverwriteRemoteDevNames: false,
|
||||
TempIndexMinBlocks: 10,
|
||||
UnackedNotificationIDs: []string{"authenticationUserAndPassword"},
|
||||
SetLowPriority: true,
|
||||
CRURL: "https://crash.syncthing.net/newcrash",
|
||||
CREnabled: true,
|
||||
StunKeepaliveStartS: 180,
|
||||
StunKeepaliveMinS: 20,
|
||||
RawStunServers: []string{"default"},
|
||||
AnnounceLANAddresses: true,
|
||||
FeatureFlags: []string{},
|
||||
},
|
||||
Defaults: Defaults{
|
||||
Folder: FolderConfiguration{
|
||||
FilesystemType: fs.FilesystemTypeBasic,
|
||||
Path: "~",
|
||||
Type: FolderTypeSendReceive,
|
||||
Devices: []FolderDeviceConfiguration{{DeviceID: device1}},
|
||||
RescanIntervalS: 3600,
|
||||
FSWatcherEnabled: true,
|
||||
FSWatcherDelayS: 10,
|
||||
IgnorePerms: false,
|
||||
AutoNormalize: true,
|
||||
MinDiskFree: size,
|
||||
Versioning: VersioningConfiguration{
|
||||
CleanupIntervalS: 3600,
|
||||
Params: map[string]string{},
|
||||
},
|
||||
MaxConflicts: 10,
|
||||
WeakHashThresholdPct: 25,
|
||||
MarkerName: ".stfolder",
|
||||
MaxConcurrentWrites: 2,
|
||||
},
|
||||
Device: DeviceConfiguration{
|
||||
Addresses: []string{"dynamic"},
|
||||
AllowedNetworks: []string{},
|
||||
Compression: protocol.CompressionMetadata,
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
},
|
||||
},
|
||||
IgnoredDevices: []ObservedDevice{},
|
||||
}
|
||||
expected.Devices = []DeviceConfiguration{expected.Defaults.Device.Copy()}
|
||||
expected.Devices[0].DeviceID = device1
|
||||
expected.Devices[0].Name, _ = os.Hostname()
|
||||
|
||||
cfg := New(device1)
|
||||
cfg.GUI = GUIConfiguration{}
|
||||
cfg.LDAP = LDAPConfiguration{}
|
||||
|
||||
if diff, equal := messagediff.PrettyDiff(expected, cfg.Options); !equal {
|
||||
if diff, equal := messagediff.PrettyDiff(expected, cfg); !equal {
|
||||
t.Errorf("Default config differs. Diff:\n%s", diff)
|
||||
}
|
||||
}
|
||||
@@ -95,7 +139,8 @@ func TestDeviceConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
os.RemoveAll(filepath.Join("testdata", DefaultMarkerName))
|
||||
wr, err := load(cfgFile, device1)
|
||||
wr, wrCancel, err := copyAndLoad(cfgFile, device1)
|
||||
defer wrCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -107,7 +152,7 @@ func TestDeviceConfig(t *testing.T) {
|
||||
t.Fatal("Unexpected file")
|
||||
}
|
||||
|
||||
cfg := wr.(*wrapper).cfg
|
||||
cfg := wr.Wrapper.(*wrapper).cfg
|
||||
|
||||
expectedFolders := []FolderConfiguration{
|
||||
{
|
||||
@@ -142,7 +187,6 @@ func TestDeviceConfig(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
{
|
||||
DeviceID: device4,
|
||||
@@ -151,7 +195,6 @@ func TestDeviceConfig(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
}
|
||||
expectedDeviceIDs := []protocol.DeviceID{device1, device4}
|
||||
@@ -172,7 +215,8 @@ func TestDeviceConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoListenAddresses(t *testing.T) {
|
||||
cfg, err := load("testdata/nolistenaddress.xml", device1)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/nolistenaddress.xml", device1)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -219,7 +263,6 @@ func TestOverriddenValues(t *testing.T) {
|
||||
OverwriteRemoteDevNames: true,
|
||||
TempIndexMinBlocks: 100,
|
||||
UnackedNotificationIDs: []string{"asdfasdf"},
|
||||
DefaultFolderPath: "/media/syncthing",
|
||||
SetLowPriority: false,
|
||||
CRURL: "https://localhost/newcrash",
|
||||
CREnabled: false,
|
||||
@@ -228,9 +271,11 @@ func TestOverriddenValues(t *testing.T) {
|
||||
RawStunServers: []string{"foo"},
|
||||
FeatureFlags: []string{"feature"},
|
||||
}
|
||||
expectedPath := "/media/syncthing"
|
||||
|
||||
os.Unsetenv("STNOUPGRADE")
|
||||
cfg, err := load("testdata/overridenvalues.xml", device1)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/overridenvalues.xml", device1)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -238,6 +283,10 @@ func TestOverriddenValues(t *testing.T) {
|
||||
if diff, equal := messagediff.PrettyDiff(expected, cfg.Options()); !equal {
|
||||
t.Errorf("Overridden config differs. Diff:\n%s", diff)
|
||||
}
|
||||
|
||||
if path := cfg.DefaultFolder().Path; path != expectedPath {
|
||||
t.Errorf("Default folder path is %v, expected %v", path, expectedPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeviceAddressesDynamic(t *testing.T) {
|
||||
@@ -248,21 +297,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,11 +317,11 @@ func TestDeviceAddressesDynamic(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := load("testdata/deviceaddressesdynamic.xml", device4)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/deviceaddressesdynamic.xml", device4)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -295,7 +341,6 @@ func TestDeviceCompression(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
device2: {
|
||||
DeviceID: device2,
|
||||
@@ -303,7 +348,6 @@ func TestDeviceCompression(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
device3: {
|
||||
DeviceID: device3,
|
||||
@@ -311,7 +355,6 @@ func TestDeviceCompression(t *testing.T) {
|
||||
Compression: protocol.CompressionNever,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
device4: {
|
||||
DeviceID: device4,
|
||||
@@ -320,11 +363,11 @@ func TestDeviceCompression(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := load("testdata/devicecompression.xml", device4)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/devicecompression.xml", device4)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -343,21 +386,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,11 +406,11 @@ func TestDeviceAddressesStatic(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
AllowedNetworks: []string{},
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
PendingFolders: []ObservedFolder{},
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := load("testdata/deviceaddressesstatic.xml", device4)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/deviceaddressesstatic.xml", device4)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -382,7 +422,8 @@ func TestDeviceAddressesStatic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersioningConfig(t *testing.T) {
|
||||
cfg, err := load("testdata/versioningconfig.xml", device4)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/versioningconfig.xml", device4)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -409,7 +450,8 @@ func TestIssue1262(t *testing.T) {
|
||||
t.Skipf("path gets converted to absolute as part of the filesystem initialization on linux")
|
||||
}
|
||||
|
||||
cfg, err := load("testdata/issue-1262.xml", device4)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/issue-1262.xml", device4)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -423,7 +465,8 @@ func TestIssue1262(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue1750(t *testing.T) {
|
||||
cfg, err := load("testdata/issue-1750.xml", device4)
|
||||
cfg, cfgCancel, err := copyAndLoad("testdata/issue-1750.xml", device4)
|
||||
defer cfgCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -511,7 +554,7 @@ func TestFolderCheckPath(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := cfg.CheckPath(); testcase.err != err {
|
||||
t.Errorf("unexpected error in case %s: %s != %s", testcase.path, err, testcase.err)
|
||||
t.Errorf("unexpected error in case %s: %s != %v", testcase.path, err, testcase.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -519,6 +562,7 @@ func TestFolderCheckPath(t *testing.T) {
|
||||
func TestNewSaveLoad(t *testing.T) {
|
||||
path := "testdata/temp.xml"
|
||||
os.Remove(path)
|
||||
defer os.Remove(path)
|
||||
|
||||
exists := func(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
@@ -527,6 +571,7 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
|
||||
intCfg := New(device1)
|
||||
cfg := wrap(path, intCfg, device1)
|
||||
defer cfg.stop()
|
||||
|
||||
if exists(path) {
|
||||
t.Error(path, "exists")
|
||||
@@ -541,6 +586,7 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
}
|
||||
|
||||
cfg2, err := load(path, device1)
|
||||
defer cfg2.stop()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -548,8 +594,6 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
if diff, equal := messagediff.PrettyDiff(cfg.RawCopy(), cfg2.RawCopy()); !equal {
|
||||
t.Errorf("Configs are not equal. Diff:\n%s", diff)
|
||||
}
|
||||
|
||||
os.Remove(path)
|
||||
}
|
||||
|
||||
func TestPrepare(t *testing.T) {
|
||||
@@ -567,7 +611,8 @@ func TestPrepare(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
wrapper, err := load("testdata/example.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/example.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -606,7 +651,8 @@ func TestCopy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPullOrder(t *testing.T) {
|
||||
wrapper, err := load("testdata/pullorder.xml", device1)
|
||||
wrapper, wrapperCleanup, err := copyAndLoad("testdata/pullorder.xml", device1)
|
||||
defer wrapperCleanup()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -646,8 +692,9 @@ func TestPullOrder(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wrapper = wrap("testdata/pullorder.xml", cfg, device1)
|
||||
folders = wrapper.Folders()
|
||||
wrapper2 := wrap(wrapper.ConfigPath(), cfg, device1)
|
||||
defer wrapper2.stop()
|
||||
folders = wrapper2.Folders()
|
||||
|
||||
for _, tc := range expected {
|
||||
if actual := folders[tc.name].Order; actual != tc.order {
|
||||
@@ -657,7 +704,8 @@ func TestPullOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLargeRescanInterval(t *testing.T) {
|
||||
wrapper, err := load("testdata/largeinterval.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/largeinterval.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -695,7 +743,8 @@ func TestGUIConfigURL(t *testing.T) {
|
||||
func TestDuplicateDevices(t *testing.T) {
|
||||
// Duplicate devices should be removed
|
||||
|
||||
wrapper, err := load("testdata/dupdevices.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/dupdevices.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -713,7 +762,8 @@ func TestDuplicateDevices(t *testing.T) {
|
||||
func TestDuplicateFolders(t *testing.T) {
|
||||
// Duplicate folders are a loading error
|
||||
|
||||
_, err := load("testdata/dupfolders.xml", device1)
|
||||
_, _Cancel, err := copyAndLoad("testdata/dupfolders.xml", device1)
|
||||
defer _Cancel()
|
||||
if err == nil || !strings.Contains(err.Error(), errFolderIDDuplicate.Error()) {
|
||||
t.Fatal(`Expected error to mention "duplicate folder ID":`, err)
|
||||
}
|
||||
@@ -724,7 +774,8 @@ func TestEmptyFolderPaths(t *testing.T) {
|
||||
// get messed up by the prepare steps (e.g., become the current dir or
|
||||
// get a slash added so that it becomes the root directory or similar).
|
||||
|
||||
_, err := load("testdata/nopath.xml", device1)
|
||||
_, _Cancel, err := copyAndLoad("testdata/nopath.xml", device1)
|
||||
defer _Cancel()
|
||||
if err == nil || !strings.Contains(err.Error(), errFolderPathEmpty.Error()) {
|
||||
t.Fatal("Expected error due to empty folder path, got", err)
|
||||
}
|
||||
@@ -793,7 +844,8 @@ func TestIgnoredDevices(t *testing.T) {
|
||||
// Verify that ignored devices that are also present in the
|
||||
// configuration are not in fact ignored.
|
||||
|
||||
wrapper, err := load("testdata/ignoreddevices.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/ignoreddevices.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -811,7 +863,8 @@ func TestIgnoredFolders(t *testing.T) {
|
||||
// configuration are not in fact ignored.
|
||||
// Also, verify that folders that are shared with a device are not ignored.
|
||||
|
||||
wrapper, err := load("testdata/ignoredfolders.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/ignoredfolders.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -847,7 +900,8 @@ func TestIgnoredFolders(t *testing.T) {
|
||||
func TestGetDevice(t *testing.T) {
|
||||
// Verify that the Device() call does the right thing
|
||||
|
||||
wrapper, err := load("testdata/ignoreddevices.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/ignoreddevices.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -874,7 +928,8 @@ func TestGetDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
|
||||
wrapper, err := load("testdata/example.xml", device1)
|
||||
wrapper, wrapperCancel, err := copyAndLoad("testdata/example.xml", device1)
|
||||
defer wrapperCancel()
|
||||
if err != nil {
|
||||
t.Errorf("Failed: %s", err)
|
||||
}
|
||||
@@ -886,10 +941,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
|
||||
t.Error("Should have less devices")
|
||||
}
|
||||
|
||||
_, err = wrapper.Replace(raw)
|
||||
if err != nil {
|
||||
t.Errorf("Failed: %s", err)
|
||||
}
|
||||
replace(t, wrapper, raw)
|
||||
|
||||
raw = wrapper.RawCopy()
|
||||
if len(raw.Folders[0].Devices) > len(raw.Devices) {
|
||||
@@ -961,6 +1013,7 @@ func TestIssue4219(t *testing.T) {
|
||||
}
|
||||
|
||||
w := wrap("/tmp/cfg", cfg, myID)
|
||||
defer w.stop()
|
||||
if !w.IgnoredFolder(device2, "t1") {
|
||||
t.Error("Folder device2 t1 should be ignored")
|
||||
}
|
||||
@@ -1097,10 +1150,6 @@ func TestDeviceConfigObservedNotNil(t *testing.T) {
|
||||
if dev.IgnoredFolders == nil {
|
||||
t.Errorf("Ignored folders nil")
|
||||
}
|
||||
|
||||
if dev.PendingFolders == nil {
|
||||
t.Errorf("Pending folders nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,13 +1204,29 @@ func TestMaxConcurrentFolders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func adjustDeviceConfiguration(cfg *DeviceConfiguration, id protocol.DeviceID, name string) {
|
||||
cfg.DeviceID = id
|
||||
cfg.Name = name
|
||||
}
|
||||
|
||||
func adjustFolderConfiguration(cfg *FolderConfiguration, id, label string, fsType fs.FilesystemType, path string) {
|
||||
cfg.ID = id
|
||||
cfg.Label = label
|
||||
cfg.FilesystemType = fsType
|
||||
cfg.Path = path
|
||||
}
|
||||
|
||||
// defaultConfigAsMap returns a valid default config as a JSON-decoded
|
||||
// map[string]interface{}. This is useful to override random elements and
|
||||
// re-encode into JSON.
|
||||
func defaultConfigAsMap() map[string]interface{} {
|
||||
cfg := New(device1)
|
||||
cfg.Devices = append(cfg.Devices, NewDeviceConfiguration(device2, "name"))
|
||||
cfg.Folders = append(cfg.Folders, NewFolderConfiguration(device1, "default", "default", fs.FilesystemTypeBasic, "/tmp"))
|
||||
dev := cfg.Defaults.Device.Copy()
|
||||
adjustDeviceConfiguration(&dev, device2, "name")
|
||||
cfg.Devices = append(cfg.Devices, dev)
|
||||
folder := cfg.Defaults.Folder.Copy()
|
||||
adjustFolderConfiguration(&folder, "default", "default", fs.FilesystemTypeBasic, "/tmp")
|
||||
cfg.Folders = append(cfg.Folders, folder)
|
||||
bs, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
// can't happen
|
||||
@@ -1175,13 +1240,80 @@ func defaultConfigAsMap() map[string]interface{} {
|
||||
return tmp
|
||||
}
|
||||
|
||||
func load(path string, myID protocol.DeviceID) (Wrapper, error) {
|
||||
cfg, _, err := Load(path, myID, events.NoopLogger)
|
||||
return cfg, err
|
||||
func copyToTmp(path string) (string, error) {
|
||||
orig, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer orig.Close()
|
||||
temp, err := ioutil.TempFile("", "syncthing-configTest-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer temp.Close()
|
||||
if _, err := io.Copy(temp, orig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return temp.Name(), nil
|
||||
}
|
||||
|
||||
func wrap(path string, cfg Configuration, myID protocol.DeviceID) Wrapper {
|
||||
return Wrap(path, cfg, myID, events.NoopLogger)
|
||||
func copyAndLoad(path string, myID protocol.DeviceID) (*testWrapper, func(), error) {
|
||||
temp, err := copyToTmp(path)
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
wrapper, err := load(temp, myID)
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
return wrapper, func() {
|
||||
wrapper.stop()
|
||||
os.Remove(temp)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func load(path string, myID protocol.DeviceID) (*testWrapper, error) {
|
||||
cfg, _, err := Load(path, myID, events.NoopLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return startWrapper(cfg), nil
|
||||
}
|
||||
|
||||
func wrap(path string, cfg Configuration, myID protocol.DeviceID) *testWrapper {
|
||||
wrapper := Wrap(path, cfg, myID, events.NoopLogger)
|
||||
return startWrapper(wrapper)
|
||||
}
|
||||
|
||||
type testWrapper struct {
|
||||
Wrapper
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (w *testWrapper) stop() {
|
||||
w.cancel()
|
||||
<-w.done
|
||||
}
|
||||
|
||||
func startWrapper(wrapper Wrapper) *testWrapper {
|
||||
tw := &testWrapper{
|
||||
Wrapper: wrapper,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
s, ok := wrapper.(suture.Service)
|
||||
if !ok {
|
||||
tw.cancel = func() {}
|
||||
close(tw.done)
|
||||
return tw
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tw.cancel = cancel
|
||||
go func() {
|
||||
s.Serve(ctx)
|
||||
close(tw.done)
|
||||
}()
|
||||
return tw
|
||||
}
|
||||
|
||||
func TestInternalVersioningConfiguration(t *testing.T) {
|
||||
@@ -1189,7 +1321,9 @@ func TestInternalVersioningConfiguration(t *testing.T) {
|
||||
// reasonable.
|
||||
|
||||
cfg := New(device1)
|
||||
cfg.Folders = append(cfg.Folders, NewFolderConfiguration(device1, "default", "default", fs.FilesystemTypeBasic, "/tmp"))
|
||||
folder := cfg.Defaults.Folder.Copy()
|
||||
adjustFolderConfiguration(&folder, "default", "default", fs.FilesystemTypeBasic, "/tmp")
|
||||
cfg.Folders = append(cfg.Folders, folder)
|
||||
cfg.Folders[0].Versioning = VersioningConfiguration{
|
||||
Type: "foo",
|
||||
Params: map[string]string{"bar": "baz"},
|
||||
|
||||
@@ -8,23 +8,8 @@ package config
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {
|
||||
d := DeviceConfiguration{
|
||||
DeviceID: id,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
util.SetDefaults(&d)
|
||||
|
||||
d.prepare(nil)
|
||||
return d
|
||||
}
|
||||
|
||||
func (cfg DeviceConfiguration) Copy() DeviceConfiguration {
|
||||
c := cfg
|
||||
c.Addresses = make([]string, len(cfg.Addresses))
|
||||
@@ -33,8 +18,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
|
||||
}
|
||||
|
||||
@@ -42,24 +25,14 @@ func (cfg *DeviceConfiguration) prepare(sharedFolders []string) {
|
||||
if len(cfg.Addresses) == 0 || len(cfg.Addresses) == 1 && cfg.Addresses[0] == "" {
|
||||
cfg.Addresses = []string{"dynamic"}
|
||||
}
|
||||
if len(cfg.AllowedNetworks) == 0 {
|
||||
cfg.AllowedNetworks = []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 {
|
||||
|
||||
@@ -26,21 +26,21 @@ var _ = math.Inf
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type DeviceConfiguration struct {
|
||||
DeviceID github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"deviceID" xml:"id,attr"`
|
||||
DeviceID github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"deviceID" xml:"id,attr" nodefault:"true"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name" xml:"name,attr,omitempty"`
|
||||
Addresses []string `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses" xml:"address,omitempty" default:"dynamic"`
|
||||
Compression protocol.Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression" xml:"compression,attr"`
|
||||
CertName string `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"certName" xml:"certName,attr,omitempty"`
|
||||
Introducer bool `protobuf:"varint,6,opt,name=introducer,proto3" json:"introducer" xml:"introducer,attr"`
|
||||
SkipIntroductionRemovals bool `protobuf:"varint,7,opt,name=skip_introduction_removals,json=skipIntroductionRemovals,proto3" json:"skipIntroductionRemovals" xml:"skipIntroductionRemovals,attr"`
|
||||
IntroducedBy github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,8,opt,name=introduced_by,json=introducedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"introducedBy" xml:"introducedBy,attr"`
|
||||
IntroducedBy github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,8,opt,name=introduced_by,json=introducedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"introducedBy" xml:"introducedBy,attr" nodefault:"true"`
|
||||
Paused bool `protobuf:"varint,9,opt,name=paused,proto3" json:"paused" xml:"paused"`
|
||||
AllowedNetworks []string `protobuf:"bytes,10,rep,name=allowed_networks,json=allowedNetworks,proto3" json:"allowedNetworks" xml:"allowedNetwork,omitempty"`
|
||||
AutoAcceptFolders bool `protobuf:"varint,11,opt,name=auto_accept_folders,json=autoAcceptFolders,proto3" json:"autoAcceptFolders" xml:"autoAcceptFolders"`
|
||||
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,72 @@ 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,
|
||||
// 1026 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xbf, 0x6f, 0xdb, 0x46,
|
||||
0x18, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x3f, 0x64, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x4e, 0x50, 0x35,
|
||||
0x28, 0x68, 0x22, 0x17, 0x6e, 0x27, 0xa3, 0x2d, 0x50, 0xc6, 0x68, 0x63, 0x18, 0x4d, 0x5c, 0x16,
|
||||
0x5d, 0xbc, 0xb0, 0x24, 0xef, 0xac, 0x1c, 0x2c, 0xf2, 0x58, 0xf2, 0xa8, 0x58, 0x40, 0xff, 0x80,
|
||||
0x76, 0x2b, 0x02, 0x74, 0xea, 0x92, 0xf6, 0xdf, 0xe8, 0xd0, 0xd5, 0x9b, 0x35, 0x16, 0x1d, 0x0e,
|
||||
0x88, 0xbd, 0x71, 0x29, 0xc0, 0x31, 0x53, 0x71, 0x77, 0x14, 0x45, 0xca, 0x51, 0x50, 0xa0, 0x1b,
|
||||
0xef, 0xbd, 0x77, 0xef, 0xdd, 0xf7, 0xe9, 0xbb, 0x13, 0xe8, 0x0c, 0x88, 0xbb, 0xeb, 0xd1, 0xe0,
|
||||
0x94, 0xf4, 0x77, 0x11, 0x1e, 0x12, 0x0f, 0xab, 0x45, 0x12, 0x39, 0x8c, 0xd0, 0xa0, 0x17, 0x46,
|
||||
0x94, 0x51, 0x7d, 0x51, 0x81, 0x3b, 0xdb, 0x42, 0x2d, 0x21, 0x8f, 0x0e, 0x76, 0x5d, 0x1c, 0x2a,
|
||||
0x7e, 0xe7, 0x5e, 0xc9, 0x85, 0xba, 0x31, 0x8e, 0x86, 0x18, 0xe5, 0x54, 0x1d, 0x9f, 0x33, 0xf5,
|
||||
0xd9, 0xfe, 0x67, 0x03, 0x6c, 0x1d, 0xc8, 0x8c, 0xc7, 0xe5, 0x0c, 0xfd, 0x4f, 0x0d, 0xd4, 0x55,
|
||||
0xb6, 0x4d, 0x90, 0xa1, 0xb5, 0xb4, 0xee, 0xaa, 0xf9, 0x9b, 0x76, 0xc1, 0x61, 0xed, 0x6f, 0x0e,
|
||||
0x3f, 0xee, 0x13, 0xf6, 0x3c, 0x71, 0x7b, 0x1e, 0xf5, 0x77, 0xe3, 0x51, 0xe0, 0xb1, 0xe7, 0x24,
|
||||
0xe8, 0x97, 0xbe, 0xca, 0x27, 0xea, 0x29, 0xf7, 0xc3, 0x83, 0x2b, 0x0e, 0x97, 0x27, 0xdf, 0x29,
|
||||
0x87, 0xcb, 0x28, 0xff, 0xce, 0x38, 0x6c, 0x9e, 0xfb, 0x83, 0xfd, 0x36, 0x41, 0x0f, 0x1d, 0xc6,
|
||||
0xa2, 0x76, 0x2b, 0xa0, 0x08, 0x9f, 0x3a, 0xc9, 0x80, 0xed, 0xb7, 0x59, 0x94, 0xe0, 0x76, 0x7a,
|
||||
0xd9, 0x59, 0xca, 0xc9, 0xec, 0xb2, 0x53, 0x6c, 0xfc, 0x71, 0xdc, 0xd1, 0x5e, 0x8e, 0x3b, 0x85,
|
||||
0xe9, 0xab, 0x71, 0x47, 0xb3, 0x26, 0x2c, 0xd2, 0x8f, 0xc1, 0xad, 0xc0, 0xf1, 0xb1, 0xf1, 0x5e,
|
||||
0x4b, 0xeb, 0xd6, 0xcd, 0x4f, 0x52, 0x0e, 0xe5, 0x3a, 0xe3, 0xf0, 0x9e, 0x8c, 0x13, 0x0b, 0xe9,
|
||||
0xf9, 0x90, 0xfa, 0x84, 0x61, 0x3f, 0x64, 0x23, 0x91, 0xb4, 0xf5, 0x16, 0xdc, 0x92, 0x3b, 0xf5,
|
||||
0x73, 0x50, 0x77, 0x10, 0x8a, 0x70, 0x1c, 0xe3, 0xd8, 0x58, 0x68, 0x2d, 0x74, 0xeb, 0xe6, 0x49,
|
||||
0xca, 0xe1, 0x14, 0xcc, 0x38, 0x7c, 0x20, 0xbd, 0x73, 0xa4, 0xe4, 0xdc, 0x2a, 0x4a, 0x42, 0xa3,
|
||||
0xc0, 0xf1, 0x89, 0x27, 0xb2, 0x36, 0x6f, 0xe8, 0xde, 0x5c, 0x76, 0x96, 0x72, 0x81, 0x35, 0xf5,
|
||||
0xd5, 0x87, 0x60, 0xc5, 0xa3, 0x7e, 0x28, 0x56, 0x84, 0x06, 0xc6, 0xad, 0x96, 0xd6, 0x5d, 0xdf,
|
||||
0xbb, 0xd3, 0x2b, 0x7a, 0xfc, 0x78, 0x4a, 0x9a, 0x9f, 0xa6, 0x1c, 0x96, 0xd5, 0x19, 0x87, 0xdb,
|
||||
0xf2, 0x50, 0x25, 0x4c, 0x35, 0x3a, 0xbd, 0xec, 0x6c, 0xcc, 0x82, 0x56, 0x79, 0xab, 0x8e, 0x41,
|
||||
0xdd, 0xc3, 0x11, 0xb3, 0x65, 0x23, 0x6f, 0xcb, 0x46, 0x3e, 0x11, 0xbf, 0x9d, 0x00, 0x9f, 0xaa,
|
||||
0x66, 0xde, 0x57, 0xde, 0x39, 0xf0, 0x96, 0x86, 0xde, 0x9d, 0xc3, 0x59, 0x85, 0x8b, 0x7e, 0x02,
|
||||
0x00, 0x09, 0x58, 0x44, 0x51, 0xe2, 0xe1, 0xc8, 0x58, 0x6c, 0x69, 0xdd, 0x65, 0x73, 0x3f, 0xe5,
|
||||
0xb0, 0x84, 0x66, 0x1c, 0xde, 0x51, 0x53, 0x52, 0x40, 0x45, 0x11, 0x8d, 0x19, 0xcc, 0x2a, 0xed,
|
||||
0xd3, 0x7f, 0xd7, 0xc0, 0x4e, 0x7c, 0x46, 0x42, 0x7b, 0x82, 0x89, 0xf1, 0xb6, 0x23, 0xec, 0xd3,
|
||||
0xa1, 0x33, 0x88, 0x8d, 0x25, 0x19, 0x86, 0x52, 0x0e, 0x0d, 0xa1, 0x3a, 0x2c, 0x89, 0xac, 0x5c,
|
||||
0x93, 0x71, 0xf8, 0xbe, 0x8c, 0x9e, 0x27, 0x28, 0x0e, 0x72, 0xff, 0x9d, 0x0a, 0x6b, 0x6e, 0x82,
|
||||
0xfe, 0x87, 0x06, 0xd6, 0x8a, 0x33, 0x23, 0xdb, 0x1d, 0x19, 0xcb, 0xf2, 0xc6, 0xfd, 0xf2, 0xbf,
|
||||
0x6e, 0x5c, 0xca, 0xe1, 0xea, 0xd4, 0xd5, 0x1c, 0x65, 0x1c, 0x76, 0xab, 0x3d, 0x44, 0xe6, 0x68,
|
||||
0xfe, 0x9d, 0xdb, 0xbc, 0x21, 0x13, 0x37, 0x4e, 0xde, 0xb2, 0x8a, 0xad, 0xbe, 0x07, 0x16, 0x43,
|
||||
0x27, 0x89, 0x31, 0x32, 0xea, 0xb2, 0x9b, 0x3b, 0x29, 0x87, 0x39, 0x92, 0x71, 0xb8, 0x2a, 0x23,
|
||||
0xd5, 0xb2, 0x6d, 0xe5, 0xb8, 0xfe, 0x03, 0xd8, 0x70, 0x06, 0x03, 0xfa, 0x02, 0x23, 0x3b, 0xc0,
|
||||
0xec, 0x05, 0x8d, 0xce, 0x62, 0x03, 0xc8, 0x2b, 0xf5, 0x75, 0xca, 0x61, 0x23, 0xe7, 0x9e, 0xe6,
|
||||
0x54, 0xf1, 0x46, 0x54, 0xf1, 0xea, 0xa0, 0x19, 0xf3, 0x48, 0x6b, 0xd6, 0x4e, 0xff, 0x0e, 0x6c,
|
||||
0x39, 0x09, 0xa3, 0xb6, 0xe3, 0x79, 0x38, 0x64, 0xf6, 0x29, 0x1d, 0x20, 0x1c, 0xc5, 0xc6, 0x8a,
|
||||
0x3c, 0xfe, 0x87, 0x29, 0x87, 0x9b, 0x82, 0xfe, 0x5c, 0xb2, 0x5f, 0x28, 0x32, 0xe3, 0xf0, 0xae,
|
||||
0x3a, 0xc2, 0x2c, 0xd3, 0xb6, 0x6e, 0xaa, 0xf5, 0x67, 0x60, 0xcd, 0x77, 0xce, 0xed, 0x18, 0x07,
|
||||
0xc8, 0x3e, 0x73, 0xc3, 0xd8, 0x58, 0x6d, 0x69, 0xdd, 0xdb, 0xe6, 0x07, 0xe2, 0x72, 0xfa, 0xce,
|
||||
0xf9, 0x37, 0x38, 0x40, 0x47, 0x6e, 0x28, 0x5c, 0x37, 0xa5, 0x6b, 0x09, 0x6b, 0xbf, 0xe1, 0x70,
|
||||
0x81, 0x04, 0xcc, 0x2a, 0x0b, 0x27, 0x86, 0x11, 0xf6, 0x86, 0xca, 0x70, 0xad, 0x62, 0x68, 0x61,
|
||||
0x6f, 0x38, 0x6b, 0x38, 0xc1, 0x2a, 0x86, 0x13, 0x50, 0x0f, 0x40, 0x83, 0xf4, 0x03, 0x1a, 0x61,
|
||||
0x54, 0xd4, 0xbf, 0xde, 0x5a, 0xe8, 0xae, 0xec, 0x6d, 0xf7, 0xd4, 0xbf, 0x46, 0xef, 0x59, 0xfe,
|
||||
0xaf, 0xa1, 0x6a, 0x32, 0x1f, 0x89, 0x59, 0x4c, 0x39, 0x5c, 0xcf, 0xb7, 0x4d, 0x1b, 0xb3, 0xa5,
|
||||
0xa6, 0xaa, 0x0c, 0xb7, 0xad, 0x19, 0x99, 0xfe, 0x93, 0x06, 0x1a, 0x21, 0x0e, 0x10, 0x09, 0xfa,
|
||||
0x45, 0x60, 0xe3, 0x9d, 0x81, 0x4f, 0x44, 0xe0, 0x15, 0x87, 0xc6, 0x01, 0x0e, 0x23, 0xec, 0x39,
|
||||
0x0c, 0xa3, 0x63, 0x65, 0x90, 0x7b, 0xa6, 0x1c, 0x6a, 0x8f, 0x8a, 0x37, 0x28, 0x2c, 0x73, 0xa5,
|
||||
0xd1, 0x30, 0x34, 0x6b, 0xbd, 0xc2, 0xc5, 0xfa, 0xaf, 0x1a, 0x68, 0xa8, 0x6e, 0x7e, 0x9f, 0xe0,
|
||||
0x98, 0xd9, 0x67, 0xc4, 0x35, 0x36, 0x64, 0x3f, 0xe3, 0x2b, 0x0e, 0xd7, 0xbe, 0x12, 0x6d, 0x92,
|
||||
0xcc, 0x11, 0x31, 0x53, 0x0e, 0xd7, 0xfc, 0x32, 0x50, 0x14, 0x5c, 0x41, 0x27, 0x4d, 0x4e, 0x2f,
|
||||
0x3b, 0x33, 0xf2, 0x59, 0xe0, 0xe5, 0xb8, 0x53, 0x4d, 0xb0, 0x2a, 0xbc, 0xab, 0x7f, 0x06, 0xea,
|
||||
0x49, 0xc0, 0xa2, 0x24, 0x66, 0x18, 0x19, 0x9b, 0x72, 0x26, 0x5b, 0xe2, 0x7f, 0xa6, 0x00, 0x33,
|
||||
0x0e, 0x1b, 0xf2, 0x04, 0x05, 0xd2, 0xb6, 0xa6, 0xac, 0xac, 0x4e, 0x3c, 0x70, 0x0c, 0xdb, 0xfd,
|
||||
0x84, 0xd8, 0x21, 0x8d, 0x98, 0xa1, 0x4f, 0xab, 0xb3, 0x24, 0xf5, 0xe5, 0xb7, 0x87, 0xc7, 0x34,
|
||||
0x62, 0xa2, 0xba, 0xa8, 0x0c, 0x14, 0xd5, 0x55, 0xd0, 0x72, 0x75, 0x55, 0xf9, 0x2c, 0x20, 0xaa,
|
||||
0xab, 0x24, 0x58, 0x13, 0x3e, 0x21, 0x62, 0x69, 0x1e, 0x5d, 0xbc, 0x6e, 0xd6, 0xc6, 0xaf, 0x9b,
|
||||
0xb5, 0x8b, 0xab, 0xa6, 0x36, 0xbe, 0x6a, 0x6a, 0x3f, 0x5f, 0x37, 0x6b, 0xaf, 0xae, 0x9b, 0xda,
|
||||
0xf8, 0xba, 0x59, 0xfb, 0xeb, 0xba, 0x59, 0x3b, 0x79, 0xf0, 0x1f, 0x1e, 0x3b, 0x35, 0x31, 0xee,
|
||||
0xa2, 0x7c, 0xf4, 0x3e, 0xfa, 0x37, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x4a, 0x4f, 0x60, 0x33, 0x09,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *DeviceConfiguration) Marshal() (dAtA []byte, err error) {
|
||||
@@ -199,10 +202,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 +408,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 +828,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 +855,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
|
||||
|
||||
@@ -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"
|
||||
@@ -33,21 +33,6 @@ const (
|
||||
maxConcurrentWritesLimit = 64
|
||||
)
|
||||
|
||||
func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration {
|
||||
f := FolderConfiguration{
|
||||
ID: id,
|
||||
Label: label,
|
||||
Devices: []FolderDeviceConfiguration{{DeviceID: myID}},
|
||||
FilesystemType: fsType,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
util.SetDefaults(&f)
|
||||
|
||||
f.prepare(myID, nil)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f FolderConfiguration) Copy() FolderConfiguration {
|
||||
c := f
|
||||
c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))
|
||||
|
||||
@@ -66,10 +66,10 @@ func (m *FolderDeviceConfiguration) XXX_DiscardUnknown() {
|
||||
var xxx_messageInfo_FolderDeviceConfiguration proto.InternalMessageInfo
|
||||
|
||||
type FolderConfiguration struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id" xml:"id,attr"`
|
||||
ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id" xml:"id,attr" nodefault:"true"`
|
||||
Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label" xml:"label,attr" restart:"false"`
|
||||
FilesystemType fs.FilesystemType `protobuf:"varint,3,opt,name=filesystem_type,json=filesystemType,proto3,enum=fs.FilesystemType" json:"filesystemType" xml:"filesystemType"`
|
||||
Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path" xml:"path,attr"`
|
||||
Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path" xml:"path,attr" default:"~"`
|
||||
Type FolderType `protobuf:"varint,5,opt,name=type,proto3,enum=config.FolderType" json:"type" xml:"type,attr"`
|
||||
Devices []FolderDeviceConfiguration `protobuf:"bytes,6,rep,name=devices,proto3" json:"devices" xml:"device"`
|
||||
RescanIntervalS int `protobuf:"varint,7,opt,name=rescan_interval_s,json=rescanIntervalS,proto3,casttype=int" json:"rescanIntervalS" xml:"rescanIntervalS,attr" default:"3600"`
|
||||
@@ -77,7 +77,7 @@ type FolderConfiguration struct {
|
||||
FSWatcherDelayS int `protobuf:"varint,9,opt,name=fs_watcher_delay_s,json=fsWatcherDelayS,proto3,casttype=int" json:"fsWatcherDelayS" xml:"fsWatcherDelayS,attr" default:"10"`
|
||||
IgnorePerms bool `protobuf:"varint,10,opt,name=ignore_perms,json=ignorePerms,proto3" json:"ignorePerms" xml:"ignorePerms,attr"`
|
||||
AutoNormalize bool `protobuf:"varint,11,opt,name=auto_normalize,json=autoNormalize,proto3" json:"autoNormalize" xml:"autoNormalize,attr" default:"true"`
|
||||
MinDiskFree Size `protobuf:"bytes,12,opt,name=min_disk_free,json=minDiskFree,proto3" json:"minDiskFree" xml:"minDiskFree"`
|
||||
MinDiskFree Size `protobuf:"bytes,12,opt,name=min_disk_free,json=minDiskFree,proto3" json:"minDiskFree" xml:"minDiskFree" default:"1 %"`
|
||||
Versioning VersioningConfiguration `protobuf:"bytes,13,opt,name=versioning,proto3" json:"versioning" xml:"versioning"`
|
||||
Copiers int `protobuf:"varint,14,opt,name=copiers,proto3,casttype=int" json:"copiers" xml:"copiers"`
|
||||
PullerMaxPendingKiB int `protobuf:"varint,15,opt,name=puller_max_pending_kib,json=pullerMaxPendingKib,proto3,casttype=int" json:"pullerMaxPendingKiB" xml:"pullerMaxPendingKiB"`
|
||||
@@ -86,7 +86,7 @@ type FolderConfiguration struct {
|
||||
IgnoreDelete bool `protobuf:"varint,18,opt,name=ignore_delete,json=ignoreDelete,proto3" json:"ignoreDelete" xml:"ignoreDelete"`
|
||||
ScanProgressIntervalS int `protobuf:"varint,19,opt,name=scan_progress_interval_s,json=scanProgressIntervalS,proto3,casttype=int" json:"scanProgressIntervalS" xml:"scanProgressIntervalS"`
|
||||
PullerPauseS int `protobuf:"varint,20,opt,name=puller_pause_s,json=pullerPauseS,proto3,casttype=int" json:"pullerPauseS" xml:"pullerPauseS"`
|
||||
MaxConflicts int `protobuf:"varint,21,opt,name=max_conflicts,json=maxConflicts,proto3,casttype=int" json:"maxConflicts" xml:"maxConflicts" default:"-1"`
|
||||
MaxConflicts int `protobuf:"varint,21,opt,name=max_conflicts,json=maxConflicts,proto3,casttype=int" json:"maxConflicts" xml:"maxConflicts" default:"10"`
|
||||
DisableSparseFiles bool `protobuf:"varint,22,opt,name=disable_sparse_files,json=disableSparseFiles,proto3" json:"disableSparseFiles" xml:"disableSparseFiles"`
|
||||
DisableTempIndexes bool `protobuf:"varint,23,opt,name=disable_temp_indexes,json=disableTempIndexes,proto3" json:"disableTempIndexes" xml:"disableTempIndexes"`
|
||||
Paused bool `protobuf:"varint,24,opt,name=paused,proto3" json:"paused" xml:"paused"`
|
||||
@@ -149,134 +149,135 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_44a9785876ed3afa = []byte{
|
||||
// 2018 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0xcd, 0x6f, 0x1c, 0x49,
|
||||
0x15, 0x77, 0x3b, 0x5f, 0x76, 0xf9, 0xbb, 0x1c, 0x27, 0x15, 0x67, 0xd7, 0xe5, 0x6d, 0x86, 0xc5,
|
||||
0xbb, 0xda, 0x38, 0x89, 0x17, 0x71, 0x88, 0x76, 0x17, 0x76, 0xe2, 0xb5, 0x08, 0x21, 0x9b, 0x51,
|
||||
0x3b, 0x10, 0x11, 0x90, 0x9a, 0x76, 0x77, 0xcd, 0x4c, 0xad, 0xfb, 0x8b, 0xaa, 0x9e, 0xd8, 0x93,
|
||||
0x53, 0xb8, 0x20, 0x10, 0x7b, 0x40, 0xe6, 0xc0, 0x15, 0x09, 0x84, 0x60, 0xff, 0x01, 0x24, 0xfe,
|
||||
0x82, 0x5c, 0x90, 0xe7, 0x84, 0x10, 0x87, 0x92, 0xd6, 0xbe, 0xcd, 0xb1, 0x8f, 0x39, 0xa1, 0xaa,
|
||||
0xea, 0xee, 0xe9, 0xee, 0x99, 0x45, 0x48, 0x7b, 0x9b, 0xfa, 0xfd, 0x7e, 0xf5, 0xde, 0xeb, 0x57,
|
||||
0xf5, 0x5e, 0xbf, 0x1e, 0xd0, 0xf0, 0xe9, 0xc1, 0x6d, 0x37, 0x0a, 0xdb, 0xb4, 0x73, 0xbb, 0x1d,
|
||||
0xf9, 0x1e, 0x61, 0x7a, 0xd1, 0x63, 0x4e, 0x42, 0xa3, 0x70, 0x3b, 0x66, 0x51, 0x12, 0xc1, 0xcb,
|
||||
0x1a, 0x5c, 0xbf, 0x39, 0xa6, 0x4e, 0xfa, 0x31, 0xd1, 0xa2, 0xf5, 0xb5, 0x12, 0xc9, 0xe9, 0x8b,
|
||||
0x1c, 0x5e, 0x2f, 0xc1, 0x71, 0xcf, 0xf7, 0x23, 0xe6, 0x11, 0x96, 0x71, 0x5b, 0x25, 0xee, 0x39,
|
||||
0x61, 0x9c, 0x46, 0x21, 0x0d, 0x3b, 0x13, 0x22, 0x58, 0xc7, 0x25, 0xe5, 0x81, 0x1f, 0xb9, 0x87,
|
||||
0x75, 0x53, 0x50, 0x0a, 0xda, 0xfc, 0xb6, 0x0c, 0x88, 0x67, 0xd8, 0x1b, 0x19, 0xe6, 0x46, 0x71,
|
||||
0x9f, 0x39, 0x61, 0x87, 0x04, 0x24, 0xe9, 0x46, 0x5e, 0xc6, 0xce, 0x92, 0xe3, 0x44, 0xff, 0x34,
|
||||
0xff, 0x75, 0x01, 0xdc, 0xd8, 0x53, 0xcf, 0xb3, 0x4b, 0x9e, 0x53, 0x97, 0xdc, 0x2f, 0x47, 0x00,
|
||||
0xbf, 0x30, 0xc0, 0xac, 0xa7, 0x70, 0x9b, 0x7a, 0xc8, 0xd8, 0x34, 0xb6, 0xe6, 0x9b, 0x9f, 0x1b,
|
||||
0xaf, 0x04, 0x9e, 0xfa, 0x8f, 0xc0, 0xdf, 0xee, 0xd0, 0xa4, 0xdb, 0x3b, 0xd8, 0x76, 0xa3, 0xe0,
|
||||
0x36, 0xef, 0x87, 0x6e, 0xd2, 0xa5, 0x61, 0xa7, 0xf4, 0x4b, 0x86, 0xa0, 0x9c, 0xb8, 0x91, 0xbf,
|
||||
0xad, 0xad, 0x3f, 0xd8, 0x3d, 0x13, 0x78, 0x26, 0xff, 0x3d, 0x14, 0x78, 0xc6, 0xcb, 0x7e, 0xa7,
|
||||
0x02, 0x2f, 0x1c, 0x07, 0xfe, 0x3d, 0x93, 0x7a, 0xef, 0x39, 0x49, 0xc2, 0xcc, 0xe1, 0x69, 0xe3,
|
||||
0x4a, 0xf6, 0x3b, 0x3d, 0x6d, 0x14, 0xba, 0x5f, 0x0f, 0x1a, 0xc6, 0xc9, 0xa0, 0x51, 0xd8, 0xb0,
|
||||
0x72, 0xc6, 0x83, 0x7f, 0x31, 0xc0, 0x02, 0x0d, 0x13, 0x16, 0x79, 0x3d, 0x97, 0x78, 0xf6, 0x41,
|
||||
0x1f, 0x4d, 0xab, 0x80, 0x5f, 0x7e, 0xad, 0x80, 0x87, 0x02, 0xcf, 0x8f, 0xac, 0x36, 0xfb, 0xa9,
|
||||
0xc0, 0xd7, 0x75, 0xa0, 0x25, 0xb0, 0x08, 0x79, 0x65, 0x0c, 0x95, 0x01, 0x5b, 0x15, 0x0b, 0xd0,
|
||||
0x05, 0xab, 0x24, 0x74, 0x59, 0x3f, 0x96, 0x39, 0xb6, 0x63, 0x87, 0xf3, 0xa3, 0x88, 0x79, 0xe8,
|
||||
0xc2, 0xa6, 0xb1, 0x35, 0xdb, 0xdc, 0x19, 0x0a, 0x0c, 0x47, 0x74, 0x2b, 0x63, 0x53, 0x81, 0x91,
|
||||
0x72, 0x3b, 0x4e, 0x99, 0xd6, 0x04, 0xbd, 0x79, 0x8e, 0xc1, 0xaa, 0x3e, 0xd8, 0xea, 0x91, 0x7e,
|
||||
0x04, 0xa6, 0xb3, 0xa3, 0x9c, 0x6d, 0x6e, 0x9f, 0x09, 0x3c, 0xad, 0x1e, 0x71, 0x9a, 0x7a, 0xff,
|
||||
0xeb, 0x04, 0x4e, 0x06, 0x8d, 0xe9, 0x07, 0xbb, 0xd6, 0x34, 0xf5, 0xe0, 0x8f, 0xc0, 0x25, 0xdf,
|
||||
0x39, 0x20, 0xbe, 0x4a, 0xee, 0x6c, 0xf3, 0xbb, 0x43, 0x81, 0x35, 0x90, 0x0a, 0xbc, 0xa9, 0xf6,
|
||||
0xab, 0x95, 0x36, 0xb1, 0xc9, 0x08, 0x4f, 0x1c, 0x96, 0xdc, 0x33, 0xdb, 0x8e, 0xcf, 0x89, 0x34,
|
||||
0x09, 0x46, 0xf4, 0xcb, 0x41, 0x63, 0xca, 0xd2, 0x9b, 0x61, 0x07, 0x2c, 0xb5, 0xa9, 0x4f, 0x78,
|
||||
0x9f, 0x27, 0x24, 0xb0, 0xe5, 0x55, 0x56, 0xf9, 0x58, 0xdc, 0x81, 0xdb, 0x6d, 0xbe, 0xbd, 0x57,
|
||||
0x50, 0x4f, 0xfa, 0x31, 0x69, 0xbe, 0x3b, 0x14, 0x78, 0xb1, 0x5d, 0xc1, 0x52, 0x81, 0xaf, 0x2a,
|
||||
0xef, 0x55, 0xd8, 0xb4, 0x6a, 0x3a, 0xf8, 0x01, 0xb8, 0x18, 0x3b, 0x49, 0x17, 0x5d, 0x54, 0xe1,
|
||||
0x6f, 0x0d, 0x05, 0x56, 0xeb, 0x54, 0xe0, 0x25, 0xb5, 0x5f, 0x2e, 0x8a, 0xe7, 0x9f, 0x2d, 0x56,
|
||||
0x96, 0x52, 0xc1, 0x16, 0xb8, 0xa8, 0x62, 0xbb, 0x94, 0xc5, 0xa6, 0xeb, 0x72, 0x5b, 0x27, 0x5a,
|
||||
0xc5, 0xa6, 0x2c, 0x26, 0x3a, 0x22, 0x6d, 0x51, 0x2e, 0x46, 0x16, 0x8b, 0x95, 0xa5, 0x54, 0xf0,
|
||||
0x67, 0xe0, 0x8a, 0xbe, 0xc1, 0x1c, 0x5d, 0xde, 0xbc, 0xb0, 0x35, 0xb7, 0xf3, 0x56, 0xd5, 0xe8,
|
||||
0x84, 0xb2, 0x6c, 0x62, 0x79, 0xa1, 0x87, 0x02, 0xe7, 0x3b, 0x53, 0x81, 0xe7, 0x95, 0x2b, 0xbd,
|
||||
0x36, 0xad, 0x9c, 0x80, 0xbf, 0x37, 0xc0, 0x0a, 0x23, 0xdc, 0x75, 0x42, 0x9b, 0x86, 0x09, 0x61,
|
||||
0xcf, 0x1d, 0xdf, 0xe6, 0xe8, 0xca, 0xa6, 0xb1, 0x75, 0xa9, 0xd9, 0x19, 0x0a, 0xbc, 0xa4, 0xc9,
|
||||
0x07, 0x19, 0xb7, 0x9f, 0x0a, 0xfc, 0x8e, 0xb2, 0x54, 0xc3, 0xb3, 0xe3, 0xf4, 0x48, 0xdb, 0xe9,
|
||||
0xf9, 0xc9, 0x3d, 0xf3, 0xfd, 0xef, 0xdc, 0xb9, 0x63, 0xbe, 0x16, 0xf8, 0x02, 0x0d, 0x93, 0xe1,
|
||||
0x69, 0xe3, 0xea, 0x24, 0xf9, 0xeb, 0xd3, 0xc6, 0x45, 0xa9, 0xb3, 0xea, 0x4e, 0xe0, 0x3f, 0x0c,
|
||||
0x00, 0xdb, 0xdc, 0x3e, 0x72, 0x12, 0xb7, 0x4b, 0x98, 0x4d, 0x42, 0xe7, 0xc0, 0x27, 0x1e, 0x9a,
|
||||
0xd9, 0x34, 0xb6, 0x66, 0x9a, 0xbf, 0x35, 0xce, 0x04, 0x5e, 0xde, 0xdb, 0x7f, 0xaa, 0xd9, 0x4f,
|
||||
0x34, 0x39, 0x14, 0x78, 0xb9, 0xcd, 0xab, 0x58, 0x2a, 0xf0, 0xbb, 0xfa, 0xcc, 0x6b, 0x44, 0x3d,
|
||||
0xda, 0x84, 0xf5, 0xd4, 0xdd, 0x5b, 0x9b, 0x28, 0x94, 0x71, 0x4a, 0xc5, 0xc9, 0xa0, 0x31, 0xe6,
|
||||
0xd6, 0x1a, 0x73, 0x0a, 0xff, 0x5e, 0x0d, 0xde, 0x23, 0xbe, 0xd3, 0xb7, 0x39, 0x9a, 0x55, 0x39,
|
||||
0xfd, 0x8d, 0x0c, 0x7e, 0xa9, 0xb0, 0xb2, 0x2b, 0xc9, 0x7d, 0x99, 0xe7, 0xc2, 0x8c, 0x86, 0x52,
|
||||
0x81, 0xbf, 0x55, 0x0d, 0x5d, 0xe3, 0xf5, 0xc8, 0xef, 0x56, 0xb2, 0x3c, 0x49, 0xfc, 0xfa, 0xb4,
|
||||
0x31, 0x7d, 0xf7, 0xce, 0xc9, 0xa0, 0x51, 0xf7, 0x6a, 0xd5, 0x7d, 0xc2, 0x9f, 0x83, 0x79, 0xda,
|
||||
0x09, 0x23, 0x46, 0xec, 0x98, 0xb0, 0x80, 0x23, 0xa0, 0xf2, 0xfd, 0xe1, 0x50, 0xe0, 0x39, 0x8d,
|
||||
0xb7, 0x24, 0x9c, 0x0a, 0x7c, 0x4d, 0xf7, 0x81, 0x11, 0x56, 0x5c, 0xdf, 0xe5, 0x3a, 0x68, 0x95,
|
||||
0xb7, 0xc2, 0x5f, 0x1a, 0x60, 0xd1, 0xe9, 0x25, 0x91, 0x1d, 0x46, 0x2c, 0x70, 0x7c, 0xfa, 0x82,
|
||||
0xa0, 0x39, 0xe5, 0xe4, 0xd9, 0x50, 0xe0, 0x05, 0xc9, 0x7c, 0x9a, 0x13, 0x45, 0x06, 0x2a, 0xe8,
|
||||
0x57, 0x9d, 0x1c, 0x1c, 0x57, 0xe5, 0xc7, 0x66, 0x55, 0xed, 0xc2, 0x67, 0x60, 0x21, 0xa0, 0xa1,
|
||||
0xed, 0x51, 0x7e, 0x68, 0xb7, 0x19, 0x21, 0x68, 0x7e, 0xd3, 0xd8, 0x9a, 0xdb, 0x99, 0xcf, 0xcb,
|
||||
0x6a, 0x9f, 0xbe, 0x20, 0xcd, 0xad, 0xac, 0x82, 0xe6, 0x02, 0x1a, 0xee, 0x52, 0x7e, 0xb8, 0xc7,
|
||||
0x88, 0x8c, 0x68, 0x45, 0x45, 0x54, 0xc2, 0x4c, 0xab, 0xac, 0x80, 0x1d, 0x00, 0x46, 0x2f, 0x6b,
|
||||
0xb4, 0xa0, 0x0c, 0xe3, 0xdc, 0xf0, 0x8f, 0x0b, 0xa6, 0x5a, 0xad, 0x6f, 0x67, 0xbe, 0x4a, 0x5b,
|
||||
0x53, 0x81, 0x97, 0x95, 0xab, 0x11, 0x64, 0x5a, 0x25, 0x1e, 0x7e, 0x08, 0xae, 0xb8, 0x51, 0x4c,
|
||||
0x09, 0xe3, 0x68, 0x51, 0x5d, 0xac, 0x6f, 0xc8, 0x72, 0xcf, 0xa0, 0xa2, 0x53, 0x67, 0xeb, 0xfc,
|
||||
0x8a, 0x58, 0xb9, 0x00, 0xfe, 0xd3, 0x00, 0xd7, 0xe4, 0x98, 0x40, 0x98, 0x1d, 0x38, 0xc7, 0x76,
|
||||
0x4c, 0x42, 0x8f, 0x86, 0x1d, 0xfb, 0x90, 0x1e, 0xa0, 0x25, 0x65, 0xee, 0x0f, 0xf2, 0x9e, 0xae,
|
||||
0xb6, 0x94, 0xe4, 0x91, 0x73, 0xdc, 0xd2, 0x82, 0x87, 0xb4, 0x39, 0x14, 0x78, 0x35, 0x1e, 0x87,
|
||||
0x53, 0x81, 0x6f, 0xe8, 0xf6, 0x38, 0xce, 0x95, 0x6e, 0xe8, 0xc4, 0xad, 0x93, 0xe1, 0x93, 0x41,
|
||||
0x63, 0x92, 0x7f, 0x6b, 0x82, 0xf6, 0x40, 0xa6, 0xa3, 0xeb, 0xf0, 0xae, 0x4c, 0xc7, 0xf2, 0x28,
|
||||
0x1d, 0x19, 0x54, 0xa4, 0x23, 0x5b, 0x8f, 0xd2, 0x91, 0x01, 0xf0, 0x63, 0x70, 0x49, 0x0d, 0x4c,
|
||||
0x68, 0x45, 0xb5, 0xed, 0x95, 0xfc, 0xc4, 0xa4, 0xff, 0xc7, 0x92, 0x68, 0x22, 0xf9, 0x1a, 0x53,
|
||||
0x9a, 0x54, 0xe0, 0x39, 0x65, 0x4d, 0xad, 0x4c, 0x4b, 0xa3, 0xf0, 0x21, 0x58, 0xc8, 0x6a, 0xc7,
|
||||
0x23, 0x3e, 0x49, 0x08, 0x82, 0xea, 0x5e, 0xbf, 0xad, 0xc6, 0x03, 0x45, 0xec, 0x2a, 0x3c, 0x15,
|
||||
0x18, 0x96, 0xaa, 0x47, 0x83, 0xa6, 0x55, 0xd1, 0xc0, 0x63, 0x80, 0x54, 0x4b, 0x8e, 0x59, 0xd4,
|
||||
0x61, 0x84, 0xf3, 0x72, 0x6f, 0x5e, 0x55, 0xcf, 0x27, 0x5f, 0xab, 0x6b, 0x52, 0xd3, 0xca, 0x24,
|
||||
0xe5, 0x0e, 0x7d, 0x53, 0x39, 0x98, 0xc8, 0x16, 0xcf, 0x3e, 0x79, 0x33, 0xdc, 0x07, 0x8b, 0xd9,
|
||||
0xbd, 0x88, 0x9d, 0x1e, 0x27, 0x36, 0x47, 0x57, 0x95, 0xbf, 0x5b, 0xf2, 0x39, 0x34, 0xd3, 0x92,
|
||||
0xc4, 0x7e, 0xf1, 0x1c, 0x65, 0xb0, 0xb0, 0x5e, 0x91, 0x42, 0x02, 0x16, 0xe4, 0x2d, 0x93, 0x49,
|
||||
0xf5, 0xa9, 0x9b, 0x70, 0xb4, 0xa6, 0x6c, 0x7e, 0x4f, 0xda, 0x0c, 0x9c, 0xe3, 0xfb, 0x39, 0x9e,
|
||||
0x0a, 0x8c, 0x75, 0x81, 0x95, 0xc0, 0x52, 0xb1, 0xdf, 0xba, 0x9b, 0x3b, 0x90, 0x4d, 0xed, 0xd6,
|
||||
0x5d, 0xab, 0xb2, 0x1b, 0x7a, 0xe0, 0xaa, 0x47, 0xb9, 0x6c, 0xc2, 0x36, 0x8f, 0x1d, 0xc6, 0x89,
|
||||
0xad, 0x5e, 0xed, 0xe8, 0x9a, 0x3a, 0x09, 0x35, 0x37, 0x65, 0xfc, 0xbe, 0xa2, 0xd5, 0xd0, 0x50,
|
||||
0xcc, 0x4d, 0xe3, 0x94, 0x69, 0x4d, 0xd0, 0x97, 0xbd, 0x24, 0x24, 0x88, 0x6d, 0x1a, 0x7a, 0xe4,
|
||||
0x98, 0x70, 0x74, 0x7d, 0xcc, 0xcb, 0x13, 0x12, 0xc4, 0x0f, 0x34, 0x5b, 0xf7, 0x52, 0xa2, 0x46,
|
||||
0x5e, 0x4a, 0x20, 0xdc, 0x01, 0x97, 0xd5, 0x01, 0x78, 0x08, 0x29, 0xbb, 0xeb, 0x43, 0x81, 0x33,
|
||||
0xa4, 0x78, 0x99, 0xeb, 0xa5, 0x69, 0x65, 0x38, 0x4c, 0xc0, 0xf5, 0x23, 0xe2, 0x1c, 0xda, 0xf2,
|
||||
0x56, 0xdb, 0x49, 0x97, 0x11, 0xde, 0x8d, 0x7c, 0xcf, 0x8e, 0xdd, 0x04, 0xdd, 0x50, 0x09, 0x97,
|
||||
0x9d, 0xfc, 0xaa, 0x94, 0x7c, 0xdf, 0xe1, 0xdd, 0x27, 0xb9, 0xa0, 0xe5, 0x26, 0xa9, 0xc0, 0xeb,
|
||||
0xca, 0xe4, 0x24, 0xb2, 0x38, 0xd4, 0x89, 0x5b, 0xe1, 0x7d, 0x30, 0x17, 0x38, 0xec, 0x90, 0x30,
|
||||
0x3b, 0x74, 0x02, 0x82, 0xd6, 0xd5, 0xd8, 0x64, 0xca, 0x76, 0xa6, 0xe1, 0x4f, 0x9d, 0x80, 0x14,
|
||||
0xed, 0x6c, 0x04, 0x99, 0x56, 0x89, 0x87, 0x7d, 0xb0, 0x2e, 0xbf, 0x44, 0xec, 0xe8, 0x28, 0x24,
|
||||
0x8c, 0x77, 0x69, 0x6c, 0xb7, 0x59, 0x14, 0xd8, 0xb1, 0xc3, 0x48, 0x98, 0xa0, 0x9b, 0x2a, 0x05,
|
||||
0x1f, 0x0c, 0x05, 0xbe, 0x2e, 0x55, 0x8f, 0x73, 0xd1, 0x1e, 0x8b, 0x82, 0x96, 0x92, 0xa4, 0x02,
|
||||
0xbf, 0x99, 0x77, 0xbc, 0x49, 0xbc, 0x69, 0x7d, 0xd5, 0x4e, 0xf8, 0x2b, 0x03, 0xac, 0x04, 0x91,
|
||||
0x67, 0x27, 0x34, 0x20, 0xf6, 0x11, 0x0d, 0xbd, 0xe8, 0xc8, 0xe6, 0xe8, 0x0d, 0x95, 0xb0, 0x9f,
|
||||
0x9e, 0x09, 0xbc, 0x62, 0x39, 0x47, 0x8f, 0x22, 0xef, 0x09, 0x0d, 0xc8, 0x53, 0xc5, 0xca, 0xd7,
|
||||
0xf5, 0x62, 0x50, 0x41, 0x8a, 0xe1, 0xb2, 0x0a, 0xe7, 0x99, 0x3b, 0x19, 0x34, 0xc6, 0xad, 0x58,
|
||||
0x35, 0x1b, 0xf0, 0xa5, 0x01, 0xd6, 0xb2, 0x32, 0x71, 0x7b, 0x4c, 0xc6, 0x66, 0x1f, 0x31, 0x9a,
|
||||
0x10, 0x8e, 0xde, 0x54, 0xc1, 0xfc, 0x50, 0xb6, 0x5e, 0x7d, 0xe1, 0x33, 0xfe, 0xa9, 0xa2, 0x53,
|
||||
0x81, 0xbf, 0x59, 0xaa, 0x9a, 0x0a, 0x57, 0x2a, 0x9e, 0x9d, 0x52, 0xed, 0x18, 0x3b, 0xd6, 0x24,
|
||||
0x4b, 0xb2, 0x89, 0xe5, 0x77, 0xbb, 0x2d, 0x3f, 0x7b, 0xd0, 0xc6, 0xa8, 0x89, 0x65, 0xc4, 0x9e,
|
||||
0xc4, 0x8b, 0xe2, 0x2f, 0x83, 0xa6, 0x55, 0xd1, 0x40, 0x1f, 0x2c, 0xab, 0xcf, 0x51, 0x5b, 0xf6,
|
||||
0x02, 0x5b, 0xf7, 0x57, 0xac, 0xfa, 0xeb, 0xb5, 0xbc, 0xbf, 0x36, 0x25, 0x3f, 0x6a, 0xb2, 0x6a,
|
||||
0x6c, 0x3f, 0xa8, 0x60, 0x45, 0x66, 0xab, 0xb0, 0x69, 0xd5, 0x74, 0xf0, 0x73, 0x03, 0xac, 0xa8,
|
||||
0x2b, 0xa4, 0xbe, 0x66, 0x6d, 0xfd, 0x39, 0x8b, 0x36, 0x95, 0xbf, 0x55, 0xf9, 0x89, 0x70, 0x3f,
|
||||
0x8a, 0xfb, 0x96, 0xe4, 0x1e, 0x29, 0xaa, 0xf9, 0x50, 0x4e, 0x5d, 0x6e, 0x15, 0x4c, 0x05, 0xde,
|
||||
0x2a, 0xae, 0x51, 0x09, 0x2f, 0xa5, 0x91, 0x27, 0x4e, 0xe8, 0x39, 0xcc, 0x33, 0x5f, 0x9f, 0x36,
|
||||
0x66, 0xf2, 0x85, 0x55, 0x37, 0x04, 0xff, 0x2c, 0xc3, 0x71, 0x64, 0x03, 0x25, 0x21, 0xa7, 0x09,
|
||||
0x7d, 0x2e, 0x33, 0x8a, 0xde, 0x52, 0xe9, 0x3c, 0x96, 0x23, 0xe0, 0x7d, 0x87, 0x93, 0xfd, 0x9c,
|
||||
0xdb, 0x53, 0x23, 0xa0, 0x5b, 0x85, 0x52, 0x81, 0xd7, 0x74, 0x30, 0x55, 0x5c, 0x8e, 0x3b, 0x63,
|
||||
0xda, 0x71, 0x48, 0x4e, 0x7c, 0x35, 0x27, 0x56, 0x4d, 0xc3, 0xe1, 0x9f, 0x0c, 0xb0, 0xdc, 0x8e,
|
||||
0x7c, 0x3f, 0x3a, 0xb2, 0x3f, 0xeb, 0x85, 0xae, 0x1c, 0x47, 0x38, 0x32, 0x47, 0x51, 0xfe, 0x20,
|
||||
0x07, 0x3f, 0xe6, 0xbb, 0x94, 0x71, 0x19, 0xe5, 0x67, 0x55, 0xa8, 0x88, 0xb2, 0x86, 0xab, 0x28,
|
||||
0xeb, 0xda, 0x71, 0x48, 0x46, 0x59, 0x73, 0x62, 0x2d, 0xe9, 0x88, 0x0a, 0x18, 0x1e, 0x82, 0x59,
|
||||
0x46, 0x1c, 0xcf, 0x8e, 0x42, 0xbf, 0x8f, 0xfe, 0xba, 0xa7, 0xc2, 0x7b, 0x74, 0x26, 0x30, 0xdc,
|
||||
0x25, 0x31, 0x23, 0xae, 0x93, 0x10, 0xcf, 0x22, 0x8e, 0xf7, 0x38, 0xf4, 0xfb, 0x43, 0x81, 0x8d,
|
||||
0x5b, 0xc5, 0x27, 0x38, 0x8b, 0xd4, 0x24, 0xf8, 0x5e, 0x14, 0x50, 0xd9, 0xab, 0x93, 0xbe, 0xfa,
|
||||
0x04, 0x1f, 0x43, 0x91, 0x61, 0xcd, 0xb0, 0xcc, 0x00, 0xfc, 0x05, 0x58, 0xa9, 0x8c, 0x87, 0xaa,
|
||||
0x7f, 0xfe, 0x4d, 0x3a, 0x35, 0x9a, 0x9f, 0x9c, 0x09, 0x8c, 0x46, 0x4e, 0x1f, 0x8d, 0x26, 0xbf,
|
||||
0x96, 0x9b, 0xe4, 0xae, 0x37, 0xea, 0x33, 0x62, 0xcb, 0x4d, 0x4a, 0x11, 0x20, 0xc3, 0x5a, 0xac,
|
||||
0x92, 0xf0, 0x27, 0xe0, 0x8a, 0x7e, 0x5f, 0x72, 0xf4, 0xc5, 0x9e, 0xaa, 0xf5, 0x8f, 0x64, 0xe3,
|
||||
0x19, 0x39, 0xd2, 0x73, 0x10, 0xaf, 0x3e, 0x5c, 0xb6, 0xa5, 0x64, 0x3a, 0x2b, 0x70, 0x64, 0x58,
|
||||
0xb9, 0xbd, 0xe6, 0xc3, 0x57, 0x5f, 0x6e, 0x4c, 0x0d, 0xbe, 0xdc, 0x98, 0x7a, 0x75, 0xb6, 0x61,
|
||||
0x0c, 0xce, 0x36, 0x8c, 0xdf, 0x9d, 0x6f, 0x4c, 0xfd, 0xf1, 0x7c, 0xc3, 0x18, 0x9c, 0x6f, 0x4c,
|
||||
0xfd, 0xfb, 0x7c, 0x63, 0xea, 0xd9, 0x3b, 0xff, 0xc7, 0x9f, 0x1e, 0xba, 0x5c, 0x0f, 0x2e, 0xab,
|
||||
0x3f, 0x3f, 0xde, 0xff, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe5, 0x04, 0x99, 0x1a, 0x13,
|
||||
0x00, 0x00,
|
||||
// 2043 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0xcd, 0x6f, 0x24, 0x47,
|
||||
0x15, 0x77, 0x7b, 0xbf, 0xec, 0xf2, 0x77, 0x79, 0xbd, 0xdb, 0xf1, 0x26, 0x53, 0x93, 0x66, 0x36,
|
||||
0x38, 0x51, 0xe2, 0xdd, 0x75, 0x10, 0x12, 0x2b, 0x16, 0xc8, 0xd8, 0xb1, 0x58, 0x16, 0x67, 0x47,
|
||||
0xed, 0x85, 0x15, 0x01, 0xa9, 0xe9, 0xe9, 0xae, 0x99, 0xa9, 0xb8, 0xbf, 0xa8, 0xea, 0x59, 0x7b,
|
||||
0xf6, 0x10, 0x2d, 0x17, 0x04, 0x22, 0x07, 0x64, 0x0e, 0xdc, 0x50, 0x24, 0x10, 0x82, 0xfc, 0x03,
|
||||
0x48, 0xfc, 0x05, 0x7b, 0x41, 0x9e, 0x13, 0x42, 0x1c, 0x4a, 0x8a, 0xf7, 0x36, 0xc7, 0x3e, 0xfa,
|
||||
0x84, 0xaa, 0xaa, 0xbb, 0xa7, 0xbb, 0x67, 0x22, 0x21, 0x71, 0x9b, 0xfa, 0xfd, 0x5e, 0xbd, 0xf7,
|
||||
0xeb, 0x57, 0xaf, 0x5e, 0xbf, 0x1e, 0xd0, 0xf0, 0x48, 0xfb, 0x8e, 0x13, 0x06, 0x1d, 0xd2, 0xbd,
|
||||
0xd3, 0x09, 0x3d, 0x17, 0x53, 0xb5, 0xe8, 0x53, 0x3b, 0x26, 0x61, 0xb0, 0x1d, 0xd1, 0x30, 0x0e,
|
||||
0xe1, 0x55, 0x05, 0x6e, 0xde, 0x9a, 0xb0, 0x8e, 0x07, 0x11, 0x56, 0x46, 0x9b, 0x1b, 0x05, 0x92,
|
||||
0x91, 0xe7, 0x19, 0xbc, 0x59, 0x80, 0xa3, 0xbe, 0xe7, 0x85, 0xd4, 0xc5, 0x34, 0xe5, 0xb6, 0x0a,
|
||||
0xdc, 0x33, 0x4c, 0x19, 0x09, 0x03, 0x12, 0x74, 0xa7, 0x28, 0xd8, 0x44, 0x05, 0xcb, 0xb6, 0x17,
|
||||
0x3a, 0x47, 0x55, 0x57, 0x50, 0x18, 0x74, 0xd8, 0x1d, 0x21, 0x88, 0xa5, 0xd8, 0xeb, 0x29, 0xe6,
|
||||
0x84, 0xd1, 0x80, 0xda, 0x41, 0x17, 0xfb, 0x38, 0xee, 0x85, 0x6e, 0xca, 0xce, 0xe3, 0x93, 0x58,
|
||||
0xfd, 0x34, 0xfe, 0x75, 0x09, 0xbc, 0xb6, 0x2f, 0x9f, 0x67, 0x0f, 0x3f, 0x23, 0x0e, 0xde, 0x2d,
|
||||
0x2a, 0x80, 0x5f, 0x68, 0x60, 0xde, 0x95, 0xb8, 0x45, 0x5c, 0x5d, 0xab, 0x6b, 0x5b, 0x8b, 0xcd,
|
||||
0xcf, 0xb4, 0x97, 0x1c, 0xcd, 0xfc, 0x87, 0xa3, 0x6f, 0x74, 0x49, 0xdc, 0xeb, 0xb7, 0xb7, 0x9d,
|
||||
0xd0, 0xbf, 0xc3, 0x06, 0x81, 0x13, 0xf7, 0x48, 0xd0, 0x2d, 0xfc, 0x12, 0x12, 0x64, 0x10, 0x27,
|
||||
0xf4, 0xb6, 0x95, 0xf7, 0x87, 0x7b, 0xe7, 0x1c, 0xcd, 0x65, 0xbf, 0x47, 0x1c, 0xcd, 0xb9, 0xe9,
|
||||
0xef, 0x84, 0xa3, 0xa5, 0x13, 0xdf, 0xbb, 0x6f, 0x10, 0xf7, 0x5d, 0x3b, 0x8e, 0xa9, 0x31, 0x3a,
|
||||
0x6b, 0x5c, 0x4b, 0x7f, 0x27, 0x67, 0x8d, 0xdc, 0xee, 0xd7, 0xc3, 0x86, 0x76, 0x3a, 0x6c, 0xe4,
|
||||
0x3e, 0xcc, 0x8c, 0x71, 0xe1, 0x5f, 0x34, 0xb0, 0x44, 0x82, 0x98, 0x86, 0x6e, 0xdf, 0xc1, 0xae,
|
||||
0xd5, 0x1e, 0xe8, 0xb3, 0x52, 0xf0, 0x8b, 0xff, 0x4b, 0xf0, 0x88, 0xa3, 0xc5, 0xb1, 0xd7, 0xe6,
|
||||
0x20, 0xe1, 0xe8, 0xa6, 0x12, 0x5a, 0x00, 0x73, 0xc9, 0x6b, 0x13, 0xa8, 0x10, 0x6c, 0x96, 0x3c,
|
||||
0x40, 0x07, 0xac, 0xe3, 0xc0, 0xa1, 0x83, 0x48, 0xe4, 0xd8, 0x8a, 0x6c, 0xc6, 0x8e, 0x43, 0xea,
|
||||
0xea, 0x97, 0xea, 0xda, 0xd6, 0x7c, 0x73, 0x67, 0xc4, 0x11, 0x1c, 0xd3, 0xad, 0x94, 0x4d, 0x38,
|
||||
0xd2, 0x65, 0xd8, 0x49, 0xca, 0x30, 0xa7, 0xd8, 0x1b, 0x7f, 0xac, 0x83, 0x75, 0x75, 0xb0, 0xe5,
|
||||
0x23, 0x3d, 0x04, 0xb3, 0xe9, 0x51, 0xce, 0x37, 0x77, 0xcf, 0x39, 0x9a, 0x95, 0x8f, 0x38, 0x4b,
|
||||
0x44, 0x84, 0x5a, 0xe9, 0x04, 0xea, 0x41, 0xe8, 0xe2, 0x8e, 0xdd, 0xf7, 0xe2, 0xfb, 0x46, 0x4c,
|
||||
0xfb, 0xb8, 0x78, 0x24, 0xa7, 0xc3, 0xc6, 0xec, 0xc3, 0xbd, 0xcf, 0xc5, 0xb3, 0xcd, 0x12, 0x17,
|
||||
0xfe, 0x08, 0x5c, 0xf1, 0xec, 0x36, 0xf6, 0x64, 0xc6, 0xe7, 0x9b, 0xdf, 0x1d, 0x71, 0xa4, 0x80,
|
||||
0x84, 0xa3, 0xba, 0x74, 0x2a, 0x57, 0xa9, 0x5f, 0x8a, 0x59, 0x6c, 0xd3, 0xf8, 0xbe, 0xd1, 0xb1,
|
||||
0x3d, 0x26, 0xdd, 0x82, 0x31, 0xfd, 0x62, 0xd8, 0x98, 0x31, 0xd5, 0x66, 0xd8, 0x05, 0x2b, 0x1d,
|
||||
0xe2, 0x61, 0x36, 0x60, 0x31, 0xf6, 0x2d, 0x51, 0xdf, 0x32, 0x49, 0xcb, 0x3b, 0x70, 0xbb, 0xc3,
|
||||
0xb6, 0xf7, 0x73, 0xea, 0xc9, 0x20, 0xc2, 0xcd, 0x77, 0x46, 0x1c, 0x2d, 0x77, 0x4a, 0x58, 0xc2,
|
||||
0xd1, 0x75, 0x19, 0xbd, 0x0c, 0x1b, 0x66, 0xc5, 0x0e, 0x1e, 0x80, 0xcb, 0x91, 0x1d, 0xf7, 0xf4,
|
||||
0xcb, 0x52, 0xfe, 0xb7, 0x46, 0x1c, 0xc9, 0x75, 0xc2, 0xd1, 0x2d, 0xb9, 0x5f, 0x2c, 0x52, 0xf1,
|
||||
0x79, 0x4a, 0x3e, 0x15, 0xc2, 0xe7, 0x73, 0xe6, 0xe2, 0xac, 0xa1, 0x7d, 0x6a, 0xca, 0x6d, 0xb0,
|
||||
0x05, 0x2e, 0x4b, 0xb1, 0x57, 0x52, 0xb1, 0xea, 0xf6, 0x6e, 0xab, 0xe3, 0x90, 0x62, 0xb7, 0x44,
|
||||
0x88, 0x58, 0x49, 0x5c, 0x91, 0x21, 0xc4, 0x22, 0x2f, 0xa3, 0xf9, 0x7c, 0x65, 0x4a, 0x2b, 0xf8,
|
||||
0x33, 0x70, 0x4d, 0xd5, 0x39, 0xd3, 0xaf, 0xd6, 0x2f, 0x6d, 0x2d, 0xec, 0xbc, 0x59, 0x76, 0x3a,
|
||||
0xe5, 0xf2, 0x36, 0x91, 0x28, 0xfb, 0x11, 0x47, 0xd9, 0xce, 0x84, 0xa3, 0x45, 0x19, 0x4a, 0xad,
|
||||
0x0d, 0x33, 0x23, 0xe0, 0xef, 0x35, 0xb0, 0x46, 0x31, 0x73, 0xec, 0xc0, 0x22, 0x41, 0x8c, 0xe9,
|
||||
0x33, 0xdb, 0xb3, 0x98, 0x7e, 0xad, 0xae, 0x6d, 0x5d, 0x69, 0x76, 0x47, 0x1c, 0xad, 0x28, 0xf2,
|
||||
0x61, 0xca, 0x1d, 0x26, 0x1c, 0xbd, 0x2d, 0x3d, 0x55, 0xf0, 0x6a, 0x8a, 0xde, 0xff, 0xe6, 0xdd,
|
||||
0xbb, 0xc6, 0x05, 0x47, 0x97, 0x48, 0x10, 0x8f, 0xce, 0x1a, 0xd7, 0xa7, 0x99, 0x5f, 0x9c, 0x35,
|
||||
0x2e, 0x0b, 0x3b, 0xb3, 0x1a, 0x04, 0xfe, 0x43, 0x03, 0xb0, 0xc3, 0xac, 0x63, 0x3b, 0x76, 0x7a,
|
||||
0x98, 0x5a, 0x38, 0xb0, 0xdb, 0x1e, 0x76, 0xf5, 0xb9, 0xba, 0xb6, 0x35, 0xd7, 0xfc, 0xad, 0x76,
|
||||
0xce, 0xd1, 0xea, 0xfe, 0xe1, 0x53, 0xc5, 0x7e, 0xa8, 0xc8, 0x11, 0x47, 0xab, 0x1d, 0x56, 0xc6,
|
||||
0x12, 0x8e, 0xde, 0x51, 0x45, 0x50, 0x21, 0xaa, 0x6a, 0xb3, 0x1a, 0xdf, 0x98, 0x6a, 0x28, 0x74,
|
||||
0x0a, 0x8b, 0xd3, 0x61, 0x63, 0x22, 0xac, 0x39, 0x11, 0x14, 0xfe, 0xbd, 0x2c, 0xde, 0xc5, 0x9e,
|
||||
0x3d, 0xb0, 0x98, 0x3e, 0x2f, 0x73, 0xfa, 0x1b, 0x21, 0x7e, 0x25, 0xf7, 0xb2, 0x27, 0xc8, 0x43,
|
||||
0x91, 0xe7, 0xdc, 0x8d, 0x82, 0x12, 0x8e, 0xbe, 0x5e, 0x96, 0xae, 0xf0, 0xaa, 0xf2, 0x7b, 0xa5,
|
||||
0x2c, 0x4f, 0x33, 0xbe, 0x38, 0x6b, 0xcc, 0xde, 0xbb, 0x7b, 0x3a, 0x6c, 0x54, 0xa3, 0x9a, 0xd5,
|
||||
0x98, 0xf0, 0xe7, 0x60, 0x91, 0x74, 0x83, 0x90, 0x62, 0x2b, 0xc2, 0xd4, 0x67, 0x3a, 0x90, 0xf9,
|
||||
0x7e, 0x30, 0xe2, 0x68, 0x41, 0xe1, 0x2d, 0x01, 0x27, 0x1c, 0xdd, 0x50, 0xdd, 0x62, 0x8c, 0xe5,
|
||||
0xe5, 0xbb, 0x5a, 0x05, 0xcd, 0xe2, 0x56, 0xf8, 0x4b, 0x0d, 0x2c, 0xdb, 0xfd, 0x38, 0xb4, 0x82,
|
||||
0x90, 0xfa, 0xb6, 0x47, 0x9e, 0x63, 0x7d, 0x41, 0x06, 0xf9, 0x78, 0xc4, 0xd1, 0x92, 0x60, 0x3e,
|
||||
0xca, 0x88, 0x3c, 0x03, 0x25, 0xf4, 0xab, 0x4e, 0x0e, 0x4e, 0x5a, 0x65, 0xc7, 0x66, 0x96, 0xfd,
|
||||
0xc2, 0x10, 0x2c, 0xf9, 0x24, 0xb0, 0x5c, 0xc2, 0x8e, 0xac, 0x0e, 0xc5, 0x58, 0x5f, 0xac, 0x6b,
|
||||
0x5b, 0x0b, 0x3b, 0x8b, 0xd9, 0xb5, 0x3a, 0x24, 0xcf, 0x71, 0xf3, 0x41, 0x7a, 0x83, 0x16, 0x7c,
|
||||
0x12, 0xec, 0x11, 0x76, 0xb4, 0x4f, 0xb1, 0x50, 0x84, 0xa4, 0xa2, 0x02, 0x56, 0x3c, 0x8a, 0xfa,
|
||||
0x6d, 0xe3, 0xe2, 0xac, 0x71, 0xe9, 0x5e, 0xfd, 0xb6, 0x59, 0xdc, 0x06, 0xbb, 0x00, 0x8c, 0xdf,
|
||||
0xf3, 0xfa, 0x92, 0x8c, 0x86, 0xb2, 0x68, 0x3f, 0xce, 0x99, 0xf2, 0x15, 0x7e, 0x2b, 0x15, 0x50,
|
||||
0xd8, 0x9a, 0x70, 0xb4, 0x2a, 0xe3, 0x8f, 0x21, 0xc3, 0x2c, 0xf0, 0xf0, 0x01, 0xb8, 0xe6, 0x84,
|
||||
0x11, 0xc1, 0x94, 0xe9, 0xcb, 0xb2, 0xda, 0xbe, 0x26, 0x7a, 0x40, 0x0a, 0xe5, 0xaf, 0xd9, 0x74,
|
||||
0x9d, 0xd5, 0x8d, 0x99, 0x19, 0xc0, 0x7f, 0x6a, 0xe0, 0x86, 0x98, 0x30, 0x30, 0xb5, 0x7c, 0xfb,
|
||||
0xc4, 0x8a, 0x70, 0xe0, 0x92, 0xa0, 0x6b, 0x1d, 0x91, 0xb6, 0xbe, 0x22, 0xdd, 0xfd, 0x41, 0x14,
|
||||
0xef, 0x7a, 0x4b, 0x9a, 0x1c, 0xd8, 0x27, 0x2d, 0x65, 0xf0, 0x88, 0x34, 0x47, 0x1c, 0xad, 0x47,
|
||||
0x93, 0x70, 0xc2, 0xd1, 0x6b, 0xaa, 0x89, 0x4e, 0x72, 0x85, 0xb2, 0x9d, 0xba, 0x75, 0x3a, 0x7c,
|
||||
0x3a, 0x6c, 0x4c, 0x8b, 0x6f, 0x4e, 0xb1, 0x6d, 0x8b, 0x74, 0xf4, 0x6c, 0xd6, 0x13, 0xe9, 0x58,
|
||||
0x1d, 0xa7, 0x23, 0x85, 0xf2, 0x74, 0xa4, 0xeb, 0x71, 0x3a, 0x52, 0x00, 0x7e, 0x00, 0xae, 0xc8,
|
||||
0x59, 0x4b, 0x5f, 0x93, 0xbd, 0x7c, 0x2d, 0x3b, 0x31, 0x11, 0xff, 0xb1, 0x20, 0x9a, 0xba, 0x78,
|
||||
0xd9, 0x49, 0x9b, 0x84, 0xa3, 0x05, 0xe9, 0x4d, 0xae, 0x0c, 0x53, 0xa1, 0xf0, 0x11, 0x58, 0x4a,
|
||||
0x2f, 0x94, 0x8b, 0x3d, 0x1c, 0x63, 0x1d, 0xca, 0x62, 0x7f, 0x4b, 0x4e, 0x16, 0x92, 0xd8, 0x93,
|
||||
0x78, 0xc2, 0x11, 0x2c, 0x5c, 0x29, 0x05, 0x1a, 0x66, 0xc9, 0x06, 0x9e, 0x00, 0x5d, 0xf6, 0xe9,
|
||||
0x88, 0x86, 0x5d, 0x8a, 0x19, 0x2b, 0x36, 0xec, 0x75, 0xf9, 0x7c, 0xe2, 0xe5, 0xbb, 0x21, 0x6c,
|
||||
0x5a, 0xa9, 0x49, 0xb1, 0x6d, 0xab, 0xd7, 0xd9, 0x54, 0x36, 0x7f, 0xf6, 0xe9, 0x9b, 0xe1, 0x21,
|
||||
0x58, 0x4e, 0xeb, 0x22, 0xb2, 0xfb, 0x0c, 0x5b, 0x4c, 0xbf, 0x2e, 0xe3, 0xbd, 0x27, 0x9e, 0x43,
|
||||
0x31, 0x2d, 0x41, 0x1c, 0xe6, 0xcf, 0x51, 0x04, 0x73, 0xef, 0x25, 0x53, 0x88, 0xc1, 0x92, 0xa8,
|
||||
0x32, 0x91, 0x54, 0x8f, 0x38, 0x31, 0xd3, 0x37, 0xa4, 0xcf, 0xef, 0x09, 0x9f, 0xbe, 0x7d, 0xb2,
|
||||
0x9b, 0xe1, 0xe3, 0x5b, 0x57, 0x00, 0xa7, 0x76, 0x40, 0xd5, 0xe9, 0xcc, 0xd2, 0x6e, 0xe8, 0x82,
|
||||
0xeb, 0x2e, 0x61, 0xa2, 0x33, 0x5b, 0x2c, 0xb2, 0x29, 0xc3, 0x96, 0x1c, 0x00, 0xf4, 0x1b, 0xf2,
|
||||
0x24, 0xe4, 0xc8, 0x95, 0xf2, 0x87, 0x92, 0x96, 0xa3, 0x45, 0x3e, 0x72, 0x4d, 0x52, 0x86, 0x39,
|
||||
0xc5, 0xbe, 0x18, 0x25, 0xc6, 0x7e, 0x64, 0x91, 0xc0, 0xc5, 0x27, 0x98, 0xe9, 0x37, 0x27, 0xa2,
|
||||
0x3c, 0xc1, 0x7e, 0xf4, 0x50, 0xb1, 0xd5, 0x28, 0x05, 0x6a, 0x1c, 0xa5, 0x00, 0xc2, 0x1d, 0x70,
|
||||
0x55, 0x1e, 0x80, 0xab, 0xeb, 0xd2, 0xef, 0xe6, 0x88, 0xa3, 0x14, 0xc9, 0xdf, 0xf0, 0x6a, 0x69,
|
||||
0x98, 0x29, 0x0e, 0x63, 0x70, 0xf3, 0x18, 0xdb, 0x47, 0x96, 0xa8, 0x6a, 0x2b, 0xee, 0x51, 0xcc,
|
||||
0x7a, 0xa1, 0xe7, 0x5a, 0x91, 0x13, 0xeb, 0xaf, 0xc9, 0x84, 0x8b, 0xf6, 0x7e, 0x5d, 0x98, 0x7c,
|
||||
0xdf, 0x66, 0xbd, 0x27, 0x99, 0x41, 0xcb, 0x89, 0x13, 0x8e, 0x36, 0xa5, 0xcb, 0x69, 0x64, 0x7e,
|
||||
0xa8, 0x53, 0xb7, 0xc2, 0x5d, 0xb0, 0xe0, 0xdb, 0xf4, 0x08, 0x53, 0x2b, 0xb0, 0x7d, 0xac, 0x6f,
|
||||
0xca, 0xe1, 0xca, 0x10, 0xed, 0x4c, 0xc1, 0x1f, 0xd9, 0x3e, 0xce, 0xdb, 0xd9, 0x18, 0x32, 0xcc,
|
||||
0x02, 0x0f, 0x07, 0x60, 0x53, 0x7c, 0xc4, 0x58, 0xe1, 0x71, 0x80, 0x29, 0xeb, 0x91, 0xc8, 0xea,
|
||||
0xd0, 0xd0, 0xb7, 0x22, 0x9b, 0xe2, 0x20, 0xd6, 0x6f, 0xc9, 0x14, 0x7c, 0x7b, 0xc4, 0xd1, 0x4d,
|
||||
0x61, 0xf5, 0x38, 0x33, 0xda, 0xa7, 0xa1, 0xdf, 0x92, 0x26, 0x09, 0x47, 0x6f, 0x64, 0x1d, 0x6f,
|
||||
0x1a, 0x6f, 0x98, 0x5f, 0xb5, 0x13, 0xfe, 0x4a, 0x03, 0x6b, 0x7e, 0xe8, 0x5a, 0x31, 0xf1, 0xb1,
|
||||
0x75, 0x4c, 0x02, 0x37, 0x3c, 0xb6, 0x98, 0xfe, 0xba, 0x4c, 0xd8, 0x4f, 0xcf, 0x39, 0x5a, 0x33,
|
||||
0xed, 0xe3, 0x83, 0xd0, 0x7d, 0x42, 0x7c, 0xfc, 0x54, 0xb2, 0xe2, 0x1d, 0xbe, 0xec, 0x97, 0x90,
|
||||
0x7c, 0x04, 0x2d, 0xc3, 0x59, 0xe6, 0x4e, 0x87, 0x8d, 0x49, 0x2f, 0x66, 0xc5, 0x07, 0x7c, 0xa1,
|
||||
0x81, 0x8d, 0xf4, 0x9a, 0x38, 0x7d, 0x2a, 0xb4, 0x59, 0xc7, 0x94, 0xc4, 0x98, 0xe9, 0x6f, 0x48,
|
||||
0x31, 0x3f, 0x14, 0xad, 0x57, 0x15, 0x7c, 0xca, 0x3f, 0x95, 0x74, 0xc2, 0xd1, 0xed, 0xc2, 0xad,
|
||||
0x29, 0x71, 0x85, 0xcb, 0xb3, 0x53, 0xb8, 0x3b, 0xda, 0x8e, 0x39, 0xcd, 0x93, 0x68, 0x62, 0x59,
|
||||
0x6d, 0x77, 0xc4, 0x17, 0x93, 0x5e, 0x1b, 0x37, 0xb1, 0x94, 0xd8, 0x17, 0x78, 0x7e, 0xf9, 0x8b,
|
||||
0xa0, 0x61, 0x96, 0x6c, 0xa0, 0x07, 0x56, 0xe5, 0x97, 0xac, 0x25, 0x7a, 0x81, 0xa5, 0xfa, 0x2b,
|
||||
0x92, 0xfd, 0xf5, 0x46, 0xd6, 0x5f, 0x9b, 0x82, 0x1f, 0x37, 0x59, 0x39, 0xdc, 0xb7, 0x4b, 0x58,
|
||||
0x9e, 0xd9, 0x32, 0x6c, 0x98, 0x15, 0x3b, 0xf8, 0x99, 0x06, 0xd6, 0x64, 0x09, 0xc9, 0x0f, 0x61,
|
||||
0x4b, 0x7d, 0x09, 0xeb, 0x75, 0x19, 0x6f, 0x5d, 0x7c, 0x48, 0xec, 0x86, 0xd1, 0xc0, 0x14, 0xdc,
|
||||
0x81, 0xa4, 0x9a, 0x8f, 0xc4, 0x28, 0xe6, 0x94, 0xc1, 0x84, 0xa3, 0xad, 0xbc, 0x8c, 0x0a, 0x78,
|
||||
0x21, 0x8d, 0x2c, 0xb6, 0x03, 0xd7, 0xa6, 0xae, 0x78, 0xff, 0xcf, 0x65, 0x0b, 0xb3, 0xea, 0x08,
|
||||
0xfe, 0x59, 0xc8, 0xb1, 0x45, 0x03, 0xc5, 0x01, 0x23, 0x31, 0x79, 0x26, 0x32, 0xaa, 0xbf, 0x29,
|
||||
0xd3, 0x79, 0x22, 0xe6, 0xc2, 0x5d, 0x9b, 0xe1, 0xc3, 0x8c, 0xdb, 0x97, 0x73, 0xa1, 0x53, 0x86,
|
||||
0x12, 0x8e, 0x36, 0x94, 0x98, 0x32, 0x2e, 0x66, 0xa0, 0x09, 0xdb, 0x49, 0x48, 0x8c, 0x81, 0x95,
|
||||
0x20, 0x66, 0xc5, 0x86, 0xc1, 0x3f, 0x69, 0x60, 0xb5, 0x13, 0x7a, 0x5e, 0x78, 0x6c, 0x7d, 0xd2,
|
||||
0x0f, 0x1c, 0x31, 0x8e, 0x30, 0xdd, 0x18, 0xab, 0xfc, 0x41, 0x06, 0x7e, 0xc0, 0xf6, 0x08, 0x65,
|
||||
0x42, 0xe5, 0x27, 0x65, 0x28, 0x57, 0x59, 0xc1, 0xa5, 0xca, 0xaa, 0xed, 0x24, 0x24, 0x54, 0x56,
|
||||
0x82, 0x98, 0x2b, 0x4a, 0x51, 0x0e, 0xc3, 0x23, 0x30, 0x4f, 0xb1, 0xed, 0x5a, 0x61, 0xe0, 0x0d,
|
||||
0xf4, 0xbf, 0xee, 0x4b, 0x79, 0x07, 0xe7, 0x1c, 0xc1, 0x3d, 0x1c, 0x51, 0xec, 0xd8, 0x31, 0x76,
|
||||
0x4d, 0x6c, 0xbb, 0x8f, 0x03, 0x6f, 0x30, 0xe2, 0x48, 0x7b, 0x2f, 0xff, 0x7a, 0xa7, 0xa1, 0x1c,
|
||||
0x0f, 0xdf, 0x0d, 0x7d, 0x22, 0x7a, 0x75, 0x3c, 0x90, 0x5f, 0xef, 0x13, 0xa8, 0xae, 0x99, 0x73,
|
||||
0x34, 0x75, 0x00, 0x7f, 0x01, 0xd6, 0x4a, 0x33, 0xa3, 0xec, 0x9f, 0x7f, 0x13, 0x41, 0xb5, 0xe6,
|
||||
0x87, 0xe7, 0x1c, 0xe9, 0xe3, 0xa0, 0x07, 0xe3, 0xc9, 0xaf, 0xe5, 0xc4, 0x59, 0xe8, 0x5a, 0x75,
|
||||
0x70, 0x6c, 0x39, 0x71, 0x41, 0x81, 0xae, 0x99, 0xcb, 0x65, 0x12, 0xfe, 0x04, 0x5c, 0x53, 0xef,
|
||||
0x4b, 0xa6, 0x7f, 0xb1, 0x2f, 0xef, 0xfa, 0x77, 0x44, 0xe3, 0x19, 0x07, 0x52, 0x73, 0x10, 0x2b,
|
||||
0x3f, 0x5c, 0xba, 0xa5, 0xe0, 0x3a, 0xbd, 0xe0, 0xba, 0x66, 0x66, 0xfe, 0x9a, 0x8f, 0x5e, 0x7e,
|
||||
0x59, 0x9b, 0x19, 0x7e, 0x59, 0x9b, 0x79, 0x79, 0x5e, 0xd3, 0x86, 0xe7, 0x35, 0xed, 0x77, 0xaf,
|
||||
0x6a, 0x33, 0x9f, 0xbf, 0xaa, 0x69, 0xc3, 0x57, 0xb5, 0x99, 0x7f, 0xbf, 0xaa, 0xcd, 0x7c, 0xfc,
|
||||
0xf6, 0xff, 0xf0, 0x7f, 0x89, 0xba, 0xae, 0xed, 0xab, 0xf2, 0x7f, 0x93, 0xf7, 0xff, 0x1b, 0x00,
|
||||
0x00, 0xff, 0xff, 0x3e, 0xb6, 0x85, 0xe6, 0x55, 0x13, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *FolderDeviceConfiguration) Marshal() (dAtA []byte, err error) {
|
||||
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
// put the newest on top for readability.
|
||||
var (
|
||||
migrations = migrationSet{
|
||||
{34, migrateToConfigV34},
|
||||
{33, migrateToConfigV33},
|
||||
{32, migrateToConfigV32},
|
||||
{31, migrateToConfigV31},
|
||||
{30, migrateToConfigV30},
|
||||
@@ -91,9 +93,16 @@ 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 migrateToConfigV34(cfg *Configuration) {
|
||||
cfg.Defaults.Folder.Path = cfg.Options.DeprecatedDefaultFolderPath
|
||||
cfg.Options.DeprecatedDefaultFolderPath = ""
|
||||
}
|
||||
|
||||
func migrateToConfigV33(cfg *Configuration) {
|
||||
for i := range cfg.Devices {
|
||||
cfg.Devices[i].DeprecatedPendingFolders = nil
|
||||
}
|
||||
cfg.DeprecatedPendingDevices = nil
|
||||
}
|
||||
|
||||
func migrateToConfigV32(cfg *Configuration) {
|
||||
@@ -102,6 +111,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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -25,55 +25,62 @@ var _ = math.Inf
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type OptionsConfiguration struct {
|
||||
RawListenAddresses []string `protobuf:"bytes,1,rep,name=listen_addresses,json=listenAddresses,proto3" json:"listenAddresses" xml:"listenAddress" default:"default"`
|
||||
RawGlobalAnnServers []string `protobuf:"bytes,2,rep,name=global_discovery_servers,json=globalDiscoveryServers,proto3" json:"globalAnnounceServers" xml:"globalAnnounceServer" default:"default"`
|
||||
GlobalAnnEnabled bool `protobuf:"varint,3,opt,name=global_discovery_enabled,json=globalDiscoveryEnabled,proto3" json:"globalAnnounceEnabled" xml:"globalAnnounceEnabled" default:"true"`
|
||||
LocalAnnEnabled bool `protobuf:"varint,4,opt,name=local_discovery_enabled,json=localDiscoveryEnabled,proto3" json:"localAnnounceEnabled" xml:"localAnnounceEnabled" default:"true"`
|
||||
LocalAnnPort int `protobuf:"varint,5,opt,name=local_announce_port,json=localAnnouncePort,proto3,casttype=int" json:"localAnnouncePort" xml:"localAnnouncePort" default:"21027"`
|
||||
LocalAnnMCAddr string `protobuf:"bytes,6,opt,name=local_announce_multicast_address,json=localAnnounceMulticastAddress,proto3" json:"localAnnounceMCAddr" xml:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
|
||||
MaxSendKbps int `protobuf:"varint,7,opt,name=max_send_kbps,json=maxSendKbps,proto3,casttype=int" json:"maxSendKbps" xml:"maxSendKbps"`
|
||||
MaxRecvKbps int `protobuf:"varint,8,opt,name=max_recv_kbps,json=maxRecvKbps,proto3,casttype=int" json:"maxRecvKbps" xml:"maxRecvKbps"`
|
||||
ReconnectIntervalS int `protobuf:"varint,9,opt,name=reconnection_interval_s,json=reconnectionIntervalS,proto3,casttype=int" json:"reconnectionIntervalS" xml:"reconnectionIntervalS" default:"60"`
|
||||
RelaysEnabled bool `protobuf:"varint,10,opt,name=relays_enabled,json=relaysEnabled,proto3" json:"relaysEnabled" xml:"relaysEnabled" default:"true"`
|
||||
RelayReconnectIntervalM int `protobuf:"varint,11,opt,name=relays_reconnect_interval_m,json=relaysReconnectIntervalM,proto3,casttype=int" json:"relayReconnectIntervalM" xml:"relayReconnectIntervalM" default:"10"`
|
||||
StartBrowser bool `protobuf:"varint,12,opt,name=start_browser,json=startBrowser,proto3" json:"startBrowser" xml:"startBrowser" default:"true"`
|
||||
NATEnabled bool `protobuf:"varint,14,opt,name=nat_traversal_enabled,json=natTraversalEnabled,proto3" json:"natEnabled" xml:"natEnabled" default:"true"`
|
||||
NATLeaseM int `protobuf:"varint,15,opt,name=nat_traversal_lease_m,json=natTraversalLeaseM,proto3,casttype=int" json:"natLeaseMinutes" xml:"natLeaseMinutes" default:"60"`
|
||||
NATRenewalM int `protobuf:"varint,16,opt,name=nat_traversal_renewal_m,json=natTraversalRenewalM,proto3,casttype=int" json:"natRenewalMinutes" xml:"natRenewalMinutes" default:"30"`
|
||||
NATTimeoutS int `protobuf:"varint,17,opt,name=nat_traversal_timeout_s,json=natTraversalTimeoutS,proto3,casttype=int" json:"natTimeoutSeconds" xml:"natTimeoutSeconds" default:"10"`
|
||||
URAccepted int `protobuf:"varint,18,opt,name=usage_reporting_accepted,json=usageReportingAccepted,proto3,casttype=int" json:"urAccepted" xml:"urAccepted"`
|
||||
URSeen int `protobuf:"varint,19,opt,name=usage_reporting_seen,json=usageReportingSeen,proto3,casttype=int" json:"urSeen" xml:"urSeen"`
|
||||
URUniqueID string `protobuf:"bytes,20,opt,name=usage_reporting_unique_id,json=usageReportingUniqueId,proto3" json:"urUniqueId" xml:"urUniqueID"`
|
||||
URURL string `protobuf:"bytes,21,opt,name=usage_reporting_url,json=usageReportingUrl,proto3" json:"urURL" xml:"urURL" default:"https://data.syncthing.net/newdata"`
|
||||
URPostInsecurely bool `protobuf:"varint,22,opt,name=usage_reporting_post_insecurely,json=usageReportingPostInsecurely,proto3" json:"urPostInsecurely" xml:"urPostInsecurely" default:"false"`
|
||||
URInitialDelayS int `protobuf:"varint,23,opt,name=usage_reporting_initial_delay_s,json=usageReportingInitialDelayS,proto3,casttype=int" json:"urInitialDelayS" xml:"urInitialDelayS" default:"1800"`
|
||||
RestartOnWakeup bool `protobuf:"varint,24,opt,name=restart_on_wakeup,json=restartOnWakeup,proto3" json:"restartOnWakeup" xml:"restartOnWakeup" default:"true"`
|
||||
AutoUpgradeIntervalH int `protobuf:"varint,25,opt,name=auto_upgrade_interval_h,json=autoUpgradeIntervalH,proto3,casttype=int" json:"autoUpgradeIntervalH" xml:"autoUpgradeIntervalH" default:"12"`
|
||||
UpgradeToPreReleases bool `protobuf:"varint,26,opt,name=upgrade_to_pre_releases,json=upgradeToPreReleases,proto3" json:"upgradeToPreReleases" xml:"upgradeToPreReleases"`
|
||||
KeepTemporariesH int `protobuf:"varint,27,opt,name=keep_temporaries_h,json=keepTemporariesH,proto3,casttype=int" json:"keepTemporariesH" xml:"keepTemporariesH" default:"24"`
|
||||
CacheIgnoredFiles bool `protobuf:"varint,28,opt,name=cache_ignored_files,json=cacheIgnoredFiles,proto3" json:"cacheIgnoredFiles" xml:"cacheIgnoredFiles" default:"false"`
|
||||
ProgressUpdateIntervalS int `protobuf:"varint,29,opt,name=progress_update_interval_s,json=progressUpdateIntervalS,proto3,casttype=int" json:"progressUpdateIntervalS" xml:"progressUpdateIntervalS" default:"5"`
|
||||
LimitBandwidthInLan bool `protobuf:"varint,30,opt,name=limit_bandwidth_in_lan,json=limitBandwidthInLan,proto3" json:"limitBandwidthInLan" xml:"limitBandwidthInLan" default:"false"`
|
||||
MinHomeDiskFree Size `protobuf:"bytes,31,opt,name=min_home_disk_free,json=minHomeDiskFree,proto3" json:"minHomeDiskFree" xml:"minHomeDiskFree" default:"1 %"`
|
||||
ReleasesURL string `protobuf:"bytes,32,opt,name=releases_url,json=releasesUrl,proto3" json:"releasesURL" xml:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
|
||||
AlwaysLocalNets []string `protobuf:"bytes,33,rep,name=always_local_nets,json=alwaysLocalNets,proto3" json:"alwaysLocalNets" xml:"alwaysLocalNet"`
|
||||
OverwriteRemoteDevNames bool `protobuf:"varint,34,opt,name=overwrite_remote_device_names_on_connect,json=overwriteRemoteDeviceNamesOnConnect,proto3" json:"overwriteRemoteDeviceNamesOnConnect" xml:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
|
||||
TempIndexMinBlocks int `protobuf:"varint,35,opt,name=temp_index_min_blocks,json=tempIndexMinBlocks,proto3,casttype=int" json:"tempIndexMinBlocks" xml:"tempIndexMinBlocks" default:"10"`
|
||||
UnackedNotificationIDs []string `protobuf:"bytes,36,rep,name=unacked_notification_ids,json=unackedNotificationIds,proto3" json:"unackedNotificationIDs" xml:"unackedNotificationID"`
|
||||
TrafficClass int `protobuf:"varint,37,opt,name=traffic_class,json=trafficClass,proto3,casttype=int" json:"trafficClass" xml:"trafficClass"`
|
||||
DefaultFolderPath string `protobuf:"bytes,38,opt,name=default_folder_path,json=defaultFolderPath,proto3" json:"defaultFolderPath" xml:"defaultFolderPath" default:"~"`
|
||||
SetLowPriority bool `protobuf:"varint,39,opt,name=set_low_priority,json=setLowPriority,proto3" json:"setLowPriority" xml:"setLowPriority" default:"true"`
|
||||
RawMaxFolderConcurrency int `protobuf:"varint,40,opt,name=max_folder_concurrency,json=maxFolderConcurrency,proto3,casttype=int" json:"maxFolderConcurrency" xml:"maxFolderConcurrency"`
|
||||
CRURL string `protobuf:"bytes,41,opt,name=crash_reporting_url,json=crashReportingUrl,proto3" json:"crURL" xml:"crashReportingURL" default:"https://crash.syncthing.net/newcrash"`
|
||||
CREnabled bool `protobuf:"varint,42,opt,name=crash_reporting_enabled,json=crashReportingEnabled,proto3" json:"crashReportingEnabled" xml:"crashReportingEnabled" default:"true"`
|
||||
StunKeepaliveStartS int `protobuf:"varint,43,opt,name=stun_keepalive_start_s,json=stunKeepaliveStartS,proto3,casttype=int" json:"stunKeepaliveStartS" xml:"stunKeepaliveStartS" default:"180"`
|
||||
StunKeepaliveMinS int `protobuf:"varint,44,opt,name=stun_keepalive_min_s,json=stunKeepaliveMinS,proto3,casttype=int" json:"stunKeepaliveMinS" xml:"stunKeepaliveMinS" default:"20"`
|
||||
RawStunServers []string `protobuf:"bytes,45,rep,name=stun_servers,json=stunServers,proto3" json:"stunServers" xml:"stunServer" default:"default"`
|
||||
DatabaseTuning Tuning `protobuf:"varint,46,opt,name=database_tuning,json=databaseTuning,proto3,enum=config.Tuning" json:"databaseTuning" xml:"databaseTuning" restart:"true"`
|
||||
RawMaxCIRequestKiB int `protobuf:"varint,47,opt,name=max_concurrent_incoming_request_kib,json=maxConcurrentIncomingRequestKib,proto3,casttype=int" json:"maxConcurrentIncomingRequestKiB" xml:"maxConcurrentIncomingRequestKiB"`
|
||||
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"`
|
||||
RawListenAddresses []string `protobuf:"bytes,1,rep,name=listen_addresses,json=listenAddresses,proto3" json:"listenAddresses" xml:"listenAddress" default:"default"`
|
||||
RawGlobalAnnServers []string `protobuf:"bytes,2,rep,name=global_discovery_servers,json=globalDiscoveryServers,proto3" json:"globalAnnounceServers" xml:"globalAnnounceServer" default:"default"`
|
||||
GlobalAnnEnabled bool `protobuf:"varint,3,opt,name=global_discovery_enabled,json=globalDiscoveryEnabled,proto3" json:"globalAnnounceEnabled" xml:"globalAnnounceEnabled" default:"true"`
|
||||
LocalAnnEnabled bool `protobuf:"varint,4,opt,name=local_discovery_enabled,json=localDiscoveryEnabled,proto3" json:"localAnnounceEnabled" xml:"localAnnounceEnabled" default:"true"`
|
||||
LocalAnnPort int `protobuf:"varint,5,opt,name=local_announce_port,json=localAnnouncePort,proto3,casttype=int" json:"localAnnouncePort" xml:"localAnnouncePort" default:"21027"`
|
||||
LocalAnnMCAddr string `protobuf:"bytes,6,opt,name=local_announce_multicast_address,json=localAnnounceMulticastAddress,proto3" json:"localAnnounceMCAddr" xml:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
|
||||
MaxSendKbps int `protobuf:"varint,7,opt,name=max_send_kbps,json=maxSendKbps,proto3,casttype=int" json:"maxSendKbps" xml:"maxSendKbps"`
|
||||
MaxRecvKbps int `protobuf:"varint,8,opt,name=max_recv_kbps,json=maxRecvKbps,proto3,casttype=int" json:"maxRecvKbps" xml:"maxRecvKbps"`
|
||||
ReconnectIntervalS int `protobuf:"varint,9,opt,name=reconnection_interval_s,json=reconnectionIntervalS,proto3,casttype=int" json:"reconnectionIntervalS" xml:"reconnectionIntervalS" default:"60"`
|
||||
RelaysEnabled bool `protobuf:"varint,10,opt,name=relays_enabled,json=relaysEnabled,proto3" json:"relaysEnabled" xml:"relaysEnabled" default:"true"`
|
||||
RelayReconnectIntervalM int `protobuf:"varint,11,opt,name=relays_reconnect_interval_m,json=relaysReconnectIntervalM,proto3,casttype=int" json:"relayReconnectIntervalM" xml:"relayReconnectIntervalM" default:"10"`
|
||||
StartBrowser bool `protobuf:"varint,12,opt,name=start_browser,json=startBrowser,proto3" json:"startBrowser" xml:"startBrowser" default:"true"`
|
||||
NATEnabled bool `protobuf:"varint,14,opt,name=nat_traversal_enabled,json=natTraversalEnabled,proto3" json:"natEnabled" xml:"natEnabled" default:"true"`
|
||||
NATLeaseM int `protobuf:"varint,15,opt,name=nat_traversal_lease_m,json=natTraversalLeaseM,proto3,casttype=int" json:"natLeaseMinutes" xml:"natLeaseMinutes" default:"60"`
|
||||
NATRenewalM int `protobuf:"varint,16,opt,name=nat_traversal_renewal_m,json=natTraversalRenewalM,proto3,casttype=int" json:"natRenewalMinutes" xml:"natRenewalMinutes" default:"30"`
|
||||
NATTimeoutS int `protobuf:"varint,17,opt,name=nat_traversal_timeout_s,json=natTraversalTimeoutS,proto3,casttype=int" json:"natTimeoutSeconds" xml:"natTimeoutSeconds" default:"10"`
|
||||
URAccepted int `protobuf:"varint,18,opt,name=usage_reporting_accepted,json=usageReportingAccepted,proto3,casttype=int" json:"urAccepted" xml:"urAccepted"`
|
||||
URSeen int `protobuf:"varint,19,opt,name=usage_reporting_seen,json=usageReportingSeen,proto3,casttype=int" json:"urSeen" xml:"urSeen"`
|
||||
URUniqueID string `protobuf:"bytes,20,opt,name=usage_reporting_unique_id,json=usageReportingUniqueId,proto3" json:"urUniqueId" xml:"urUniqueID"`
|
||||
URURL string `protobuf:"bytes,21,opt,name=usage_reporting_url,json=usageReportingUrl,proto3" json:"urURL" xml:"urURL" default:"https://data.syncthing.net/newdata"`
|
||||
URPostInsecurely bool `protobuf:"varint,22,opt,name=usage_reporting_post_insecurely,json=usageReportingPostInsecurely,proto3" json:"urPostInsecurely" xml:"urPostInsecurely" default:"false"`
|
||||
URInitialDelayS int `protobuf:"varint,23,opt,name=usage_reporting_initial_delay_s,json=usageReportingInitialDelayS,proto3,casttype=int" json:"urInitialDelayS" xml:"urInitialDelayS" default:"1800"`
|
||||
RestartOnWakeup bool `protobuf:"varint,24,opt,name=restart_on_wakeup,json=restartOnWakeup,proto3" json:"restartOnWakeup" xml:"restartOnWakeup" default:"true"`
|
||||
AutoUpgradeIntervalH int `protobuf:"varint,25,opt,name=auto_upgrade_interval_h,json=autoUpgradeIntervalH,proto3,casttype=int" json:"autoUpgradeIntervalH" xml:"autoUpgradeIntervalH" default:"12"`
|
||||
UpgradeToPreReleases bool `protobuf:"varint,26,opt,name=upgrade_to_pre_releases,json=upgradeToPreReleases,proto3" json:"upgradeToPreReleases" xml:"upgradeToPreReleases"`
|
||||
KeepTemporariesH int `protobuf:"varint,27,opt,name=keep_temporaries_h,json=keepTemporariesH,proto3,casttype=int" json:"keepTemporariesH" xml:"keepTemporariesH" default:"24"`
|
||||
CacheIgnoredFiles bool `protobuf:"varint,28,opt,name=cache_ignored_files,json=cacheIgnoredFiles,proto3" json:"cacheIgnoredFiles" xml:"cacheIgnoredFiles" default:"false"`
|
||||
ProgressUpdateIntervalS int `protobuf:"varint,29,opt,name=progress_update_interval_s,json=progressUpdateIntervalS,proto3,casttype=int" json:"progressUpdateIntervalS" xml:"progressUpdateIntervalS" default:"5"`
|
||||
LimitBandwidthInLan bool `protobuf:"varint,30,opt,name=limit_bandwidth_in_lan,json=limitBandwidthInLan,proto3" json:"limitBandwidthInLan" xml:"limitBandwidthInLan" default:"false"`
|
||||
MinHomeDiskFree Size `protobuf:"bytes,31,opt,name=min_home_disk_free,json=minHomeDiskFree,proto3" json:"minHomeDiskFree" xml:"minHomeDiskFree" default:"1 %"`
|
||||
ReleasesURL string `protobuf:"bytes,32,opt,name=releases_url,json=releasesUrl,proto3" json:"releasesURL" xml:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
|
||||
AlwaysLocalNets []string `protobuf:"bytes,33,rep,name=always_local_nets,json=alwaysLocalNets,proto3" json:"alwaysLocalNets" xml:"alwaysLocalNet"`
|
||||
OverwriteRemoteDevNames bool `protobuf:"varint,34,opt,name=overwrite_remote_device_names_on_connect,json=overwriteRemoteDeviceNamesOnConnect,proto3" json:"overwriteRemoteDeviceNamesOnConnect" xml:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
|
||||
TempIndexMinBlocks int `protobuf:"varint,35,opt,name=temp_index_min_blocks,json=tempIndexMinBlocks,proto3,casttype=int" json:"tempIndexMinBlocks" xml:"tempIndexMinBlocks" default:"10"`
|
||||
UnackedNotificationIDs []string `protobuf:"bytes,36,rep,name=unacked_notification_ids,json=unackedNotificationIds,proto3" json:"unackedNotificationIDs" xml:"unackedNotificationID"`
|
||||
TrafficClass int `protobuf:"varint,37,opt,name=traffic_class,json=trafficClass,proto3,casttype=int" json:"trafficClass" xml:"trafficClass"`
|
||||
DeprecatedDefaultFolderPath string `protobuf:"bytes,38,opt,name=default_folder_path,json=defaultFolderPath,proto3" json:"-" xml:"defaultFolderPath,omitempty"` // Deprecated: Do not use.
|
||||
SetLowPriority bool `protobuf:"varint,39,opt,name=set_low_priority,json=setLowPriority,proto3" json:"setLowPriority" xml:"setLowPriority" default:"true"`
|
||||
RawMaxFolderConcurrency int `protobuf:"varint,40,opt,name=max_folder_concurrency,json=maxFolderConcurrency,proto3,casttype=int" json:"maxFolderConcurrency" xml:"maxFolderConcurrency"`
|
||||
CRURL string `protobuf:"bytes,41,opt,name=crash_reporting_url,json=crashReportingUrl,proto3" json:"crURL" xml:"crashReportingURL" default:"https://crash.syncthing.net/newcrash"`
|
||||
CREnabled bool `protobuf:"varint,42,opt,name=crash_reporting_enabled,json=crashReportingEnabled,proto3" json:"crashReportingEnabled" xml:"crashReportingEnabled" default:"true"`
|
||||
StunKeepaliveStartS int `protobuf:"varint,43,opt,name=stun_keepalive_start_s,json=stunKeepaliveStartS,proto3,casttype=int" json:"stunKeepaliveStartS" xml:"stunKeepaliveStartS" default:"180"`
|
||||
StunKeepaliveMinS int `protobuf:"varint,44,opt,name=stun_keepalive_min_s,json=stunKeepaliveMinS,proto3,casttype=int" json:"stunKeepaliveMinS" xml:"stunKeepaliveMinS" default:"20"`
|
||||
RawStunServers []string `protobuf:"bytes,45,rep,name=stun_servers,json=stunServers,proto3" json:"stunServers" xml:"stunServer" default:"default"`
|
||||
DatabaseTuning Tuning `protobuf:"varint,46,opt,name=database_tuning,json=databaseTuning,proto3,enum=config.Tuning" json:"databaseTuning" xml:"databaseTuning" restart:"true"`
|
||||
RawMaxCIRequestKiB int `protobuf:"varint,47,opt,name=max_concurrent_incoming_request_kib,json=maxConcurrentIncomingRequestKib,proto3,casttype=int" json:"maxConcurrentIncomingRequestKiB" xml:"maxConcurrentIncomingRequestKiB"`
|
||||
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,203 +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,
|
||||
// 3218 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x5a, 0x5d, 0x6c, 0x1d, 0x47,
|
||||
0xf5, 0xcf, 0x26, 0x4d, 0xda, 0x6c, 0x1c, 0x27, 0x1e, 0x3b, 0xf6, 0x36, 0x49, 0xbd, 0xee, 0xcd,
|
||||
0x4d, 0xeb, 0x7e, 0x25, 0xb6, 0x93, 0xe6, 0x9f, 0x46, 0xfa, 0xab, 0xf8, 0xa3, 0x26, 0x6e, 0xec,
|
||||
0xc4, 0x1a, 0xdb, 0x2a, 0x2a, 0x42, 0xab, 0xb9, 0x7b, 0xe7, 0xda, 0x8b, 0xf7, 0xce, 0xde, 0xee,
|
||||
0xcc, 0xfa, 0xda, 0x2d, 0x82, 0xaa, 0x88, 0x8f, 0x37, 0xc0, 0xe2, 0x43, 0x02, 0x09, 0x15, 0x01,
|
||||
0x12, 0xa5, 0x14, 0x21, 0x21, 0x21, 0xc1, 0x0b, 0x08, 0x09, 0xa9, 0x82, 0x07, 0xfb, 0xb1, 0x12,
|
||||
0x65, 0x51, 0x9d, 0x3e, 0xdd, 0x07, 0x1e, 0xee, 0xa3, 0x79, 0x41, 0x33, 0xfb, 0x35, 0xbb, 0x3b,
|
||||
0xb7, 0xc9, 0xdb, 0xdd, 0xf3, 0x3b, 0x73, 0xe6, 0x77, 0xe6, 0xe3, 0xcc, 0x39, 0x33, 0x57, 0xbf,
|
||||
0xec, 0x3a, 0xb5, 0xab, 0xb6, 0x47, 0x1a, 0xce, 0xfa, 0x55, 0xaf, 0xc5, 0x1c, 0x8f, 0xd0, 0xe8,
|
||||
0x2b, 0xf0, 0x11, 0xff, 0xba, 0xd2, 0xf2, 0x3d, 0xe6, 0x81, 0x13, 0x91, 0xf0, 0xfc, 0x88, 0xa4,
|
||||
0xce, 0x02, 0xe2, 0x90, 0xf5, 0x48, 0xe1, 0xfc, 0x39, 0x09, 0xa0, 0xce, 0x9b, 0x38, 0x16, 0x9f,
|
||||
0xc4, 0xdb, 0x2c, 0xfa, 0x59, 0xf9, 0xe8, 0xb6, 0x3e, 0x74, 0x2f, 0xea, 0x61, 0x56, 0xee, 0x01,
|
||||
0xfc, 0x54, 0xd3, 0xcf, 0xba, 0x0e, 0x65, 0x98, 0x58, 0xa8, 0x5e, 0xf7, 0x31, 0xa5, 0x98, 0x1a,
|
||||
0xda, 0xd8, 0xb1, 0xf1, 0x93, 0x33, 0xf4, 0x20, 0x34, 0x01, 0x44, 0xed, 0x45, 0x01, 0x4f, 0x27,
|
||||
0x68, 0x27, 0x34, 0xcf, 0xb8, 0x79, 0x51, 0x37, 0x34, 0x2f, 0x6f, 0x37, 0xdd, 0x5b, 0x95, 0x9c,
|
||||
0xbc, 0x32, 0x56, 0xc7, 0x0d, 0x14, 0xb8, 0xec, 0x56, 0x25, 0xfe, 0x51, 0x39, 0xdc, 0xab, 0x3e,
|
||||
0x1a, 0xff, 0xde, 0xdd, 0xaf, 0x2a, 0x8c, 0xc3, 0xa2, 0x69, 0xf0, 0x1f, 0x4d, 0x37, 0xd6, 0x5d,
|
||||
0xaf, 0x86, 0x5c, 0xab, 0xee, 0x50, 0xdb, 0xdb, 0xc2, 0xfe, 0x8e, 0x45, 0xb1, 0xbf, 0x85, 0x7d,
|
||||
0x6a, 0x1c, 0x15, 0x44, 0x7f, 0xaf, 0x1d, 0x84, 0xe6, 0x20, 0x44, 0xed, 0xcf, 0x0b, 0xbd, 0x69,
|
||||
0x42, 0x56, 0x22, 0xbc, 0x13, 0x9a, 0xe7, 0xd6, 0x13, 0x99, 0x17, 0x10, 0x1b, 0xc7, 0x40, 0x37,
|
||||
0x34, 0x9f, 0x17, 0x84, 0x55, 0xa8, 0x82, 0x77, 0x67, 0xaf, 0x3a, 0xa4, 0x52, 0xed, 0xee, 0x55,
|
||||
0xd5, 0x1d, 0xe4, 0x1d, 0x55, 0x71, 0x83, 0xc3, 0x51, 0xc3, 0xb9, 0xc4, 0xa9, 0x58, 0x0e, 0x3e,
|
||||
0x55, 0x39, 0x8c, 0x09, 0xaa, 0xb9, 0xb8, 0x6e, 0x1c, 0x1b, 0xd3, 0xc6, 0x1f, 0x9b, 0x79, 0x8f,
|
||||
0x3b, 0x7c, 0x36, 0xb5, 0xf8, 0x4a, 0x04, 0x96, 0xbd, 0x8d, 0x81, 0x6e, 0x68, 0x3e, 0xab, 0xf0,
|
||||
0x36, 0x46, 0x25, 0x77, 0x99, 0x1f, 0x60, 0xee, 0x6b, 0x0f, 0x33, 0xbd, 0x80, 0xc3, 0xbd, 0xea,
|
||||
0x23, 0xbc, 0xe9, 0xee, 0x7e, 0xb5, 0x44, 0xaa, 0xe4, 0x66, 0x2c, 0x07, 0x1f, 0x6b, 0xfa, 0x88,
|
||||
0xeb, 0xd9, 0x4a, 0x2f, 0x1f, 0x11, 0x5e, 0xfe, 0x9c, 0x7b, 0x79, 0x66, 0x91, 0xeb, 0xe4, 0x9c,
|
||||
0x1c, 0x72, 0x63, 0x51, 0xc1, 0xc7, 0x67, 0xa2, 0x25, 0xa8, 0x00, 0x15, 0x2e, 0xaa, 0x8d, 0xf4,
|
||||
0x90, 0x4b, 0x0e, 0x16, 0xf9, 0xc0, 0x73, 0xa2, 0x41, 0xc9, 0xbd, 0x7f, 0x68, 0xfa, 0x60, 0xe4,
|
||||
0x1e, 0x8a, 0x6d, 0x59, 0x2d, 0xcf, 0x67, 0xc6, 0xf1, 0x31, 0x6d, 0xfc, 0xf8, 0xcc, 0x8f, 0xb9,
|
||||
0x6b, 0x7d, 0x89, 0xa9, 0x65, 0xcf, 0x67, 0x9d, 0xd0, 0x1c, 0xc8, 0x75, 0xcd, 0x85, 0xdd, 0xd0,
|
||||
0x7c, 0xba, 0xec, 0x14, 0x47, 0x24, 0x8f, 0xa6, 0x26, 0x27, 0xa6, 0xfe, 0xaf, 0x72, 0x18, 0x9a,
|
||||
0xc7, 0x1c, 0xc2, 0x3a, 0x7b, 0x55, 0x85, 0x19, 0x95, 0xf0, 0x70, 0xaf, 0x7a, 0x5c, 0x34, 0xdd,
|
||||
0xdd, 0xaf, 0xe6, 0x98, 0xc0, 0xb2, 0x2e, 0xf8, 0xfa, 0x51, 0x7d, 0xac, 0xe0, 0x4d, 0x33, 0x70,
|
||||
0x99, 0x63, 0x23, 0xca, 0x92, 0xb8, 0x61, 0x9c, 0x18, 0xd3, 0xc6, 0x4f, 0xce, 0xfc, 0x91, 0xbb,
|
||||
0xd6, 0x9f, 0x18, 0x5c, 0x9a, 0xe5, 0x3b, 0xb9, 0x13, 0x9a, 0x83, 0x39, 0xa3, 0x91, 0xb8, 0x1b,
|
||||
0x9a, 0x37, 0xca, 0xee, 0x45, 0x98, 0xe4, 0xe0, 0x17, 0x1b, 0x8d, 0xc9, 0xa9, 0x5b, 0xb7, 0x6e,
|
||||
0x5e, 0xbb, 0x79, 0xfd, 0x4b, 0xb7, 0x22, 0x6f, 0x3b, 0x7b, 0x55, 0xa5, 0x41, 0xb5, 0xf8, 0x70,
|
||||
0xaf, 0x0a, 0xca, 0x46, 0x76, 0xf7, 0xab, 0x05, 0x9a, 0xf0, 0x89, 0x7c, 0xe3, 0xc4, 0xc3, 0x38,
|
||||
0x18, 0x81, 0x7b, 0xfa, 0xe9, 0x26, 0xda, 0xb6, 0x28, 0x26, 0x75, 0x6b, 0xb3, 0xd6, 0xa2, 0xc6,
|
||||
0xa3, 0x62, 0x32, 0x9f, 0xeb, 0x84, 0xe6, 0xa9, 0x26, 0xda, 0x5e, 0xc1, 0xa4, 0x7e, 0xa7, 0xd6,
|
||||
0xe2, 0xc1, 0x65, 0x40, 0xb8, 0x25, 0xc9, 0x92, 0xf9, 0x81, 0xb2, 0x62, 0x62, 0xd0, 0xc7, 0xf6,
|
||||
0x56, 0x64, 0xf0, 0xb1, 0x9c, 0x41, 0x88, 0xed, 0xad, 0xa2, 0xc1, 0x44, 0x96, 0x33, 0x98, 0x08,
|
||||
0xc1, 0x1f, 0x34, 0x7d, 0xc4, 0xc7, 0xb6, 0x47, 0x08, 0xb6, 0x79, 0x78, 0xb7, 0x1c, 0xc2, 0xb0,
|
||||
0xbf, 0x85, 0x5c, 0x8b, 0x1a, 0x27, 0x85, 0xed, 0xaf, 0x8a, 0xa0, 0x9e, 0xa8, 0x2c, 0xc4, 0xf0,
|
||||
0x0a, 0x8f, 0x1d, 0x72, 0xc3, 0x14, 0xe8, 0x86, 0xe6, 0xb8, 0xe8, 0x5b, 0x89, 0x4a, 0xb3, 0x74,
|
||||
0x63, 0x22, 0xa1, 0x74, 0xb8, 0x57, 0x3d, 0x7a, 0x63, 0x42, 0xc4, 0xf7, 0x52, 0x3f, 0x50, 0xdd,
|
||||
0x0b, 0x68, 0xe8, 0xfd, 0x3e, 0x76, 0xd1, 0x0e, 0x4d, 0x63, 0x80, 0x2e, 0x62, 0xc0, 0xcb, 0x9d,
|
||||
0xd0, 0x3c, 0x1d, 0x21, 0xd9, 0x46, 0xaf, 0xc4, 0x84, 0x24, 0x69, 0x71, 0x87, 0x27, 0x3b, 0x16,
|
||||
0xe6, 0x1b, 0x83, 0x77, 0x8e, 0xea, 0x17, 0xe2, 0x8e, 0x52, 0x22, 0xd9, 0x20, 0x35, 0x8d, 0x53,
|
||||
0x62, 0x90, 0xfe, 0xca, 0xd7, 0xf0, 0x08, 0xe4, 0x7a, 0x25, 0x17, 0x96, 0x3a, 0xa1, 0x39, 0xe2,
|
||||
0xab, 0xa1, 0x34, 0xd0, 0xf6, 0xc0, 0x25, 0x96, 0x93, 0x13, 0xd2, 0x96, 0xed, 0x69, 0xaf, 0x37,
|
||||
0xc4, 0x07, 0x79, 0x92, 0x0f, 0x72, 0x2f, 0x9a, 0xd0, 0x88, 0xfc, 0x2c, 0x23, 0xa0, 0xa6, 0x9f,
|
||||
0xa6, 0x0c, 0xf9, 0xcc, 0xaa, 0xf9, 0x5e, 0x9b, 0x62, 0xdf, 0xe8, 0x13, 0x63, 0xfd, 0xff, 0x9d,
|
||||
0xd0, 0xec, 0x13, 0xc0, 0x4c, 0x24, 0xef, 0x86, 0xe6, 0x93, 0xc2, 0x1d, 0x59, 0xd8, 0x73, 0xa4,
|
||||
0x73, 0x4d, 0xc1, 0x2f, 0x35, 0xfd, 0x1c, 0x41, 0xcc, 0x62, 0x3e, 0xe2, 0xa7, 0x1a, 0x72, 0xd3,
|
||||
0x89, 0xed, 0x17, 0x9d, 0xbd, 0x71, 0x10, 0x9a, 0xfa, 0xdd, 0xe9, 0xd5, 0x2c, 0xac, 0xeb, 0x04,
|
||||
0xb1, 0x6c, 0x8e, 0x4d, 0xd1, 0x71, 0x26, 0x52, 0x84, 0x70, 0xb9, 0x41, 0xee, 0x4b, 0x0a, 0xd7,
|
||||
0x52, 0x17, 0x70, 0x90, 0x20, 0xb6, 0x9a, 0xd0, 0x49, 0x16, 0xc4, 0x9f, 0x4a, 0x3c, 0x5d, 0x8c,
|
||||
0x28, 0xb6, 0x9a, 0xc6, 0x19, 0xb1, 0x14, 0xbe, 0xc9, 0x97, 0xc2, 0xc9, 0xbb, 0xd3, 0xab, 0x8b,
|
||||
0x5c, 0xcc, 0x27, 0xff, 0x0c, 0x41, 0x2c, 0xfa, 0x70, 0x48, 0xc0, 0x44, 0xf2, 0x53, 0x49, 0xc8,
|
||||
0xca, 0x72, 0xe5, 0xde, 0xe8, 0xec, 0x55, 0x4b, 0xed, 0xcb, 0xa2, 0x74, 0x07, 0x65, 0x1d, 0x43,
|
||||
0x20, 0xb3, 0x8f, 0x64, 0xe0, 0xef, 0x9a, 0x3e, 0x92, 0x27, 0xef, 0x63, 0x82, 0xdb, 0x62, 0x25,
|
||||
0x9f, 0x15, 0xf4, 0x77, 0x39, 0xfd, 0x53, 0x77, 0xa7, 0x57, 0x61, 0x04, 0x70, 0x07, 0x06, 0x08,
|
||||
0x62, 0xc9, 0x67, 0xea, 0x42, 0x35, 0x71, 0x21, 0x8f, 0x48, 0x4e, 0x5c, 0x93, 0x9d, 0x50, 0xd8,
|
||||
0x50, 0x09, 0xb9, 0x23, 0xd7, 0xb8, 0x23, 0x32, 0x05, 0x38, 0x24, 0xbb, 0x92, 0x48, 0x15, 0xce,
|
||||
0x30, 0xa7, 0x89, 0xbd, 0x80, 0x59, 0xd4, 0x18, 0xc8, 0x3b, 0xb3, 0x1a, 0x01, 0x2b, 0xb1, 0x33,
|
||||
0xc9, 0x27, 0x5f, 0xe9, 0xf5, 0x9c, 0x33, 0x79, 0xa4, 0xd7, 0xf6, 0x53, 0xd8, 0x50, 0x09, 0xd3,
|
||||
0x2d, 0x27, 0x53, 0xc8, 0x3b, 0x93, 0x48, 0xc1, 0x4f, 0x34, 0xdd, 0x08, 0x28, 0x5a, 0xc7, 0x96,
|
||||
0x8f, 0xf9, 0xb9, 0xef, 0x90, 0x75, 0x0b, 0xd9, 0x36, 0x6e, 0x31, 0x5c, 0x37, 0x80, 0xf0, 0x06,
|
||||
0xf1, 0x1d, 0xb0, 0x06, 0xa7, 0x63, 0x29, 0xdf, 0x01, 0x81, 0x9f, 0x7c, 0x75, 0x43, 0xf3, 0xac,
|
||||
0x70, 0x22, 0x13, 0x49, 0x84, 0x65, 0xc5, 0xdc, 0x17, 0x5f, 0xf1, 0x99, 0x49, 0x38, 0x2c, 0x28,
|
||||
0xc0, 0x84, 0x41, 0x22, 0x07, 0x6f, 0xe9, 0x43, 0x45, 0x72, 0x14, 0x63, 0x62, 0x0c, 0x0a, 0x62,
|
||||
0x0b, 0x07, 0xa1, 0x79, 0x62, 0x0d, 0xae, 0x60, 0x4c, 0x3a, 0xa1, 0x79, 0x22, 0xf0, 0xf9, 0xaf,
|
||||
0x6e, 0x68, 0xf6, 0xc5, 0x84, 0xf8, 0xa7, 0x44, 0x26, 0x51, 0x48, 0x7f, 0xed, 0xee, 0x57, 0xe3,
|
||||
0xe6, 0x10, 0xe4, 0x09, 0x70, 0x19, 0xf8, 0x81, 0xa6, 0x3f, 0x5e, 0xec, 0x3d, 0x20, 0xce, 0x1b,
|
||||
0x01, 0xb6, 0x9c, 0xba, 0x31, 0x24, 0x92, 0x88, 0xd7, 0xa3, 0xb1, 0x59, 0x13, 0xe2, 0x85, 0xb9,
|
||||
0x68, 0x6c, 0xe2, 0x2f, 0x79, 0x6c, 0x12, 0x85, 0x4a, 0x34, 0x28, 0xc9, 0x67, 0x57, 0xfe, 0x8a,
|
||||
0x07, 0x25, 0xc1, 0x8a, 0x83, 0x92, 0x68, 0x81, 0xbf, 0x68, 0xfa, 0x60, 0x89, 0x97, 0xef, 0x1a,
|
||||
0xe7, 0x04, 0xa3, 0xef, 0xf0, 0xb5, 0x77, 0x7c, 0x0d, 0xae, 0xc1, 0xc5, 0x4e, 0x68, 0x1e, 0x0f,
|
||||
0xfc, 0x35, 0xb8, 0xd8, 0x0d, 0xcd, 0x9b, 0x09, 0x11, 0xb8, 0x28, 0xad, 0xae, 0x0d, 0xc6, 0x5a,
|
||||
0xf4, 0xd6, 0xd5, 0xab, 0x75, 0xc4, 0xd0, 0x15, 0xba, 0x43, 0x6c, 0xb6, 0xc1, 0x8b, 0x35, 0x82,
|
||||
0xd9, 0x55, 0x82, 0xdb, 0x5c, 0xca, 0x09, 0xc7, 0x46, 0x92, 0x1f, 0x87, 0x7b, 0xd5, 0x87, 0x68,
|
||||
0xb8, 0xbb, 0x5f, 0x8d, 0x58, 0xc0, 0x81, 0x82, 0x1f, 0xbe, 0x0b, 0xfe, 0xad, 0xe9, 0x66, 0xd1,
|
||||
0x85, 0x96, 0x47, 0xf9, 0x09, 0x47, 0xb1, 0x1d, 0xf8, 0xd8, 0xdd, 0x31, 0x86, 0x45, 0xf8, 0xfd,
|
||||
0x91, 0xa8, 0x20, 0xd6, 0xe0, 0xb2, 0x47, 0xd9, 0x42, 0x0a, 0x76, 0x42, 0xf3, 0x6c, 0xe0, 0xe7,
|
||||
0x65, 0xdd, 0xd0, 0x7c, 0x2a, 0x76, 0x32, 0x0f, 0x48, 0xfe, 0x36, 0x90, 0x4b, 0x45, 0x48, 0x2e,
|
||||
0xb7, 0x56, 0xc8, 0x78, 0xe6, 0x29, 0x5a, 0xf0, 0x7a, 0xa1, 0x48, 0x01, 0x5e, 0xcc, 0xbb, 0x95,
|
||||
0x47, 0xc1, 0xbf, 0x14, 0x1e, 0x3a, 0xc4, 0x61, 0x0e, 0xaf, 0x23, 0xf8, 0x79, 0x67, 0x51, 0x63,
|
||||
0x44, 0xac, 0xe2, 0x1f, 0x8a, 0xea, 0x61, 0x0d, 0x2e, 0x44, 0xe8, 0x1c, 0x07, 0x79, 0xc0, 0x38,
|
||||
0x13, 0xf8, 0x39, 0x51, 0x1a, 0x2e, 0x0a, 0x72, 0x39, 0x58, 0xdc, 0x9c, 0xc8, 0x05, 0xf0, 0xa2,
|
||||
0x85, 0xb2, 0x88, 0x9f, 0x40, 0xbc, 0x15, 0x2f, 0x18, 0x0a, 0x14, 0xe0, 0x85, 0xbc, 0x83, 0x39,
|
||||
0x10, 0x78, 0xfa, 0x80, 0x8f, 0xa3, 0xc3, 0xd9, 0x23, 0x56, 0x1b, 0x6d, 0xe2, 0xa0, 0x65, 0x18,
|
||||
0x62, 0xca, 0x66, 0x39, 0xf9, 0x18, 0xbc, 0x47, 0x5e, 0x13, 0x50, 0x4a, 0xbe, 0x20, 0xef, 0x79,
|
||||
0x48, 0x17, 0x0d, 0x80, 0x6f, 0x69, 0xfa, 0x08, 0x0a, 0x98, 0x67, 0x05, 0xad, 0x75, 0x1f, 0xd5,
|
||||
0x71, 0x96, 0x0c, 0x6d, 0x18, 0x8f, 0x8b, 0x81, 0x5c, 0xe6, 0x25, 0x17, 0x57, 0x59, 0x8b, 0x34,
|
||||
0x92, 0x3c, 0xe2, 0x76, 0x5a, 0x9d, 0xa8, 0x40, 0x79, 0xf8, 0xa6, 0xe4, 0xcc, 0x70, 0x72, 0x0a,
|
||||
0x2a, 0xad, 0x81, 0xa6, 0x3e, 0x92, 0x70, 0x60, 0x9e, 0xd5, 0xf2, 0xf9, 0x14, 0x8b, 0xb3, 0x98,
|
||||
0x1a, 0xe7, 0xc5, 0x00, 0xdc, 0xe0, 0x44, 0x62, 0x95, 0x55, 0x6f, 0xd9, 0xc7, 0x30, 0xc6, 0xbb,
|
||||
0xa1, 0x79, 0x3e, 0x9a, 0x42, 0x05, 0x58, 0x81, 0xca, 0x36, 0x60, 0x4b, 0x07, 0x9b, 0x18, 0xb7,
|
||||
0x2c, 0x86, 0x9b, 0x2d, 0xcf, 0x47, 0xbe, 0x83, 0xa9, 0xb5, 0x61, 0x5c, 0x10, 0x2e, 0xdf, 0xe6,
|
||||
0x1b, 0x81, 0xa3, 0xab, 0x19, 0xc8, 0xdd, 0xbd, 0x24, 0x7a, 0x29, 0x02, 0x72, 0x2d, 0x76, 0x5d,
|
||||
0x76, 0x75, 0xea, 0x3a, 0x2c, 0x59, 0x01, 0x3b, 0xfa, 0xa0, 0x8d, 0xec, 0x0d, 0x6c, 0x39, 0xeb,
|
||||
0xc4, 0xf3, 0x71, 0xdd, 0x6a, 0x38, 0x2e, 0xa6, 0xc6, 0x45, 0xe1, 0xe2, 0x02, 0x3f, 0xd1, 0x04,
|
||||
0xbc, 0x10, 0xa1, 0xf3, 0x1c, 0x4c, 0x07, 0xba, 0x84, 0x94, 0xf6, 0x60, 0xba, 0xb7, 0x60, 0xd9,
|
||||
0x0c, 0xf8, 0x9e, 0xa6, 0x9f, 0x6f, 0xf9, 0xde, 0x3a, 0x2f, 0x66, 0xac, 0xa0, 0x55, 0x47, 0x0c,
|
||||
0xcb, 0x05, 0xc2, 0x13, 0xc2, 0xf7, 0x55, 0x9e, 0xdf, 0x26, 0x5a, 0x6b, 0x42, 0x49, 0x2e, 0x06,
|
||||
0xa2, 0x22, 0xbb, 0x07, 0x2e, 0xd1, 0x79, 0x51, 0x1a, 0x08, 0xed, 0x45, 0xd8, 0xcb, 0x22, 0x78,
|
||||
0x47, 0xd3, 0x87, 0x5d, 0xa7, 0xe9, 0x30, 0xab, 0x86, 0x48, 0xbd, 0xed, 0xd4, 0xd9, 0x86, 0xe5,
|
||||
0x10, 0xcb, 0x45, 0xc4, 0x18, 0x15, 0x43, 0xb2, 0x24, 0x8a, 0x47, 0xae, 0x31, 0x93, 0x28, 0x2c,
|
||||
0x90, 0x45, 0x44, 0xb2, 0x82, 0xbf, 0x8c, 0x7d, 0xc6, 0xb0, 0xa8, 0x4c, 0x81, 0xb7, 0x35, 0x1d,
|
||||
0x34, 0x1d, 0x62, 0x6d, 0x78, 0x4d, 0x6c, 0xd5, 0x1d, 0xba, 0x69, 0x35, 0x7c, 0x8c, 0x0d, 0x73,
|
||||
0x4c, 0x1b, 0x3f, 0x35, 0xd5, 0x77, 0x25, 0xba, 0x59, 0xbb, 0xb2, 0xe2, 0xbc, 0x89, 0x67, 0x5e,
|
||||
0xf9, 0x30, 0x34, 0x8f, 0xf0, 0x9d, 0xd8, 0x74, 0xc8, 0x6d, 0xaf, 0x89, 0xe7, 0x1c, 0xba, 0x39,
|
||||
0xef, 0x63, 0x9c, 0xae, 0x8e, 0x82, 0x5c, 0xde, 0x07, 0x63, 0x97, 0x39, 0x91, 0x63, 0x93, 0x63,
|
||||
0x97, 0x61, 0xb1, 0x39, 0xb8, 0xaf, 0xe9, 0x7d, 0xc9, 0x7a, 0x17, 0xc7, 0xce, 0x98, 0x38, 0x76,
|
||||
0xfe, 0x2c, 0x52, 0x9e, 0x64, 0xd1, 0x46, 0x87, 0xcf, 0x29, 0x3f, 0xfb, 0xec, 0x86, 0xe6, 0x5c,
|
||||
0x52, 0x71, 0x24, 0x32, 0xc5, 0x41, 0x14, 0xef, 0x00, 0x5a, 0x38, 0x53, 0x9a, 0x98, 0xa1, 0x2b,
|
||||
0x5f, 0xa6, 0x1e, 0xe1, 0xb1, 0x3b, 0x67, 0x36, 0xff, 0x79, 0xb8, 0x57, 0x1d, 0x7f, 0x58, 0x53,
|
||||
0x3c, 0x3f, 0x92, 0xf8, 0xc2, 0xcc, 0x8e, 0xef, 0x82, 0xd7, 0xf4, 0x01, 0xe4, 0xb6, 0x79, 0xf5,
|
||||
0x15, 0xdd, 0x26, 0x10, 0xcc, 0xa8, 0xf1, 0xa4, 0xb8, 0xc4, 0xe3, 0x45, 0xef, 0x99, 0x08, 0x14,
|
||||
0x55, 0xf9, 0x5d, 0xcc, 0xf8, 0xc2, 0x1f, 0x8a, 0x22, 0x4c, 0x4e, 0x5e, 0x81, 0x45, 0x45, 0xf0,
|
||||
0x5f, 0x4d, 0x1f, 0xf7, 0xb6, 0xb0, 0xdf, 0xf6, 0x1d, 0xc6, 0x03, 0x47, 0xd3, 0x63, 0xd8, 0xaa,
|
||||
0xe3, 0x2d, 0xc7, 0xc6, 0x16, 0x41, 0x4d, 0x4c, 0x79, 0x38, 0x8d, 0x0b, 0x21, 0xa3, 0x92, 0x5d,
|
||||
0x2f, 0x8d, 0xdc, 0x4b, 0x1a, 0x41, 0xd1, 0x66, 0x0e, 0x6f, 0xdd, 0xe5, 0xea, 0x9d, 0xd0, 0xbc,
|
||||
0xe4, 0x95, 0x20, 0xc7, 0xc6, 0x02, 0xbd, 0x47, 0x66, 0x23, 0x53, 0xdd, 0xd0, 0x7c, 0x49, 0x10,
|
||||
0x7c, 0x08, 0xdd, 0xde, 0x8b, 0x92, 0x57, 0x71, 0x3d, 0x78, 0xc0, 0x87, 0x61, 0x01, 0xbe, 0xa6,
|
||||
0x9f, 0xe3, 0x61, 0xcc, 0x72, 0x48, 0x1d, 0x6f, 0x5b, 0x7c, 0x25, 0xd7, 0x5c, 0xcf, 0xde, 0xa4,
|
||||
0xc6, 0x25, 0xb1, 0xa5, 0xf9, 0xa2, 0x01, 0x5c, 0x61, 0x81, 0xe3, 0x4b, 0x0e, 0x99, 0x11, 0x68,
|
||||
0x7a, 0x6b, 0x5b, 0x86, 0x94, 0x99, 0x72, 0x94, 0xff, 0x42, 0x85, 0x25, 0xf0, 0x4f, 0x9e, 0xee,
|
||||
0x12, 0x64, 0x6f, 0xe2, 0xba, 0x45, 0x3c, 0xe6, 0x34, 0x1c, 0x1b, 0x45, 0xf7, 0x0f, 0x75, 0x6a,
|
||||
0x54, 0xc5, 0xfc, 0xbe, 0xcb, 0x87, 0x7b, 0x78, 0x2d, 0x52, 0xba, 0x2b, 0xe9, 0x2c, 0xcc, 0xf1,
|
||||
0xd1, 0x1e, 0x0e, 0x94, 0x48, 0x37, 0x34, 0x2f, 0x44, 0xa1, 0x5d, 0x05, 0x8b, 0xbb, 0x4a, 0x25,
|
||||
0xd2, 0xdd, 0xab, 0xf6, 0xb0, 0xb8, 0xbb, 0x5f, 0xed, 0xc1, 0x02, 0x2a, 0x5b, 0xd4, 0x29, 0x80,
|
||||
0xfa, 0x69, 0xe6, 0xa3, 0x46, 0xc3, 0xb1, 0x2d, 0xdb, 0x45, 0x94, 0x1a, 0x97, 0xc5, 0xb0, 0xbe,
|
||||
0xc0, 0xeb, 0xe5, 0x18, 0x98, 0xe5, 0xf2, 0x6e, 0x68, 0x82, 0x68, 0x40, 0x25, 0x61, 0x7a, 0x51,
|
||||
0x93, 0x53, 0x05, 0x6f, 0xe9, 0x83, 0xf1, 0x10, 0x5b, 0x0d, 0xcf, 0xad, 0x63, 0xdf, 0x6a, 0x21,
|
||||
0xb6, 0x61, 0x3c, 0x25, 0x76, 0xfd, 0x9d, 0x83, 0xd0, 0xbc, 0x30, 0x87, 0x5b, 0x3e, 0xb6, 0x11,
|
||||
0xc3, 0xf5, 0xb9, 0x48, 0x71, 0x5e, 0xe8, 0x2d, 0x23, 0xb6, 0xd1, 0x09, 0x4d, 0xed, 0x85, 0xb4,
|
||||
0x3a, 0xaf, 0x17, 0xe1, 0xe7, 0xbd, 0xa6, 0xc3, 0x27, 0x89, 0xed, 0x54, 0x0c, 0x0d, 0x0e, 0x94,
|
||||
0x70, 0xb0, 0xa9, 0x9f, 0xa5, 0x98, 0x59, 0xae, 0xd7, 0xb6, 0x5a, 0xbe, 0xe3, 0xf9, 0x0e, 0xdb,
|
||||
0x31, 0x9e, 0x16, 0x9b, 0x62, 0xba, 0x13, 0x9a, 0xfd, 0x14, 0xb3, 0x45, 0xaf, 0xbd, 0x1c, 0x23,
|
||||
0x69, 0x64, 0xcb, 0x8b, 0x7b, 0xa6, 0x18, 0x85, 0xe6, 0xe0, 0x3d, 0x4d, 0x1f, 0x6e, 0xa2, 0xed,
|
||||
0xc4, 0x4d, 0xdb, 0x23, 0x76, 0xe0, 0xfb, 0x98, 0xd8, 0x3b, 0xc6, 0xb8, 0x18, 0x47, 0x2a, 0x2e,
|
||||
0x5b, 0x50, 0x7b, 0x09, 0x6d, 0x47, 0x1c, 0x67, 0x33, 0x15, 0x7e, 0xe4, 0x37, 0x15, 0xf2, 0xf4,
|
||||
0xc8, 0x57, 0x81, 0xc9, 0x90, 0x8b, 0xdb, 0x11, 0xb5, 0x5d, 0xa8, 0xb4, 0x0a, 0x3e, 0xd6, 0xf4,
|
||||
0x41, 0xdb, 0x47, 0x74, 0xa3, 0x50, 0x03, 0x3c, 0x23, 0xa6, 0xe5, 0x7d, 0x51, 0x03, 0xcc, 0x26,
|
||||
0x35, 0x80, 0x1d, 0xd7, 0x00, 0xf3, 0xd1, 0xd9, 0xcc, 0x9b, 0x65, 0xd9, 0xb8, 0x32, 0x0c, 0x0b,
|
||||
0x9d, 0x72, 0x5e, 0x2f, 0xc4, 0x7c, 0x2d, 0x0f, 0x94, 0x8c, 0xf0, 0xea, 0xc0, 0x8e, 0xab, 0x83,
|
||||
0xea, 0xc3, 0x98, 0xe1, 0xf5, 0xc1, 0x6c, 0x54, 0x1f, 0x14, 0x8c, 0xf9, 0x2e, 0xf8, 0x99, 0xa6,
|
||||
0x8f, 0x14, 0xdd, 0x4b, 0xae, 0x65, 0x9e, 0x15, 0xf3, 0xef, 0x1c, 0x84, 0xe6, 0xc9, 0x59, 0x28,
|
||||
0xbd, 0x28, 0xe4, 0xad, 0x14, 0x5f, 0x14, 0x94, 0x68, 0xaf, 0xa5, 0xb1, 0xbb, 0x5f, 0xcd, 0x6c,
|
||||
0x43, 0xb5, 0x65, 0xf0, 0x0d, 0x4d, 0x1f, 0xa6, 0x2c, 0x20, 0x16, 0xcf, 0x9c, 0x90, 0xeb, 0x6c,
|
||||
0x61, 0x2b, 0xca, 0x87, 0xa9, 0xf1, 0x5c, 0x9a, 0x8f, 0x0e, 0x72, 0x8d, 0x3b, 0x89, 0xc2, 0x0a,
|
||||
0xc7, 0x57, 0xd2, 0x2c, 0x49, 0x81, 0xe5, 0x93, 0x79, 0x29, 0xa0, 0x1d, 0x9b, 0xbc, 0x39, 0x01,
|
||||
0x55, 0xd6, 0x78, 0x8d, 0x5c, 0xa0, 0xc1, 0xe3, 0x2a, 0x35, 0x9e, 0x17, 0x24, 0x5e, 0xe5, 0x89,
|
||||
0x5a, 0xae, 0xd9, 0x92, 0x43, 0xb2, 0x5a, 0xa2, 0x84, 0xc8, 0x39, 0x62, 0x2e, 0xa0, 0x4e, 0x4d,
|
||||
0xc0, 0xb2, 0x1d, 0x9e, 0x95, 0xf7, 0x89, 0xde, 0x93, 0x87, 0xae, 0x17, 0x44, 0x0c, 0xad, 0x1f,
|
||||
0x84, 0x66, 0x3f, 0x44, 0xed, 0x15, 0x16, 0x48, 0x4f, 0x5c, 0xa7, 0x68, 0xf6, 0x99, 0x5e, 0x46,
|
||||
0x65, 0xb2, 0x07, 0x3e, 0xc3, 0x15, 0x2c, 0x42, 0xd9, 0x1e, 0xd8, 0xd2, 0xcf, 0xf0, 0xb2, 0xb3,
|
||||
0x86, 0x28, 0xb6, 0xa2, 0x37, 0x47, 0xe3, 0xca, 0x98, 0x36, 0xde, 0x3f, 0xd5, 0x9f, 0xa4, 0x45,
|
||||
0xab, 0x42, 0x2a, 0x6e, 0x0f, 0xfb, 0x13, 0xd5, 0x48, 0x96, 0x46, 0x8e, 0xbc, 0xb8, 0x32, 0x16,
|
||||
0x17, 0x21, 0xf1, 0xf2, 0x78, 0x7b, 0xbf, 0xaa, 0xc1, 0x42, 0x53, 0xf0, 0xfd, 0xa3, 0xfa, 0x25,
|
||||
0x1e, 0x35, 0xd2, 0x70, 0xc1, 0x8b, 0x58, 0xdb, 0x6b, 0xf2, 0x25, 0xeb, 0xe3, 0x37, 0x02, 0x4c,
|
||||
0x99, 0xb5, 0xe9, 0xd4, 0x8c, 0xab, 0x62, 0x3a, 0xfe, 0xa6, 0xc5, 0x6f, 0x95, 0x4b, 0x68, 0x7b,
|
||||
0x76, 0x01, 0x46, 0xf8, 0x1d, 0x67, 0xa6, 0x13, 0x9a, 0x66, 0x13, 0x6d, 0xa7, 0x5b, 0x9c, 0x2d,
|
||||
0xc4, 0x36, 0x32, 0x95, 0xf4, 0x14, 0x7c, 0x80, 0x9e, 0x54, 0x00, 0x3e, 0xd0, 0xe4, 0x83, 0x55,
|
||||
0xe2, 0xd7, 0xcf, 0x02, 0x5d, 0xf8, 0x80, 0x66, 0x35, 0xf0, 0xa9, 0xa6, 0x0f, 0xa7, 0x4f, 0x30,
|
||||
0x2e, 0x92, 0x1f, 0x6d, 0x27, 0xc4, 0x06, 0xfe, 0x80, 0x8f, 0xc4, 0x50, 0xf2, 0x84, 0xb1, 0x38,
|
||||
0x7d, 0x57, 0x7e, 0xb7, 0x1d, 0x42, 0x0a, 0x79, 0x9a, 0x48, 0xab, 0x40, 0xd5, 0xcb, 0x99, 0xd2,
|
||||
0x48, 0x0f, 0xb9, 0xb4, 0xf5, 0x95, 0xa4, 0x60, 0xd6, 0x0a, 0x49, 0x8f, 0xbe, 0x5b, 0xfa, 0x79,
|
||||
0xf1, 0xca, 0xd2, 0x08, 0x5c, 0x37, 0xce, 0x6a, 0x3c, 0x92, 0x94, 0xa8, 0xc6, 0xa4, 0xf0, 0xf4,
|
||||
0x16, 0xcf, 0x1a, 0xb8, 0xd6, 0x7c, 0xe0, 0xba, 0x22, 0x1f, 0xb9, 0x47, 0xe2, 0xa2, 0xb2, 0x1b,
|
||||
0x9a, 0x17, 0xe3, 0x23, 0x4b, 0x05, 0x57, 0x60, 0x8f, 0x76, 0xe0, 0x55, 0xfd, 0x74, 0x03, 0x23,
|
||||
0x16, 0xf8, 0xd8, 0x6a, 0xb8, 0x68, 0x9d, 0x1a, 0x53, 0x62, 0xdf, 0x5d, 0xe6, 0x27, 0x7d, 0x0c,
|
||||
0xcc, 0x73, 0x79, 0xfa, 0x22, 0x23, 0x09, 0x2b, 0x30, 0xa7, 0x02, 0xda, 0xfa, 0x88, 0xf4, 0x10,
|
||||
0x13, 0xd5, 0x38, 0x98, 0x78, 0xc1, 0xfa, 0x86, 0x71, 0x4d, 0x2c, 0xda, 0x97, 0x45, 0x78, 0x4d,
|
||||
0x55, 0x16, 0xb9, 0xc6, 0x2b, 0x42, 0x21, 0xcd, 0x7a, 0x94, 0x68, 0x9a, 0x51, 0xa8, 0x1b, 0x83,
|
||||
0x4d, 0x7d, 0xa8, 0xd4, 0x71, 0x13, 0x6d, 0x1b, 0xd7, 0x45, 0xaf, 0x2f, 0xf1, 0x64, 0xb0, 0xd0,
|
||||
0x70, 0x09, 0x6d, 0x77, 0x43, 0xd3, 0x50, 0x75, 0xb9, 0x84, 0xb6, 0xd3, 0xfe, 0x14, 0xcd, 0xc0,
|
||||
0x57, 0xf4, 0xbe, 0xa0, 0x45, 0x5a, 0xe9, 0x31, 0xf2, 0xab, 0x79, 0x31, 0x39, 0x5f, 0x38, 0x08,
|
||||
0xcd, 0x73, 0x59, 0x06, 0xb3, 0xb6, 0x4c, 0x96, 0xb3, 0x33, 0x45, 0xe4, 0x2e, 0x71, 0x5a, 0xd7,
|
||||
0x22, 0xad, 0x18, 0x90, 0xb2, 0x96, 0xdd, 0xfd, 0xaa, 0xba, 0xb1, 0xa1, 0xc1, 0x53, 0x52, 0x13,
|
||||
0xf0, 0x0b, 0x2d, 0xee, 0x3e, 0xb9, 0xb4, 0x7f, 0x6f, 0x5e, 0x38, 0xf9, 0xb6, 0xd8, 0x05, 0x79,
|
||||
0x13, 0xe9, 0x05, 0xbe, 0xe8, 0x7e, 0x2c, 0xed, 0x5e, 0xbe, 0x78, 0x97, 0x38, 0x64, 0xdb, 0xfd,
|
||||
0x7c, 0x6f, 0x2d, 0xbe, 0xac, 0x55, 0xbd, 0x18, 0x1a, 0xd4, 0xb3, 0x56, 0xe0, 0x77, 0x9a, 0xde,
|
||||
0x2f, 0x68, 0x66, 0xd7, 0xf3, 0xbf, 0x8e, 0x88, 0x7e, 0x5b, 0x64, 0xc5, 0x79, 0x13, 0xd2, 0x55,
|
||||
0xbd, 0xa0, 0x5a, 0x49, 0xa9, 0xe6, 0x2f, 0xd7, 0x95, 0x64, 0x2f, 0x7e, 0x96, 0x1e, 0xcf, 0x7d,
|
||||
0xd5, 0x7d, 0x19, 0x1a, 0xec, 0x93, 0x5b, 0x66, 0x94, 0xb3, 0x4b, 0xf8, 0xf7, 0x7b, 0x53, 0x96,
|
||||
0x2e, 0xe4, 0x0b, 0x94, 0xf3, 0x57, 0xe8, 0xbd, 0x29, 0xf7, 0xd2, 0x2b, 0x53, 0x4e, 0x34, 0x13,
|
||||
0xca, 0xe9, 0x9d, 0x7b, 0x43, 0x8f, 0x1e, 0xfb, 0xd2, 0x43, 0xf3, 0x37, 0xf3, 0x62, 0xf7, 0x7e,
|
||||
0x2e, 0xcf, 0x57, 0xbc, 0x97, 0x65, 0xa7, 0xa7, 0xb4, 0x18, 0xfd, 0x0c, 0xc9, 0xa7, 0xd0, 0x7d,
|
||||
0x12, 0x42, 0xc5, 0x95, 0x45, 0xf9, 0xb6, 0xc0, 0x6a, 0xd9, 0xcc, 0xf8, 0x80, 0x0f, 0x91, 0x36,
|
||||
0xb3, 0x74, 0x10, 0x9a, 0x17, 0xb3, 0x1e, 0x97, 0xf2, 0xb5, 0xfe, 0xb2, 0xcd, 0xf2, 0xe3, 0xd4,
|
||||
0x2c, 0xe1, 0xf9, 0xee, 0x41, 0x59, 0x81, 0x67, 0x08, 0x43, 0x85, 0xf3, 0x91, 0xda, 0x88, 0x50,
|
||||
0xe3, 0xb7, 0xd1, 0x2c, 0xad, 0x16, 0x28, 0xc8, 0xe7, 0xca, 0x0a, 0x57, 0x2c, 0x50, 0x28, 0xe1,
|
||||
0xe5, 0xa9, 0x12, 0x4c, 0x4a, 0x7a, 0x33, 0x77, 0x3e, 0xfc, 0x64, 0xf4, 0xc8, 0xfe, 0x27, 0xa3,
|
||||
0x47, 0x3e, 0x3c, 0x18, 0xd5, 0xf6, 0x0f, 0x46, 0xb5, 0xef, 0xde, 0x1f, 0x3d, 0xf2, 0xee, 0xfd,
|
||||
0x51, 0x6d, 0xff, 0xfe, 0xe8, 0x91, 0x8f, 0xee, 0x8f, 0x1e, 0x79, 0xfd, 0x99, 0x75, 0x87, 0x6d,
|
||||
0x04, 0xb5, 0x2b, 0xb6, 0xd7, 0xbc, 0x9a, 0x66, 0xad, 0xd2, 0xaf, 0xec, 0xdf, 0x4b, 0xb5, 0x13,
|
||||
0xe2, 0xef, 0x4a, 0xd7, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x30, 0xd1, 0x0d, 0x1a, 0x25,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -419,6 +431,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])
|
||||
@@ -533,10 +559,10 @@ func (m *OptionsConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i--
|
||||
dAtA[i] = 0xb8
|
||||
}
|
||||
if len(m.DefaultFolderPath) > 0 {
|
||||
i -= len(m.DefaultFolderPath)
|
||||
copy(dAtA[i:], m.DefaultFolderPath)
|
||||
i = encodeVarintOptionsconfiguration(dAtA, i, uint64(len(m.DefaultFolderPath)))
|
||||
if len(m.DeprecatedDefaultFolderPath) > 0 {
|
||||
i -= len(m.DeprecatedDefaultFolderPath)
|
||||
copy(dAtA[i:], m.DeprecatedDefaultFolderPath)
|
||||
i = encodeVarintOptionsconfiguration(dAtA, i, uint64(len(m.DeprecatedDefaultFolderPath)))
|
||||
i--
|
||||
dAtA[i] = 0x2
|
||||
i--
|
||||
@@ -993,7 +1019,7 @@ func (m *OptionsConfiguration) ProtoSize() (n int) {
|
||||
if m.TrafficClass != 0 {
|
||||
n += 2 + sovOptionsconfiguration(uint64(m.TrafficClass))
|
||||
}
|
||||
l = len(m.DefaultFolderPath)
|
||||
l = len(m.DeprecatedDefaultFolderPath)
|
||||
if l > 0 {
|
||||
n += 2 + l + sovOptionsconfiguration(uint64(l))
|
||||
}
|
||||
@@ -1040,6 +1066,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
|
||||
}
|
||||
@@ -1917,7 +1949,7 @@ func (m *OptionsConfiguration) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
case 38:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DefaultFolderPath", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedDefaultFolderPath", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -1945,7 +1977,7 @@ func (m *OptionsConfiguration) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.DefaultFolderPath = string(dAtA[iNdEx:postIndex])
|
||||
m.DeprecatedDefaultFolderPath = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 39:
|
||||
if wireType != 0 {
|
||||
@@ -2218,6 +2250,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)
|
||||
|
||||
+42
-2
@@ -1,4 +1,4 @@
|
||||
<configuration version="31">
|
||||
<configuration version="34">
|
||||
<options>
|
||||
<listenAddress>tcp://:23000</listenAddress>
|
||||
<allowDelete>false</allowDelete>
|
||||
@@ -36,7 +36,6 @@
|
||||
<releasesURL>https://localhost/releases</releasesURL>
|
||||
<overwriteRemoteDeviceNamesOnConnect>true</overwriteRemoteDeviceNamesOnConnect>
|
||||
<tempIndexMinBlocks>100</tempIndexMinBlocks>
|
||||
<defaultFolderPath>/media/syncthing</defaultFolderPath>
|
||||
<setLowPriority>false</setLowPriority>
|
||||
<crashReportingURL>https://localhost/newcrash</crashReportingURL>
|
||||
<crashReportingEnabled>false</crashReportingEnabled>
|
||||
@@ -47,4 +46,45 @@
|
||||
<announceLANAddresses>false</announceLANAddresses>
|
||||
<featureFlag>feature</featureFlag>
|
||||
</options>
|
||||
<defaults>
|
||||
<folder id="" label="" path="/media/syncthing" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||
<filesystemType>basic</filesystemType>
|
||||
<minDiskFree unit="%">1</minDiskFree>
|
||||
<versioning type="trashcan">
|
||||
<param key="cleanoutDays" val="0"></param>
|
||||
<cleanupIntervalS>3600</cleanupIntervalS>
|
||||
</versioning>
|
||||
<copiers>0</copiers>
|
||||
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
|
||||
<hashers>0</hashers>
|
||||
<order>random</order>
|
||||
<ignoreDelete>false</ignoreDelete>
|
||||
<scanProgressIntervalS>0</scanProgressIntervalS>
|
||||
<pullerPauseS>0</pullerPauseS>
|
||||
<maxConflicts>10</maxConflicts>
|
||||
<disableSparseFiles>false</disableSparseFiles>
|
||||
<disableTempIndexes>false</disableTempIndexes>
|
||||
<paused>false</paused>
|
||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||
<markerName>.stfolder</markerName>
|
||||
<copyOwnershipFromParent>false</copyOwnershipFromParent>
|
||||
<modTimeWindowS>0</modTimeWindowS>
|
||||
<maxConcurrentWrites>2</maxConcurrentWrites>
|
||||
<disableFsync>false</disableFsync>
|
||||
<blockPullOrder>standard</blockPullOrder>
|
||||
<copyRangeMethod>standard</copyRangeMethod>
|
||||
<caseSensitiveFS>false</caseSensitiveFS>
|
||||
<junctionsAsDirs>false</junctionsAsDirs>
|
||||
</folder>
|
||||
<device id="" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||
<address>dynamic</address>
|
||||
<paused>false</paused>
|
||||
<autoAcceptFolders>false</autoAcceptFolders>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<maxRequestKiB>0</maxRequestKiB>
|
||||
<untrusted>false</untrusted>
|
||||
<remoteGUIPort>0</remoteGUIPort>
|
||||
</device>
|
||||
</defaults>
|
||||
</configuration>
|
||||
|
||||
@@ -52,7 +52,7 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *VersioningConfiguration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
func (c VersioningConfiguration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
// Using EncodeElement instead of plain Encode ensures that we use the
|
||||
// outer tag name from the VersioningConfiguration (i.e.,
|
||||
// `<versioning>`) rather than whatever the internal representation
|
||||
|
||||
+167
-189
@@ -7,7 +7,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -17,6 +20,13 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxModifications = 1000
|
||||
minSaveInterval = 5 * time.Second
|
||||
)
|
||||
|
||||
var errTooManyModifications = errors.New("too many concurrent config modifications")
|
||||
|
||||
// The Committer interface is implemented by objects that need to know about
|
||||
// or have a say in configuration changes.
|
||||
//
|
||||
@@ -36,6 +46,10 @@ import (
|
||||
// false will result in a "restart needed" response to the API/user. Note that
|
||||
// the new configuration will still have been applied by those who were
|
||||
// capable of doing so.
|
||||
//
|
||||
// A Committer must take care not to hold any locks while changing the
|
||||
// configuration (e.g. calling Wrapper.SetFolder), that are also acquired in any
|
||||
// methods of the Committer interface.
|
||||
type Committer interface {
|
||||
VerifyConfiguration(from, to Configuration) error
|
||||
CommitConfiguration(from, to Configuration) (handled bool)
|
||||
@@ -51,46 +65,49 @@ type noopWaiter struct{}
|
||||
|
||||
func (noopWaiter) Wait() {}
|
||||
|
||||
// A Wrapper around a Configuration that manages loads, saves and published
|
||||
// notifications of changes to registered Handlers
|
||||
// ModifyFunction gets a pointer to a copy of the currently active configuration
|
||||
// for modification.
|
||||
type ModifyFunction func(*Configuration)
|
||||
|
||||
// Wrapper handles a Configuration, i.e. it provides methods to access, change
|
||||
// and save the config, and notifies registered subscribers (Committer) of
|
||||
// changes.
|
||||
//
|
||||
// Modify allows changing the currently active configuration through the given
|
||||
// ModifyFunction. It can be called concurrently: All calls will be queued and
|
||||
// called in order.
|
||||
type Wrapper interface {
|
||||
ConfigPath() string
|
||||
MyID() protocol.DeviceID
|
||||
|
||||
RawCopy() Configuration
|
||||
Replace(cfg Configuration) (Waiter, error)
|
||||
RequiresRestart() bool
|
||||
Save() error
|
||||
|
||||
GUI() GUIConfiguration
|
||||
SetGUI(gui GUIConfiguration) (Waiter, error)
|
||||
LDAP() LDAPConfiguration
|
||||
SetLDAP(ldap LDAPConfiguration) (Waiter, error)
|
||||
Modify(ModifyFunction) (Waiter, error)
|
||||
RemoveFolder(id string) (Waiter, error)
|
||||
RemoveDevice(id protocol.DeviceID) (Waiter, error)
|
||||
|
||||
GUI() GUIConfiguration
|
||||
LDAP() LDAPConfiguration
|
||||
Options() OptionsConfiguration
|
||||
SetOptions(opts OptionsConfiguration) (Waiter, error)
|
||||
|
||||
Folder(id string) (FolderConfiguration, bool)
|
||||
Folders() map[string]FolderConfiguration
|
||||
FolderList() []FolderConfiguration
|
||||
RemoveFolder(id string) (Waiter, error)
|
||||
SetFolder(fld FolderConfiguration) (Waiter, error)
|
||||
SetFolders(folders []FolderConfiguration) (Waiter, error)
|
||||
FolderPasswords(device protocol.DeviceID) map[string]string
|
||||
DefaultFolder() FolderConfiguration
|
||||
|
||||
Device(id protocol.DeviceID) (DeviceConfiguration, bool)
|
||||
Devices() map[protocol.DeviceID]DeviceConfiguration
|
||||
DeviceList() []DeviceConfiguration
|
||||
RemoveDevice(id protocol.DeviceID) (Waiter, error)
|
||||
SetDevice(DeviceConfiguration) (Waiter, error)
|
||||
SetDevices([]DeviceConfiguration) (Waiter, error)
|
||||
DefaultDevice() DeviceConfiguration
|
||||
|
||||
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
|
||||
|
||||
Subscribe(c Committer)
|
||||
Subscribe(c Committer) Configuration
|
||||
Unsubscribe(c Committer)
|
||||
}
|
||||
|
||||
@@ -99,6 +116,7 @@ type wrapper struct {
|
||||
path string
|
||||
evLogger events.Logger
|
||||
myID protocol.DeviceID
|
||||
queue chan modifyEntry
|
||||
|
||||
waiter Waiter // Latest ongoing config change
|
||||
subs []Committer
|
||||
@@ -109,12 +127,15 @@ type wrapper struct {
|
||||
|
||||
// Wrap wraps an existing Configuration structure and ties it to a file on
|
||||
// disk.
|
||||
// The returned Wrapper is a suture.Service, thus needs to be started (added to
|
||||
// a supervisor).
|
||||
func Wrap(path string, cfg Configuration, myID protocol.DeviceID, evLogger events.Logger) Wrapper {
|
||||
w := &wrapper{
|
||||
cfg: cfg,
|
||||
path: path,
|
||||
evLogger: evLogger,
|
||||
myID: myID,
|
||||
queue: make(chan modifyEntry, maxModifications),
|
||||
waiter: noopWaiter{}, // Noop until first config change
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
@@ -123,6 +144,8 @@ func Wrap(path string, cfg Configuration, myID protocol.DeviceID, evLogger event
|
||||
|
||||
// Load loads an existing file on disk and returns a new configuration
|
||||
// wrapper.
|
||||
// The returned Wrapper is a suture.Service, thus needs to be started (added to
|
||||
// a supervisor).
|
||||
func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper, int, error) {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
@@ -147,11 +170,13 @@ func (w *wrapper) MyID() protocol.DeviceID {
|
||||
}
|
||||
|
||||
// Subscribe registers the given handler to be called on any future
|
||||
// configuration changes.
|
||||
func (w *wrapper) Subscribe(c Committer) {
|
||||
// configuration changes. It returns the config that is in effect while
|
||||
// subscribing, that can be used for initial setup.
|
||||
func (w *wrapper) Subscribe(c Committer) Configuration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
w.subs = append(w.subs, c)
|
||||
w.mut.Unlock()
|
||||
return w.cfg.Copy()
|
||||
}
|
||||
|
||||
// Unsubscribe de-registers the given handler from any future calls to
|
||||
@@ -181,11 +206,84 @@ func (w *wrapper) RawCopy() Configuration {
|
||||
return w.cfg.Copy()
|
||||
}
|
||||
|
||||
// Replace swaps the current configuration object for the given one.
|
||||
func (w *wrapper) Replace(cfg Configuration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.replaceLocked(cfg.Copy())
|
||||
func (w *wrapper) Modify(fn ModifyFunction) (Waiter, error) {
|
||||
return w.modifyQueued(fn)
|
||||
}
|
||||
|
||||
func (w *wrapper) modifyQueued(modifyFunc ModifyFunction) (Waiter, error) {
|
||||
e := modifyEntry{
|
||||
modifyFunc: modifyFunc,
|
||||
res: make(chan modifyResult),
|
||||
}
|
||||
select {
|
||||
case w.queue <- e:
|
||||
default:
|
||||
return noopWaiter{}, errTooManyModifications
|
||||
}
|
||||
res := <-e.res
|
||||
return res.w, res.err
|
||||
}
|
||||
|
||||
func (w *wrapper) Serve(ctx context.Context) error {
|
||||
defer w.serveSave()
|
||||
|
||||
var e modifyEntry
|
||||
saveTimer := time.NewTimer(0)
|
||||
<-saveTimer.C
|
||||
saveTimerRunning := false
|
||||
for {
|
||||
select {
|
||||
case e = <-w.queue:
|
||||
case <-saveTimer.C:
|
||||
w.serveSave()
|
||||
saveTimerRunning = false
|
||||
continue
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
var waiter Waiter = noopWaiter{}
|
||||
var err error
|
||||
|
||||
// Let the caller modify the config.
|
||||
to := w.RawCopy()
|
||||
e.modifyFunc(&to)
|
||||
|
||||
// Check if the config was actually changed at all.
|
||||
w.mut.Lock()
|
||||
if !reflect.DeepEqual(w.cfg, to) {
|
||||
waiter, err = w.replaceLocked(to)
|
||||
if !saveTimerRunning {
|
||||
saveTimer.Reset(minSaveInterval)
|
||||
saveTimerRunning = true
|
||||
}
|
||||
}
|
||||
w.mut.Unlock()
|
||||
|
||||
e.res <- modifyResult{
|
||||
w: waiter,
|
||||
err: err,
|
||||
}
|
||||
|
||||
// Wait for all subscriber to handle the config change before continuing
|
||||
// to process the next change.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
waiter.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wrapper) serveSave() {
|
||||
if err := w.Save(); err != nil {
|
||||
l.Warnln("Failed to save config:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
|
||||
@@ -248,55 +346,22 @@ func (w *wrapper) DeviceList() []DeviceConfiguration {
|
||||
return w.cfg.Copy().Devices
|
||||
}
|
||||
|
||||
// SetDevices adds new devices to the configuration, or overwrites existing
|
||||
// devices with the same ID.
|
||||
func (w *wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
newCfg := w.cfg.Copy()
|
||||
var replaced bool
|
||||
for oldIndex := range devs {
|
||||
replaced = false
|
||||
for newIndex := range newCfg.Devices {
|
||||
if newCfg.Devices[newIndex].DeviceID == devs[oldIndex].DeviceID {
|
||||
newCfg.Devices[newIndex] = devs[oldIndex].Copy()
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
newCfg.Devices = append(newCfg.Devices, devs[oldIndex].Copy())
|
||||
}
|
||||
}
|
||||
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// SetDevice adds a new device to the configuration, or overwrites an existing
|
||||
// device with the same ID.
|
||||
func (w *wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
|
||||
return w.SetDevices([]DeviceConfiguration{dev})
|
||||
}
|
||||
|
||||
// RemoveDevice removes the device from the configuration
|
||||
func (w *wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
newCfg := w.cfg.Copy()
|
||||
for i := range newCfg.Devices {
|
||||
if newCfg.Devices[i].DeviceID == id {
|
||||
newCfg.Devices = append(newCfg.Devices[:i], newCfg.Devices[i+1:]...)
|
||||
return w.replaceLocked(newCfg)
|
||||
return w.modifyQueued(func(cfg *Configuration) {
|
||||
if _, i, ok := cfg.Device(id); ok {
|
||||
cfg.Devices = append(cfg.Devices[:i], cfg.Devices[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return noopWaiter{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Folders returns a map of folders. Folder structures should not be changed,
|
||||
// other than for the purpose of updating via SetFolder().
|
||||
func (w *wrapper) DefaultDevice() DeviceConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Defaults.Device.Copy()
|
||||
}
|
||||
|
||||
// Folders returns a map of folders.
|
||||
func (w *wrapper) Folders() map[string]FolderConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
@@ -314,51 +379,13 @@ func (w *wrapper) FolderList() []FolderConfiguration {
|
||||
return w.cfg.Copy().Folders
|
||||
}
|
||||
|
||||
// SetFolder adds a new folder to the configuration, or overwrites an existing
|
||||
// folder with the same ID.
|
||||
func (w *wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
|
||||
return w.SetFolders([]FolderConfiguration{fld})
|
||||
}
|
||||
|
||||
// SetFolders adds new folders to the configuration, or overwrites existing
|
||||
// folders with the same ID.
|
||||
func (w *wrapper) SetFolders(folders []FolderConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
newCfg := w.cfg.Copy()
|
||||
|
||||
inds := make(map[string]int, len(w.cfg.Folders))
|
||||
for i, folder := range newCfg.Folders {
|
||||
inds[folder.ID] = i
|
||||
}
|
||||
filtered := folders[:0]
|
||||
for _, folder := range folders {
|
||||
if i, ok := inds[folder.ID]; ok {
|
||||
newCfg.Folders[i] = folder
|
||||
} else {
|
||||
filtered = append(filtered, folder)
|
||||
}
|
||||
}
|
||||
newCfg.Folders = append(newCfg.Folders, filtered...)
|
||||
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// RemoveFolder removes the folder from the configuration
|
||||
func (w *wrapper) RemoveFolder(id string) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
newCfg := w.cfg.Copy()
|
||||
for i := range newCfg.Folders {
|
||||
if newCfg.Folders[i].ID == id {
|
||||
newCfg.Folders = append(newCfg.Folders[:i], newCfg.Folders[i+1:]...)
|
||||
return w.replaceLocked(newCfg)
|
||||
return w.modifyQueued(func(cfg *Configuration) {
|
||||
if _, i, ok := cfg.Folder(id); ok {
|
||||
cfg.Folders = append(cfg.Folders[:i], cfg.Folders[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return noopWaiter{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// FolderPasswords returns the folder passwords set for this device, for
|
||||
@@ -369,6 +396,12 @@ func (w *wrapper) FolderPasswords(device protocol.DeviceID) map[string]string {
|
||||
return w.cfg.FolderPasswords(device)
|
||||
}
|
||||
|
||||
func (w *wrapper) DefaultFolder() FolderConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Defaults.Folder.Copy()
|
||||
}
|
||||
|
||||
// Options returns the current options configuration object.
|
||||
func (w *wrapper) Options() OptionsConfiguration {
|
||||
w.mut.Lock()
|
||||
@@ -376,29 +409,12 @@ func (w *wrapper) Options() OptionsConfiguration {
|
||||
return w.cfg.Options.Copy()
|
||||
}
|
||||
|
||||
// SetOptions replaces the current options configuration object.
|
||||
func (w *wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.Options = opts.Copy()
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
func (w *wrapper) LDAP() LDAPConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.LDAP.Copy()
|
||||
}
|
||||
|
||||
func (w *wrapper) SetLDAP(ldap LDAPConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.LDAP = ldap.Copy()
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// GUI returns the current GUI configuration object.
|
||||
func (w *wrapper) GUI() GUIConfiguration {
|
||||
w.mut.Lock()
|
||||
@@ -406,15 +422,6 @@ func (w *wrapper) GUI() GUIConfiguration {
|
||||
return w.cfg.GUI.Copy()
|
||||
}
|
||||
|
||||
// SetGUI replaces the current GUI configuration object.
|
||||
func (w *wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.GUI = gui.Copy()
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// IgnoredDevice returns whether or not connection attempts from the given
|
||||
// device should be silently ignored.
|
||||
func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
@@ -428,6 +435,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 {
|
||||
@@ -442,24 +458,22 @@ func (w *wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
|
||||
func (w *wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
for _, device := range w.cfg.Devices {
|
||||
if device.DeviceID == id {
|
||||
return device.Copy(), true
|
||||
}
|
||||
device, _, ok := w.cfg.Device(id)
|
||||
if !ok {
|
||||
return DeviceConfiguration{}, false
|
||||
}
|
||||
return DeviceConfiguration{}, false
|
||||
return device.Copy(), ok
|
||||
}
|
||||
|
||||
// Folder returns the configuration for the given folder and an "ok" bool.
|
||||
func (w *wrapper) Folder(id string) (FolderConfiguration, bool) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
for _, folder := range w.cfg.Folders {
|
||||
if folder.ID == id {
|
||||
return folder.Copy(), true
|
||||
}
|
||||
fcfg, _, ok := w.cfg.Folder(id)
|
||||
if !ok {
|
||||
return FolderConfiguration{}, false
|
||||
}
|
||||
return FolderConfiguration{}, false
|
||||
return fcfg.Copy(), ok
|
||||
}
|
||||
|
||||
// Save writes the configuration to disk, and generates a ConfigSaved event.
|
||||
@@ -496,48 +510,12 @@ 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,
|
||||
})
|
||||
type modifyEntry struct {
|
||||
modifyFunc ModifyFunction
|
||||
res chan modifyResult
|
||||
}
|
||||
|
||||
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")
|
||||
type modifyResult struct {
|
||||
w Waiter
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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:])
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package connections
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"io"
|
||||
"math/rand"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/thejerf/suture/v4"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
@@ -29,24 +31,41 @@ func init() {
|
||||
device4, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
|
||||
}
|
||||
|
||||
func initConfig() config.Wrapper {
|
||||
cfg := config.Wrap("/dev/null", config.New(device1), device1, events.NoopLogger)
|
||||
dev1Conf = config.NewDeviceConfiguration(device1, "device1")
|
||||
dev2Conf = config.NewDeviceConfiguration(device2, "device2")
|
||||
dev3Conf = config.NewDeviceConfiguration(device3, "device3")
|
||||
dev4Conf = config.NewDeviceConfiguration(device4, "device4")
|
||||
func newDeviceConfiguration(w config.Wrapper, id protocol.DeviceID, name string) config.DeviceConfiguration {
|
||||
cfg := w.DefaultDevice()
|
||||
cfg.DeviceID = id
|
||||
cfg.Name = name
|
||||
return cfg
|
||||
}
|
||||
|
||||
func initConfig() (config.Wrapper, context.CancelFunc) {
|
||||
wrapper := config.Wrap("/dev/null", config.New(device1), device1, events.NoopLogger)
|
||||
dev1Conf = newDeviceConfiguration(wrapper, device1, "device1")
|
||||
dev2Conf = newDeviceConfiguration(wrapper, device2, "device2")
|
||||
dev3Conf = newDeviceConfiguration(wrapper, device3, "device3")
|
||||
dev4Conf = newDeviceConfiguration(wrapper, device4, "device4")
|
||||
|
||||
var cancel context.CancelFunc = func() {}
|
||||
if wrapperService, ok := wrapper.(suture.Service); ok {
|
||||
var ctx context.Context
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
go wrapperService.Serve(ctx)
|
||||
}
|
||||
|
||||
dev2Conf.MaxRecvKbps = rand.Int() % 100000
|
||||
dev2Conf.MaxSendKbps = rand.Int() % 100000
|
||||
|
||||
waiter, _ := cfg.SetDevices([]config.DeviceConfiguration{dev1Conf, dev2Conf, dev3Conf, dev4Conf})
|
||||
waiter, _ := wrapper.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetDevices([]config.DeviceConfiguration{dev1Conf, dev2Conf, dev3Conf, dev4Conf})
|
||||
})
|
||||
waiter.Wait()
|
||||
return cfg
|
||||
return wrapper, cancel
|
||||
}
|
||||
|
||||
func TestLimiterInit(t *testing.T) {
|
||||
cfg := initConfig()
|
||||
lim := newLimiter(device1, cfg)
|
||||
wrapper, wrapperCancel := initConfig()
|
||||
defer wrapperCancel()
|
||||
lim := newLimiter(device1, wrapper)
|
||||
|
||||
device2ReadLimit := dev2Conf.MaxRecvKbps
|
||||
device2WriteLimit := dev2Conf.MaxSendKbps
|
||||
@@ -70,8 +89,9 @@ func TestLimiterInit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetDeviceLimits(t *testing.T) {
|
||||
cfg := initConfig()
|
||||
lim := newLimiter(device1, cfg)
|
||||
wrapper, wrapperCancel := initConfig()
|
||||
defer wrapperCancel()
|
||||
lim := newLimiter(device1, wrapper)
|
||||
|
||||
// should still be inf/inf because this is local device
|
||||
dev1ReadLimit := rand.Int() % 100000
|
||||
@@ -87,7 +107,9 @@ func TestSetDeviceLimits(t *testing.T) {
|
||||
dev3ReadLimit := rand.Int() % 10000
|
||||
dev3Conf.MaxRecvKbps = dev3ReadLimit
|
||||
|
||||
waiter, _ := cfg.SetDevices([]config.DeviceConfiguration{dev1Conf, dev2Conf, dev3Conf, dev4Conf})
|
||||
waiter, _ := wrapper.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetDevices([]config.DeviceConfiguration{dev1Conf, dev2Conf, dev3Conf, dev4Conf})
|
||||
})
|
||||
waiter.Wait()
|
||||
|
||||
expectedR := map[protocol.DeviceID]*rate.Limiter{
|
||||
@@ -108,10 +130,11 @@ func TestSetDeviceLimits(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveDevice(t *testing.T) {
|
||||
cfg := initConfig()
|
||||
lim := newLimiter(device1, cfg)
|
||||
wrapper, wrapperCancel := initConfig()
|
||||
defer wrapperCancel()
|
||||
lim := newLimiter(device1, wrapper)
|
||||
|
||||
waiter, _ := cfg.RemoveDevice(device3)
|
||||
waiter, _ := wrapper.RemoveDevice(device3)
|
||||
waiter.Wait()
|
||||
expectedR := map[protocol.DeviceID]*rate.Limiter{
|
||||
device2: rate.NewLimiter(rate.Limit(dev2Conf.MaxRecvKbps*1024), limiterBurstSize),
|
||||
@@ -128,15 +151,18 @@ func TestRemoveDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddDevice(t *testing.T) {
|
||||
cfg := initConfig()
|
||||
lim := newLimiter(device1, cfg)
|
||||
wrapper, wrapperCancel := initConfig()
|
||||
defer wrapperCancel()
|
||||
lim := newLimiter(device1, wrapper)
|
||||
|
||||
addedDevice, _ := protocol.DeviceIDFromString("XZJ4UNS-ENI7QGJ-J45DT6G-QSGML2K-6I4XVOG-NAZ7BF5-2VAOWNT-TFDOMQU")
|
||||
addDevConf := config.NewDeviceConfiguration(addedDevice, "addedDevice")
|
||||
addDevConf := newDeviceConfiguration(wrapper, addedDevice, "addedDevice")
|
||||
addDevConf.MaxRecvKbps = 120
|
||||
addDevConf.MaxSendKbps = 240
|
||||
|
||||
waiter, _ := cfg.SetDevice(addDevConf)
|
||||
waiter, _ := wrapper.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetDevice(addDevConf)
|
||||
})
|
||||
waiter.Wait()
|
||||
|
||||
expectedR := map[protocol.DeviceID]*rate.Limiter{
|
||||
@@ -159,17 +185,20 @@ func TestAddDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddAndRemove(t *testing.T) {
|
||||
cfg := initConfig()
|
||||
lim := newLimiter(device1, cfg)
|
||||
wrapper, wrapperCancel := initConfig()
|
||||
defer wrapperCancel()
|
||||
lim := newLimiter(device1, wrapper)
|
||||
|
||||
addedDevice, _ := protocol.DeviceIDFromString("XZJ4UNS-ENI7QGJ-J45DT6G-QSGML2K-6I4XVOG-NAZ7BF5-2VAOWNT-TFDOMQU")
|
||||
addDevConf := config.NewDeviceConfiguration(addedDevice, "addedDevice")
|
||||
addDevConf := newDeviceConfiguration(wrapper, addedDevice, "addedDevice")
|
||||
addDevConf.MaxRecvKbps = 120
|
||||
addDevConf.MaxSendKbps = 240
|
||||
|
||||
waiter, _ := cfg.SetDevice(addDevConf)
|
||||
waiter, _ := wrapper.Modify(func(cfg *config.Configuration) {
|
||||
cfg.SetDevice(addDevConf)
|
||||
})
|
||||
waiter.Wait()
|
||||
waiter, _ = cfg.RemoveDevice(device3)
|
||||
waiter, _ = wrapper.RemoveDevice(device3)
|
||||
waiter.Wait()
|
||||
|
||||
expectedR := map[protocol.DeviceID]*rate.Limiter{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+253
-186
@@ -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 {
|
||||
|
||||
+23
-31
@@ -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 {
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+20
-21
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
+74
-22
@@ -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"
|
||||
@@ -512,8 +513,10 @@ func TestCheckGlobals(t *testing.T) {
|
||||
}
|
||||
|
||||
// Clean up global entry of the now missing file
|
||||
if err := db.checkGlobals([]byte(fs.folder)); err != nil {
|
||||
if repaired, err := db.checkGlobals([]byte(fs.folder)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if repaired != 1 {
|
||||
t.Error("Expected 1 repaired global item, got", repaired)
|
||||
}
|
||||
|
||||
// Check that the global entry is gone
|
||||
@@ -532,14 +535,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 +649,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 +737,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 +747,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 +809,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 +847,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 +922,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 +936,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 {
|
||||
@@ -942,6 +951,49 @@ func TestDuplicateNeedCount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNeedAfterDropGlobal(t *testing.T) {
|
||||
db := newLowlevelMemory(t)
|
||||
defer db.Close()
|
||||
|
||||
folder := "test"
|
||||
testFs := fs.NewFilesystem(fs.FilesystemTypeFake, "")
|
||||
|
||||
fs := newFileSet(t, folder, testFs, db)
|
||||
|
||||
// Initial:
|
||||
// Three devices and a file "test": local has Version 1, remoteDevice0
|
||||
// Version 2 and remoteDevice2 doesn't have it.
|
||||
// All of them have "bar", just so the db knows about remoteDevice2.
|
||||
files := []protocol.FileInfo{
|
||||
{Name: "foo", Version: protocol.Vector{}.Update(myID), Sequence: 1},
|
||||
{Name: "bar", Version: protocol.Vector{}.Update(myID), Sequence: 2},
|
||||
}
|
||||
fs.Update(protocol.LocalDeviceID, files)
|
||||
files[0].Version = files[0].Version.Update(myID)
|
||||
fs.Update(remoteDevice0, files)
|
||||
fs.Update(remoteDevice1, files[1:])
|
||||
|
||||
// remoteDevice1 needs one file: test
|
||||
snap := fs.Snapshot()
|
||||
c := snap.NeedSize(remoteDevice1)
|
||||
if c.Files != 1 {
|
||||
t.Errorf("Expected 1 needed files initially, got %v", c.Files)
|
||||
}
|
||||
snap.Release()
|
||||
|
||||
// Drop remoteDevice0, i.e. remove all their files from db.
|
||||
// That changes the global file, which is now what local has.
|
||||
fs.Drop(remoteDevice0)
|
||||
|
||||
// remoteDevice1 still needs test.
|
||||
snap = fs.Snapshot()
|
||||
c = snap.NeedSize(remoteDevice1)
|
||||
if c.Files != 1 {
|
||||
t.Errorf("Expected still 1 needed files, got %v", c.Files)
|
||||
}
|
||||
snap.Release()
|
||||
}
|
||||
|
||||
func numBlockLists(db *Lowlevel) (int, error) {
|
||||
it, err := db.Backend.NewPrefixIterator([]byte{KeyTypeBlockList})
|
||||
if err != nil {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user