From 296cc1bca2bd6095e91a634ace5603e436e21860 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomasz=20Wilczy=C5=84ski?=
Date: Thu, 30 Sep 2021 19:19:18 +0900
Subject: [PATCH 001/220] lib/model: Limit the number of default hashers on
Android (ref #2220)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Like Windows and Mac, Android is also an interactive operating system.
On top of that, it usually runs on much slower hardware than the other
two. Because of that, it makes sense to limit the number of hashes used
by default there too.
Signed-off-by: Tomasz Wilczyński
---
lib/model/model.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/model/model.go b/lib/model/model.go
index 2218bd8020d..c1b3ddaa810 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -2403,7 +2403,7 @@ func (m *model) numHashers(folder string) int {
return folderCfg.Hashers
}
- if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
+ if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "android" {
// Interactive operating systems; don't load the system too heavily by
// default.
return 1
From 807a6b10221ea0a01dfedab0ce26473f20266761 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Fri, 29 Oct 2021 20:20:46 +0200
Subject: [PATCH 002/220] lib/model: Optimize jobQueue performance and memory
use (#8023)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
By truncating time.Time to an int64 nanosecond count, we lose the
ability to precisely order timestamps before 1678 or after 2262, but we
gain (linux/amd64, Go 1.17.1):
name old time/op new time/op delta
JobQueuePushPopDone10k-8 2.85ms ± 5% 2.29ms ± 2% -19.80% (p=0.000 n=20+18)
JobQueueBump-8 34.0µs ± 1% 29.8µs ± 1% -12.35% (p=0.000 n=19+19)
name old alloc/op new alloc/op delta
JobQueuePushPopDone10k-8 2.56MB ± 0% 1.76MB ± 0% -31.31% (p=0.000 n=18+13)
name old allocs/op new allocs/op delta
JobQueuePushPopDone10k-8 23.0 ± 0% 23.0 ± 0% ~ (all equal)
Results for BenchmarkJobQueueBump are with the fixed version, which no
longer depends on b.N for the amount of work performed. rand.Rand.Intn
is cheap at ~10ns per iteration.
---
lib/model/queue.go | 7 ++++---
lib/model/queue_test.go | 8 ++++++--
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/model/queue.go b/lib/model/queue.go
index 28070a9a47f..16ac83c9434 100644
--- a/lib/model/queue.go
+++ b/lib/model/queue.go
@@ -23,7 +23,7 @@ type jobQueue struct {
type jobQueueEntry struct {
name string
size int64
- modified time.Time
+ modified int64
}
func newJobQueue() *jobQueue {
@@ -34,7 +34,8 @@ func newJobQueue() *jobQueue {
func (q *jobQueue) Push(file string, size int64, modified time.Time) {
q.mut.Lock()
- q.queued = append(q.queued, jobQueueEntry{file, size, modified})
+ // The range of UnixNano covers a range of reasonable timestamps.
+ q.queued = append(q.queued, jobQueueEntry{file, size, modified.UnixNano()})
q.mut.Unlock()
}
@@ -191,5 +192,5 @@ func (q smallestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] }
type oldestFirst []jobQueueEntry
func (q oldestFirst) Len() int { return len(q) }
-func (q oldestFirst) Less(a, b int) bool { return q[a].modified.Before(q[b].modified) }
+func (q oldestFirst) Less(a, b int) bool { return q[a].modified < q[b].modified }
func (q oldestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] }
diff --git a/lib/model/queue_test.go b/lib/model/queue_test.go
index 677c056dd72..9f49a683b2e 100644
--- a/lib/model/queue_test.go
+++ b/lib/model/queue_test.go
@@ -8,6 +8,7 @@ package model
import (
"fmt"
+ "math/rand"
"testing"
"time"
@@ -251,16 +252,19 @@ func TestSortByAge(t *testing.T) {
}
func BenchmarkJobQueueBump(b *testing.B) {
- files := genFiles(b.N)
+ files := genFiles(10000)
q := newJobQueue()
for _, f := range files {
q.Push(f.Name, 0, time.Time{})
}
+ rng := rand.New(rand.NewSource(int64(b.N)))
+
b.ResetTimer()
for i := 0; i < b.N; i++ {
- q.BringToFront(files[i].Name)
+ r := rng.Intn(len(files))
+ q.BringToFront(files[r].Name)
}
}
From 28ff033da65292868bd8e8f82d3083da2c338ff2 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Fri, 29 Oct 2021 20:21:50 +0200
Subject: [PATCH 003/220] cmd/syncthing/cli: indexDumpSize doesn't need a heap
(#8024)
---
cmd/syncthing/cli/index_dumpsize.go | 46 ++++++++++-------------------
1 file changed, 15 insertions(+), 31 deletions(-)
diff --git a/cmd/syncthing/cli/index_dumpsize.go b/cmd/syncthing/cli/index_dumpsize.go
index 35e095cd80a..79c399d9838 100644
--- a/cmd/syncthing/cli/index_dumpsize.go
+++ b/cmd/syncthing/cli/index_dumpsize.go
@@ -7,53 +7,35 @@
package cli
import (
- "container/heap"
"encoding/binary"
"fmt"
+ "sort"
"github.com/urfave/cli"
"github.com/syncthing/syncthing/lib/db"
)
-type SizedElement struct {
- key string
- size int
-}
-
-type ElementHeap []SizedElement
-
-func (h ElementHeap) Len() int { return len(h) }
-func (h ElementHeap) Less(i, j int) bool { return h[i].size > h[j].size }
-func (h ElementHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
-
-func (h *ElementHeap) Push(x interface{}) {
- *h = append(*h, x.(SizedElement))
-}
-
-func (h *ElementHeap) Pop() interface{} {
- old := *h
- n := len(old)
- x := old[n-1]
- *h = old[0 : n-1]
- return x
-}
-
func indexDumpSize(*cli.Context) error {
+ type sizedElement struct {
+ key string
+ size int
+ }
+
ldb, err := getDB()
if err != nil {
return err
}
- h := &ElementHeap{}
- heap.Init(h)
-
it, err := ldb.NewPrefixIterator(nil)
if err != nil {
return err
}
- var ele SizedElement
+
+ var elems []sizedElement
for it.Next() {
+ var ele sizedElement
+
key := it.Key()
switch key[0] {
case db.KeyTypeDevice:
@@ -94,11 +76,13 @@ func indexDumpSize(*cli.Context) error {
ele.key = fmt.Sprintf("UNKNOWN:%x", key)
}
ele.size = len(it.Value())
- heap.Push(h, ele)
+ elems = append(elems, ele)
}
- for h.Len() > 0 {
- ele = heap.Pop(h).(SizedElement)
+ sort.Slice(elems, func(i, j int) bool {
+ return elems[i].size > elems[j].size
+ })
+ for _, ele := range elems {
fmt.Println(ele.key, ele.size)
}
From 1c2e96a5ca470773bdfd176a9e84e54b168e4d09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Fri, 29 Oct 2021 20:23:41 +0200
Subject: [PATCH 004/220] gui: Display identicons for discovered device IDs.
(#8022)
---
gui/default/syncthing/device/editDeviceModalView.html | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/gui/default/syncthing/device/editDeviceModalView.html b/gui/default/syncthing/device/editDeviceModalView.html
index 38fb995c41b..0b772c4373f 100644
--- a/gui/default/syncthing/device/editDeviceModalView.html
+++ b/gui/default/syncthing/device/editDeviceModalView.html
@@ -25,7 +25,11 @@
You can also select one of these nearby devices:
From 41bfb7a3302ea7e751fb40a6b8b5a021a0634f2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Thu, 4 Nov 2021 08:42:55 +0100
Subject: [PATCH 005/220] Normalize CLI options to always use two dashes.
(#8037)
Consistently use double dashes and fix typos -conf, -data-dir and
-verify.
Applies also to tests running the syncthing binary for consistency.
* Fix mismatched option name --conf in cli subcommand.
According to the source code comments, the cli option flags should
mirror those from the serve subcommand where applicable. That one is
actually called --config though.
* cli: Fix help text option placeholders.
The urfave/cli package uses the Value field of StringFlag to provide a
default value, not to name the placeholder. That is instead done with
backticks around some part of the Usage field.
* cli: Add missing --data flag in subcommand help text.
The urfave/cli based option parsing uses a fake flags collection to
generate help texts matching the used global options. But the --data
option was omitted from it, although it is definitely required when
using --config as well. Note that it cannot just be ignored, as some
debug stuff actually uses the DB:
syncthing cli --data=/bar --config=/foo debug index dump
---
cmd/syncthing/cli/main.go | 18 +++++++++---------
cmd/syncthing/cmdutil/util.go | 4 ++--
cmd/syncthing/decrypt/decrypt.go | 2 +-
cmd/syncthing/main.go | 4 ++--
lib/locations/locations.go | 6 +++---
lib/syncthing/utils.go | 2 +-
test/cli_test.go | 8 ++++----
test/override_test.go | 4 ++--
test/util.go | 2 +-
9 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/cmd/syncthing/cli/main.go b/cmd/syncthing/cli/main.go
index f1ef9e4a4d2..8e08b92ef5a 100644
--- a/cmd/syncthing/cli/main.go
+++ b/cmd/syncthing/cli/main.go
@@ -61,23 +61,23 @@ func Run() error {
fakeFlags := []cli.Flag{
cli.StringFlag{
Name: "gui-address",
- Value: "URL",
- Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
+ Usage: "Override GUI address to `URL` (e.g. \"http://192.0.2.42:8443\")",
},
cli.StringFlag{
Name: "gui-apikey",
- Value: "API-KEY",
- Usage: "Override GUI API key",
+ Usage: "Override GUI API key to `API-KEY`",
},
cli.StringFlag{
Name: "home",
- Value: "PATH",
- Usage: "Set configuration and data directory",
+ Usage: "Set configuration and data directory to `PATH`",
},
cli.StringFlag{
- Name: "conf",
- Value: "PATH",
- Usage: "Set configuration directory (config and keys)",
+ Name: "config",
+ Usage: "Set configuration directory (config and keys) to `PATH`",
+ },
+ cli.StringFlag{
+ Name: "data",
+ Usage: "Set data directory (database and logs) to `PATH`",
},
}
diff --git a/cmd/syncthing/cmdutil/util.go b/cmd/syncthing/cmdutil/util.go
index 7c15f299513..6a44456b095 100644
--- a/cmd/syncthing/cmdutil/util.go
+++ b/cmd/syncthing/cmdutil/util.go
@@ -18,9 +18,9 @@ func SetConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error {
dataSet := dataDir != ""
switch {
case dataSet != confSet:
- return errors.New("either both or none of -conf and -data must be given, use -home to set both at once")
+ return errors.New("either both or none of --config and --data must be given, use --home to set both at once")
case homeSet && dataSet:
- return errors.New("-home must not be used together with -conf and -data")
+ return errors.New("--home must not be used together with --config and --data")
case homeSet:
confDir = homeDir
dataDir = homeDir
diff --git a/cmd/syncthing/decrypt/decrypt.go b/cmd/syncthing/decrypt/decrypt.go
index 0247bb20f01..119583dbd70 100644
--- a/cmd/syncthing/decrypt/decrypt.go
+++ b/cmd/syncthing/decrypt/decrypt.go
@@ -45,7 +45,7 @@ func (c *CLI) Run() error {
log.SetFlags(0)
if c.To == "" && !c.VerifyOnly {
- return fmt.Errorf("must set --to or --verify")
+ return fmt.Errorf("must set --to or --verify-only")
}
if c.TokenPath == "" {
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 00e2eef5d95..b21273ab69b 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -76,9 +76,9 @@ above). The value 0 is used to disable all of the above. The default is to
show time only (2).
Logging always happens to the command line (stdout) and optionally to the
-file at the path specified by -logfile=path. In addition to an path, the special
+file at the path specified by --logfile=path. In addition to an path, the special
values "default" and "-" may be used. The former logs to DATADIR/syncthing.log
-(see -data-dir), which is the default on Windows, and the latter only to stdout,
+(see --data), which is the default on Windows, and the latter only to stdout,
no file, which is the default anywhere else.
diff --git a/lib/locations/locations.go b/lib/locations/locations.go
index 3df0b877f0a..12077471ad2 100644
--- a/lib/locations/locations.go
+++ b/lib/locations/locations.go
@@ -40,10 +40,10 @@ const (
type BaseDirEnum string
const (
- // Overridden by -home flag
+ // Overridden by --home flag
ConfigBaseDir BaseDirEnum = "config"
DataBaseDir BaseDirEnum = "data"
- // User's home directory, *not* -home flag
+ // User's home directory, *not* --home flag
UserHomeBaseDir BaseDirEnum = "userHome"
LevelDBDir = "index-v0.14.0.db"
@@ -98,7 +98,7 @@ var locationTemplates = map[LocationEnum]string{
HTTPSCertFile: "${config}/https-cert.pem",
HTTPSKeyFile: "${config}/https-key.pem",
Database: "${data}/" + LevelDBDir,
- LogFile: "${data}/syncthing.log", // -logfile on Windows
+ LogFile: "${data}/syncthing.log", // --logfile on Windows
CsrfTokens: "${data}/csrftokens.txt",
PanicLog: "${data}/panic-${timestamp}.log",
AuditLog: "${data}/audit-${timestamp}.log",
diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go
index 3ddd6e56994..660f856d118 100644
--- a/lib/syncthing/utils.go
+++ b/lib/syncthing/utils.go
@@ -90,7 +90,7 @@ func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logg
l.Infof("Now, THAT's what we call a config from the future! Don't worry. As long as you hit that wire with the connecting hook at precisely eighty-eight miles per hour the instant the lightning strikes the tower... everything will be fine.")
}
if originalVersion > config.CurrentVersion && !allowNewerConfig {
- return nil, fmt.Errorf("config file version (%d) is newer than supported version (%d). If this is expected, use -allow-newer-config to override.", originalVersion, config.CurrentVersion)
+ return nil, fmt.Errorf("config file version (%d) is newer than supported version (%d). If this is expected, use --allow-newer-config to override.", originalVersion, config.CurrentVersion)
}
err = archiveAndSaveConfig(cfg, originalVersion)
if err != nil {
diff --git a/test/cli_test.go b/test/cli_test.go
index f8e783893c1..c19d4383124 100644
--- a/test/cli_test.go
+++ b/test/cli_test.go
@@ -31,7 +31,7 @@ func TestCLIReset(t *testing.T) {
// Run reset to clean up
- cmd := exec.Command("../bin/syncthing", "-no-browser", "-home", "h1", "-reset-database")
+ cmd := exec.Command("../bin/syncthing", "--no-browser", "--home", "h1", "--reset-database")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
err := cmd.Run()
@@ -63,9 +63,9 @@ func TestCLIGenerate(t *testing.T) {
t.Fatal(err)
}
- // -generate should create a bunch of stuff
+ // --generate should create a bunch of stuff
- cmd := exec.Command("../bin/syncthing", "-no-browser", "-generate", "home.out")
+ cmd := exec.Command("../bin/syncthing", "--no-browser", "--generate", "home.out")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
err = cmd.Run()
@@ -91,7 +91,7 @@ func TestCLIFirstStartup(t *testing.T) {
// First startup should create config, BEP certificate, and HTTP certificate.
- cmd := exec.Command("../bin/syncthing", "-no-browser", "-home", "home.out")
+ cmd := exec.Command("../bin/syncthing", "--no-browser", "--home", "home.out")
cmd.Env = append(os.Environ(), "STNORESTART=1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
diff --git a/test/override_test.go b/test/override_test.go
index a4af61bf576..b2fa14f11cd 100644
--- a/test/override_test.go
+++ b/test/override_test.go
@@ -199,7 +199,7 @@ func TestOverrideIgnores(t *testing.T) {
log.Println("Starting sendOnly...")
sendOnly := syncthingProcess{ // id1
instance: "1",
- argv: []string{"-home", "h1"},
+ argv: []string{"--home", "h1"},
port: 8081,
apiKey: apiKey,
}
@@ -212,7 +212,7 @@ func TestOverrideIgnores(t *testing.T) {
log.Println("Starting sendRecv...")
sendRecv := syncthingProcess{ // id2
instance: "2",
- argv: []string{"-home", "h2"},
+ argv: []string{"--home", "h2"},
port: 8082,
apiKey: apiKey,
}
diff --git a/test/util.go b/test/util.go
index 38b00a5a704..8f69dacdaa1 100644
--- a/test/util.go
+++ b/test/util.go
@@ -542,7 +542,7 @@ func startInstance(t *testing.T, i int) *rc.Process {
p := rc.NewProcess(addr)
p.LogTo(log)
- if err := p.Start("../bin/syncthing", "-home", fmt.Sprintf("h%d", i), "-no-browser"); err != nil {
+ if err := p.Start("../bin/syncthing", "--home", fmt.Sprintf("h%d", i), "--no-browser"); err != nil {
t.Fatal(err)
}
p.AwaitStartup()
From db15e5274346e4d3ba343c12a00bc7d1744f1b75 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Sat, 6 Nov 2021 12:38:08 +0100
Subject: [PATCH 006/220] lib/api: http.Request.BasicAuth instead of custom
code (#8039)
---
lib/api/api_auth.go | 22 ++--------------------
1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/lib/api/api_auth.go b/lib/api/api_auth.go
index 5a009c5db00..d7d7d5a2509 100644
--- a/lib/api/api_auth.go
+++ b/lib/api/api_auth.go
@@ -7,9 +7,7 @@
package api
import (
- "bytes"
"crypto/tls"
- "encoding/base64"
"fmt"
"net"
"net/http"
@@ -66,28 +64,12 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
http.Error(w, "Not Authorized", http.StatusUnauthorized)
}
- hdr := r.Header.Get("Authorization")
- if !strings.HasPrefix(hdr, "Basic ") {
+ username, password, ok := r.BasicAuth()
+ if !ok {
error()
return
}
- hdr = hdr[6:]
- bs, err := base64.StdEncoding.DecodeString(hdr)
- if err != nil {
- error()
- return
- }
-
- fields := bytes.SplitN(bs, []byte(":"), 2)
- if len(fields) != 2 {
- error()
- return
- }
-
- username := string(fields[0])
- password := string(fields[1])
-
authOk := auth(username, password, guiCfg, ldapCfg)
if !authOk {
usernameIso := string(iso88591ToUTF8([]byte(username)))
From ec8a748514e93f41e94478b59679afe67eb1d3a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Sun, 7 Nov 2021 23:59:48 +0100
Subject: [PATCH 007/220] lib/syncthing: Clean up / refactor
LoadOrGenerateCertificate() utility function. (#8025)
LoadOrGenerateCertificate() takes two file path arguments, but then
uses the locations package to determine the actual path. Fix that
with a minimally invasive change, by using the arguments instead.
Factor out GenerateCertificate().
The only caller of this function is cmd/syncthing, which passes the
same values, so this is technically a no-op.
* lib/tlsutil: Make storing generated certificate optional. Avoid
temporary cert and key files in tests, keep cert in memory.
---
cmd/syncthing/main.go | 7 ++--
lib/api/api_test.go | 12 ++-----
lib/connections/connections_test.go | 16 +--------
lib/discover/global_test.go | 14 ++------
lib/syncthing/syncthing_test.go | 9 +-----
lib/syncthing/utils.go | 18 ++++-------
lib/tlsutil/tlsutil.go | 50 +++++++++++++++++++----------
7 files changed, 49 insertions(+), 77 deletions(-)
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index b21273ab69b..21a8fbe6bc5 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -49,16 +49,13 @@ import (
"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/pkg/errors"
)
const (
- tlsDefaultCommonName = "syncthing"
- deviceCertLifetimeDays = 20 * 365
- sigTerm = syscall.Signal(15)
+ sigTerm = syscall.Signal(15)
)
const (
@@ -442,7 +439,7 @@ func generate(generateDir string, noDefaultFolder bool) error {
if err == nil {
l.Warnln("Key exists; will not overwrite.")
} else {
- cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
+ cert, err = syncthing.GenerateCertificate(certFile, keyFile)
if err != nil {
return errors.Wrap(err, "create certificate")
}
diff --git a/lib/api/api_test.go b/lib/api/api_test.go
index 951776d7eac..f9ecc15c5cc 100644
--- a/lib/api/api_test.go
+++ b/lib/api/api_test.go
@@ -1209,15 +1209,9 @@ func TestPrefixMatch(t *testing.T) {
}
func TestShouldRegenerateCertificate(t *testing.T) {
- dir, err := ioutil.TempDir("", "syncthing-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
// Self signed certificates expiring in less than a month are errored so we
// can regenerate in time.
- crt, err := tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 29)
+ crt, err := tlsutil.NewCertificateInMemory("foo.example.com", 29)
if err != nil {
t.Fatal(err)
}
@@ -1226,7 +1220,7 @@ func TestShouldRegenerateCertificate(t *testing.T) {
}
// Certificates with at least 31 days of life left are fine.
- crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 31)
+ crt, err = tlsutil.NewCertificateInMemory("foo.example.com", 31)
if err != nil {
t.Fatal(err)
}
@@ -1236,7 +1230,7 @@ func TestShouldRegenerateCertificate(t *testing.T) {
if runtime.GOOS == "darwin" {
// Certificates with too long an expiry time are not allowed on macOS
- crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 1000)
+ crt, err = tlsutil.NewCertificateInMemory("foo.example.com", 1000)
if err != nil {
t.Fatal(err)
}
diff --git a/lib/connections/connections_test.go b/lib/connections/connections_test.go
index b9a10d5caa5..a7d4caf1939 100644
--- a/lib/connections/connections_test.go
+++ b/lib/connections/connections_test.go
@@ -11,11 +11,9 @@ import (
"crypto/tls"
"errors"
"fmt"
- "io/ioutil"
"math/rand"
"net"
"net/url"
- "os"
"strings"
"testing"
"time"
@@ -470,21 +468,9 @@ func withConnectionPair(b *testing.B, connUri string, h func(client, server inte
}
func mustGetCert(b *testing.B) tls.Certificate {
- f1, err := ioutil.TempFile("", "")
+ cert, err := tlsutil.NewCertificateInMemory("bench", 10)
if err != nil {
b.Fatal(err)
}
- f1.Close()
- f2, err := ioutil.TempFile("", "")
- if err != nil {
- b.Fatal(err)
- }
- f2.Close()
- cert, err := tlsutil.NewCertificate(f1.Name(), f2.Name(), "bench", 10)
- if err != nil {
- b.Fatal(err)
- }
- _ = os.Remove(f1.Name())
- _ = os.Remove(f2.Name())
return cert
}
diff --git a/lib/discover/global_test.go b/lib/discover/global_test.go
index 8ebbc7c4d3e..8a9761782ed 100644
--- a/lib/discover/global_test.go
+++ b/lib/discover/global_test.go
@@ -107,13 +107,8 @@ func TestGlobalOverHTTP(t *testing.T) {
}
func TestGlobalOverHTTPS(t *testing.T) {
- dir, err := ioutil.TempDir("", "syncthing")
- if err != nil {
- t.Fatal(err)
- }
-
// Generate a server certificate.
- cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
+ cert, err := tlsutil.NewCertificateInMemory("syncthing", 30)
if err != nil {
t.Fatal(err)
}
@@ -172,13 +167,8 @@ func TestGlobalOverHTTPS(t *testing.T) {
}
func TestGlobalAnnounce(t *testing.T) {
- dir, err := ioutil.TempDir("", "syncthing")
- if err != nil {
- t.Fatal(err)
- }
-
// Generate a server certificate.
- cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
+ cert, err := tlsutil.NewCertificateInMemory("syncthing", 30)
if err != nil {
t.Fatal(err)
}
diff --git a/lib/syncthing/syncthing_test.go b/lib/syncthing/syncthing_test.go
index 5c6d6278cf1..8add69ee821 100644
--- a/lib/syncthing/syncthing_test.go
+++ b/lib/syncthing/syncthing_test.go
@@ -9,7 +9,6 @@ package syncthing
import (
"io/ioutil"
"os"
- "path/filepath"
"testing"
"time"
@@ -57,13 +56,7 @@ func TestShortIDCheck(t *testing.T) {
}
func TestStartupFail(t *testing.T) {
- tmpDir, err := ioutil.TempDir("", "syncthing-TestStartupFail-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDir)
-
- cert, err := tlsutil.NewCertificate(filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key"), "syncthing", 365)
+ cert, err := tlsutil.NewCertificateInMemory("syncthing", 365)
if err != nil {
t.Fatal(err)
}
diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go
index 660f856d118..468e94a045a 100644
--- a/lib/syncthing/utils.go
+++ b/lib/syncthing/utils.go
@@ -25,22 +25,18 @@ import (
)
func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
- cert, err := tls.LoadX509KeyPair(
- locations.Get(locations.CertFile),
- locations.Get(locations.KeyFile),
- )
+ cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
- l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName)
- return tlsutil.NewCertificate(
- locations.Get(locations.CertFile),
- locations.Get(locations.KeyFile),
- tlsDefaultCommonName,
- deviceCertLifetimeDays,
- )
+ return GenerateCertificate(certFile, keyFile)
}
return cert, nil
}
+func GenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
+ l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName)
+ return tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
+}
+
func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder bool) (config.Wrapper, error) {
newCfg, err := config.NewWithFreePorts(myID)
if err != nil {
diff --git a/lib/tlsutil/tlsutil.go b/lib/tlsutil/tlsutil.go
index 146e6d96c01..7ef22d5c73a 100644
--- a/lib/tlsutil/tlsutil.go
+++ b/lib/tlsutil/tlsutil.go
@@ -86,11 +86,11 @@ func SecureDefaultWithTLS12() *tls.Config {
}
}
-// NewCertificate generates and returns a new TLS certificate.
-func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls.Certificate, error) {
+// generateCertificate generates a PEM formatted key pair and self-signed certificate in memory.
+func generateCertificate(commonName string, lifetimeDays int) (*pem.Block, *pem.Block, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
- return tls.Certificate{}, errors.Wrap(err, "generate key")
+ return nil, nil, errors.Wrap(err, "generate key")
}
notBefore := time.Now().Truncate(24 * time.Hour)
@@ -117,19 +117,33 @@ func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
- return tls.Certificate{}, errors.Wrap(err, "create cert")
+ return nil, nil, errors.Wrap(err, "create cert")
+ }
+
+ certBlock := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}
+ keyBlock, err := pemBlockForKey(priv)
+ if err != nil {
+ return nil, nil, errors.Wrap(err, "save key")
+ }
+
+ return certBlock, keyBlock, nil
+}
+
+// NewCertificate generates and returns a new TLS certificate, saved to the given PEM files.
+func NewCertificate(certFile, keyFile string, commonName string, lifetimeDays int) (tls.Certificate, error) {
+ certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays)
+ if err != nil {
+ return tls.Certificate{}, err
}
certOut, err := os.Create(certFile)
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "save cert")
}
- err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
- if err != nil {
+ if err = pem.Encode(certOut, certBlock); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save cert")
}
- err = certOut.Close()
- if err != nil {
+ if err = certOut.Close(); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save cert")
}
@@ -137,22 +151,24 @@ func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
-
- block, err := pemBlockForKey(priv)
- if err != nil {
+ if err = pem.Encode(keyOut, keyBlock); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
-
- err = pem.Encode(keyOut, block)
- if err != nil {
+ if err = keyOut.Close(); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
- err = keyOut.Close()
+
+ return tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
+}
+
+// NewCertificateInMemory generates and returns a new TLS certificate, kept only in memory.
+func NewCertificateInMemory(commonName string, lifetimeDays int) (tls.Certificate, error) {
+ certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays)
if err != nil {
- return tls.Certificate{}, errors.Wrap(err, "save key")
+ return tls.Certificate{}, err
}
- return tls.LoadX509KeyPair(certFile, keyFile)
+ return tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
}
type DowngradingListener struct {
From dec6f80d2bc9067347d80cba692da49c38cbdfde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Mon, 8 Nov 2021 13:32:04 +0100
Subject: [PATCH 008/220] lib/config: Move the bcrypt password hashing to
GUIConfiguration (#8028)
What hash is used to store the password should ideally be an
implementation detail, so that every user of the GUIConfiguration
object automatically agrees on how to handle it. That is currently
distribututed over the confighandler.go and api_auth.go files, plus
tests.
Add the SetHasedPassword() / CompareHashedPassword() API to keep the
hashing method encapsulated. Add a separate test for it and adjust
other users and tests. Remove all deprecated imports of the bcrypt
package.
---
lib/api/api_auth.go | 9 +++------
lib/api/api_auth_test.go | 13 +++++++------
lib/api/confighandler.go | 33 ++++++++++++++-------------------
lib/config/config_test.go | 21 +++++++++++++++++++++
lib/config/guiconfiguration.go | 19 +++++++++++++++++++
5 files changed, 64 insertions(+), 31 deletions(-)
diff --git a/lib/api/api_auth.go b/lib/api/api_auth.go
index d7d7d5a2509..70add9a9541 100644
--- a/lib/api/api_auth.go
+++ b/lib/api/api_auth.go
@@ -19,7 +19,6 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sync"
- "golang.org/x/crypto/bcrypt"
)
var (
@@ -117,14 +116,12 @@ func auth(username string, password string, guiCfg config.GUIConfiguration, ldap
if guiCfg.AuthMode == config.AuthModeLDAP {
return authLDAP(username, password, ldapCfg)
} else {
- return authStatic(username, password, guiCfg.User, guiCfg.Password)
+ return authStatic(username, password, guiCfg)
}
}
-func authStatic(username string, password string, configUser string, configPassword string) bool {
- configPasswordBytes := []byte(configPassword)
- passwordBytes := []byte(password)
- return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes) == nil && username == configUser
+func authStatic(username string, password string, guiCfg config.GUIConfiguration) bool {
+ return guiCfg.CompareHashedPassword(password) == nil && username == guiCfg.User
}
func authLDAP(username string, password string, cfg config.LDAPConfiguration) bool {
diff --git a/lib/api/api_auth_test.go b/lib/api/api_auth_test.go
index 4735378a5bf..046b06a0771 100644
--- a/lib/api/api_auth_test.go
+++ b/lib/api/api_auth_test.go
@@ -9,19 +9,20 @@ package api
import (
"testing"
- "golang.org/x/crypto/bcrypt"
+ "github.com/syncthing/syncthing/lib/config"
)
-var passwordHashBytes []byte
+var guiCfg config.GUIConfiguration
func init() {
- passwordHashBytes, _ = bcrypt.GenerateFromPassword([]byte("pass"), 0)
+ guiCfg.User = "user"
+ guiCfg.HashAndSetPassword("pass")
}
func TestStaticAuthOK(t *testing.T) {
t.Parallel()
- ok := authStatic("user", "pass", "user", string(passwordHashBytes))
+ ok := authStatic("user", "pass", guiCfg)
if !ok {
t.Fatalf("should pass auth")
}
@@ -30,7 +31,7 @@ func TestStaticAuthOK(t *testing.T) {
func TestSimpleAuthUsernameFail(t *testing.T) {
t.Parallel()
- ok := authStatic("userWRONG", "pass", "user", string(passwordHashBytes))
+ ok := authStatic("userWRONG", "pass", guiCfg)
if ok {
t.Fatalf("should fail auth")
}
@@ -39,7 +40,7 @@ func TestSimpleAuthUsernameFail(t *testing.T) {
func TestStaticAuthPasswordFail(t *testing.T) {
t.Parallel()
- ok := authStatic("user", "passWRONG", "user", string(passwordHashBytes))
+ ok := authStatic("user", "passWRONG", guiCfg)
if ok {
t.Fatalf("should fail auth")
}
diff --git a/lib/api/confighandler.go b/lib/api/confighandler.go
index 8f03acfdff5..24e6b118c16 100644
--- a/lib/api/confighandler.go
+++ b/lib/api/confighandler.go
@@ -13,7 +13,6 @@ import (
"net/http"
"github.com/julienschmidt/httprouter"
- "golang.org/x/crypto/bcrypt"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
@@ -290,11 +289,13 @@ func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request)
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
+ if to.GUI.Password != cfg.GUI.Password {
+ if err := to.GUI.HashAndSetPassword(to.GUI.Password); err != nil {
+ l.Warnln("hashing password:", err)
+ errMsg = err.Error()
+ status = http.StatusInternalServerError
+ return
+ }
}
*cfg = to
})
@@ -370,11 +371,13 @@ func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui
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
+ if gui.Password != oldPassword {
+ if err := gui.HashAndSetPassword(gui.Password); err != nil {
+ l.Warnln("hashing password:", err)
+ errMsg = err.Error()
+ status = http.StatusInternalServerError
+ return
+ }
}
cfg.GUI = gui
})
@@ -418,14 +421,6 @@ func unmarshalToRawMessages(body io.ReadCloser) ([]json.RawMessage, error) {
return data, err
}
-func checkGUIPassword(oldPassword, newPassword string) (string, error) {
- if newPassword == oldPassword {
- return newPassword, nil
- }
- hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), 0)
- return string(hash), err
-}
-
func (c *configMuxBuilder) finish(w http.ResponseWriter, waiter config.Waiter) {
waiter.Wait()
if err := c.cfg.Save(); err != nil {
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index e49aa977604..5da13207c9c 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -739,6 +739,27 @@ func TestGUIConfigURL(t *testing.T) {
}
}
+func TestGUIPasswordHash(t *testing.T) {
+ var c GUIConfiguration
+
+ testPass := "pass"
+ if err := c.HashAndSetPassword(testPass); err != nil {
+ t.Fatal(err)
+ }
+ if c.Password == testPass {
+ t.Error("Password hashing resulted in plaintext")
+ }
+
+ if err := c.CompareHashedPassword(testPass); err != nil {
+ t.Errorf("No match on same password: %v", err)
+ }
+
+ failPass := "different"
+ if err := c.CompareHashedPassword(failPass); err == nil {
+ t.Errorf("Match on different password: %v", err)
+ }
+}
+
func TestDuplicateDevices(t *testing.T) {
// Duplicate devices should be removed
diff --git a/lib/config/guiconfiguration.go b/lib/config/guiconfiguration.go
index 7b9830b30a9..147ed9fdc36 100644
--- a/lib/config/guiconfiguration.go
+++ b/lib/config/guiconfiguration.go
@@ -12,6 +12,8 @@ import (
"strconv"
"strings"
+ "golang.org/x/crypto/bcrypt"
+
"github.com/syncthing/syncthing/lib/rand"
)
@@ -113,6 +115,23 @@ func (c GUIConfiguration) URL() string {
return u.String()
}
+// SetHashedPassword hashes the given plaintext password and stores the new hash.
+func (c *GUIConfiguration) HashAndSetPassword(password string) error {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), 0)
+ if err != nil {
+ return err
+ }
+ c.Password = string(hash)
+ return nil
+}
+
+// CompareHashedPassword returns nil when the given plaintext password matches the stored hash.
+func (c GUIConfiguration) CompareHashedPassword(password string) error {
+ configPasswordBytes := []byte(c.Password)
+ passwordBytes := []byte(password)
+ return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes)
+}
+
// IsValidAPIKey returns true when the given API key is valid, including both
// the value in config and any overrides
func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
From 591e4d8af1a60768a573dbb5409016d30d6b18b1 Mon Sep 17 00:00:00 2001
From: Simon Frei
Date: Wed, 10 Nov 2021 09:46:21 +0100
Subject: [PATCH 009/220] gui, lib: Fix tracking deleted locally-changed on
encrypted (fixes #7715) (#7726)
---
gui/default/index.html | 6 -
.../syncthing/core/syncthingController.js | 23 +---
lib/db/lowlevel.go | 117 ++++++++++++++----
lib/db/set.go | 16 +++
lib/model/fakeconns_test.go | 1 +
lib/model/folder.go | 84 ++++++++-----
lib/model/folder_recvonly_test.go | 37 ++----
lib/model/folder_sendrecv_test.go | 16 ++-
lib/model/model.go | 4 +-
lib/model/model_test.go | 74 ++++++++---
lib/model/requests_test.go | 20 +--
lib/model/testutils_test.go | 15 +++
12 files changed, 271 insertions(+), 142 deletions(-)
diff --git a/gui/default/index.html b/gui/default/index.html
index 07cd90d6946..43ae81434db 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -455,12 +455,6 @@
{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} items, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B
-
- Locally Changed Items |
-
- {{receiveEncryptedItemsCount(folder) | alwaysNumber | localeNumber}} items, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B
- |
-
Folder Type |
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index 8e91fda4190..fa565b8b8d3 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -931,9 +931,9 @@ angular.module('syncthing.core')
return 'faileditems';
}
if ($scope.hasReceiveOnlyChanged(folderCfg)) {
- return 'localadditions';
- }
- if ($scope.hasReceiveEncryptedItems(folderCfg)) {
+ if (folderCfg.type === "receiveonly") {
+ return 'localadditions';
+ }
return 'localunencrypted';
}
if (folderCfg.devices.length <= 1) {
@@ -2609,28 +2609,13 @@ angular.module('syncthing.core')
};
$scope.hasReceiveOnlyChanged = function (folderCfg) {
- if (!folderCfg || folderCfg.type !== "receiveonly") {
+ if (!folderCfg || folderCfg.type !== ["receiveonly", "receiveencrypted"].indexOf(folderCfg.type) === -1) {
return false;
}
var counts = $scope.model[folderCfg.id];
return counts && counts.receiveOnlyTotalItems > 0;
};
- $scope.hasReceiveEncryptedItems = function (folderCfg) {
- if (!folderCfg || folderCfg.type !== "receiveencrypted") {
- return false;
- }
- return $scope.receiveEncryptedItemsCount(folderCfg) > 0;
- };
-
- $scope.receiveEncryptedItemsCount = function (folderCfg) {
- var counts = $scope.model[folderCfg.id];
- if (!counts) {
- return 0;
- }
- return counts.receiveOnlyTotalItems - counts.receiveOnlyChangedDeletes;
- }
-
$scope.revertOverride = function () {
$http.post(
urlbase + "/db/" + $scope.revertOverrideParams.operation +"?folder="
diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go
index 7f4916da488..88792a36d56 100644
--- a/lib/db/lowlevel.go
+++ b/lib/db/lowlevel.go
@@ -223,35 +223,10 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
blocksHashSame := ok && bytes.Equal(ef.BlocksHash, f.BlocksHash)
if ok {
- if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 {
- for _, block := range ef.Blocks {
- keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
- if err != nil {
- return err
- }
- if err := t.Delete(keyBuf); err != nil {
- return err
- }
- }
- if !blocksHashSame {
- keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name)
- if err != nil {
- return err
- }
- if err = t.Delete(keyBuf); err != nil {
- return err
- }
- }
- }
-
- keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo())
+ keyBuf, err = db.removeLocalBlockAndSequenceInfo(keyBuf, folder, name, ef, !blocksHashSame, &t)
if err != nil {
return err
}
- if err := t.Delete(keyBuf); err != nil {
- return err
- }
- l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName())
}
f.Sequence = meta.nextLocalSeq()
@@ -314,6 +289,96 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
return t.Commit()
}
+func (db *Lowlevel) removeLocalFiles(folder []byte, nameStrs []string, meta *metadataTracker) error {
+ db.gcMut.RLock()
+ defer db.gcMut.RUnlock()
+
+ t, err := db.newReadWriteTransaction(meta.CommitHook(folder))
+ if err != nil {
+ return err
+ }
+ defer t.close()
+
+ var dk, gk, buf []byte
+ for _, nameStr := range nameStrs {
+ name := []byte(nameStr)
+ dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name)
+ if err != nil {
+ return err
+ }
+
+ ef, ok, err := t.getFileByKey(dk)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ l.Debugf("remove (local); folder=%q %v: file doesn't exist", folder, nameStr)
+ continue
+ }
+
+ buf, err = db.removeLocalBlockAndSequenceInfo(buf, folder, name, ef, true, &t)
+ if err != nil {
+ return err
+ }
+
+ meta.removeFile(protocol.LocalDeviceID, ef)
+
+ gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
+ if err != nil {
+ return err
+ }
+ buf, err = t.removeFromGlobal(gk, buf, folder, protocol.LocalDeviceID[:], name, meta)
+ if err != nil {
+ return err
+ }
+
+ err = t.Delete(dk)
+ if err != nil {
+ return err
+ }
+
+ if err := t.Checkpoint(); err != nil {
+ return err
+ }
+ }
+
+ return t.Commit()
+}
+
+func (db *Lowlevel) removeLocalBlockAndSequenceInfo(keyBuf, folder, name []byte, ef protocol.FileInfo, removeFromBlockListMap bool, t *readWriteTransaction) ([]byte, error) {
+ var err error
+ if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 {
+ for _, block := range ef.Blocks {
+ keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
+ if err != nil {
+ return nil, err
+ }
+ if err := t.Delete(keyBuf); err != nil {
+ return nil, err
+ }
+ }
+ if removeFromBlockListMap {
+ keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name)
+ if err != nil {
+ return nil, err
+ }
+ if err = t.Delete(keyBuf); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo())
+ if err != nil {
+ return nil, err
+ }
+ if err := t.Delete(keyBuf); err != nil {
+ return nil, err
+ }
+ l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName())
+ return keyBuf, nil
+}
+
func (db *Lowlevel) dropFolder(folder []byte) error {
db.gcMut.RLock()
defer db.gcMut.RUnlock()
diff --git a/lib/db/set.go b/lib/db/set.go
index a1c0cb64962..12a7083cb8f 100644
--- a/lib/db/set.go
+++ b/lib/db/set.go
@@ -144,6 +144,22 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
}
}
+func (s *FileSet) RemoveLocalItems(items []string) {
+ opStr := fmt.Sprintf("%s RemoveLocalItems([%d])", s.folder, len(items))
+ l.Debugf(opStr)
+
+ s.updateMutex.Lock()
+ defer s.updateMutex.Unlock()
+
+ for i := range items {
+ items[i] = osutil.NormalizedFilename(items[i])
+ }
+
+ if err := s.db.removeLocalFiles([]byte(s.folder), items, s.meta); err != nil && !backend.IsClosed(err) {
+ fatalError(err, opStr, s.db)
+ }
+}
+
type Snapshot struct {
folder string
t readOnlyTransaction
diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go
index 79b4e85520e..9b5495b5f17 100644
--- a/lib/model/fakeconns_test.go
+++ b/lib/model/fakeconns_test.go
@@ -162,6 +162,7 @@ func (f *fakeConnection) sendIndexUpdate() {
func addFakeConn(m *testModel, dev protocol.DeviceID, folderID string) *fakeConnection {
fc := newFakeConnection(dev, m)
+ fc.folder = folderID
m.AddConnection(fc, protocol.Hello{})
m.ClusterConfig(dev, protocol.ClusterConfig{
diff --git a/lib/model/folder.go b/lib/model/folder.go
index 18ef41a0657..41b9fe98067 100644
--- a/lib/model/folder.go
+++ b/lib/model/folder.go
@@ -532,16 +532,20 @@ func (f *folder) scanSubdirs(subDirs []string) error {
return nil
}
+const maxToRemove = 1000
+
type scanBatch struct {
- *db.FileInfoBatch
- f *folder
+ f *folder
+ updateBatch *db.FileInfoBatch
+ toRemove []string
}
func (f *folder) newScanBatch() *scanBatch {
b := &scanBatch{
- f: f,
+ f: f,
+ toRemove: make([]string, 0, maxToRemove),
}
- b.FileInfoBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
+ b.updateBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
if err := b.f.getHealthErrorWithoutIgnores(); err != nil {
l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err)
return err
@@ -552,9 +556,32 @@ func (f *folder) newScanBatch() *scanBatch {
return b
}
-// Append adds the fileinfo to the batch for updating, and does a few checks.
+func (b *scanBatch) Remove(item string) {
+ b.toRemove = append(b.toRemove, item)
+}
+
+func (b *scanBatch) flushToRemove() {
+ if len(b.toRemove) > 0 {
+ b.f.fset.RemoveLocalItems(b.toRemove)
+ b.toRemove = b.toRemove[:0]
+ }
+}
+
+func (b *scanBatch) Flush() error {
+ b.flushToRemove()
+ return b.updateBatch.Flush()
+}
+
+func (b *scanBatch) FlushIfFull() error {
+ if len(b.toRemove) >= maxToRemove {
+ b.flushToRemove()
+ }
+ return b.updateBatch.FlushIfFull()
+}
+
+// Update adds the fileinfo to the batch for updating, and does a few checks.
// It returns false if the checks result in the file not going to be updated or removed.
-func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool {
+func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool {
// Check for a "virtual" parent directory of encrypted files. We don't track
// it, but check if anything still exists within and delete it otherwise.
if b.f.Type == config.FolderTypeReceiveEncrypted && fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) {
@@ -565,20 +592,21 @@ func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool {
}
// Resolve receive-only items which are identical with the global state or
// the global item is our own receive-only item.
- // Except if they are in a receive-encrypted folder and are locally added.
- // Those must never be sent in index updates and thus must retain the flag.
switch gf, ok := snap.GetGlobal(fi.Name); {
case !ok:
case gf.IsReceiveOnlyChanged():
- if b.f.Type == config.FolderTypeReceiveOnly && fi.Deleted {
- l.Debugf("%v scanning: Marking deleted item as not locally changed", b.f, fi)
- fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
+ if fi.IsDeleted() {
+ // Our item is deleted and the global item is our own receive only
+ // file. No point in keeping track of that.
+ b.Remove(fi.Name)
+ return true
}
case gf.IsEquivalentOptional(fi, b.f.modTimeWindow, false, false, protocol.FlagLocalReceiveOnly):
- l.Debugf("%v scanning: Replacing scanned file info with global as it's equivalent", b.f, fi)
+ // What we have locally is equivalent to the global file.
+ l.Debugf("%v scanning: Merging identical locally changed item with global", b.f, fi)
fi = gf
}
- b.FileInfoBatch.Append(fi)
+ b.updateBatch.Append(fi)
return true
}
@@ -634,7 +662,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
return changes, err
}
- if batch.Append(res.File, snap) {
+ if batch.Update(res.File, snap) {
changes++
}
@@ -642,7 +670,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
default:
if nf, ok := f.findRename(snap, res.File, alreadyUsedOrExisting); ok {
- if batch.Append(nf, snap) {
+ if batch.Update(nf, snap) {
changes++
}
}
@@ -683,7 +711,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
for _, file := range toIgnore {
l.Debugln("marking file as ignored", file)
nf := file.ConvertToIgnoredFileInfo()
- if batch.Append(nf, snap) {
+ if batch.Update(nf, snap) {
changes++
}
if err := batch.FlushIfFull(); err != nil {
@@ -713,7 +741,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
l.Debugln("marking file as ignored", file)
nf := file.ConvertToIgnoredFileInfo()
- if batch.Append(nf, snap) {
+ if batch.Update(nf, snap) {
changes++
}
@@ -743,24 +771,24 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
nf.Version = protocol.Vector{}
}
l.Debugln("marking file as deleted", nf)
- if batch.Append(nf, snap) {
+ if batch.Update(nf, snap) {
changes++
}
case file.IsDeleted() && file.IsReceiveOnlyChanged():
switch f.Type {
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
- if gf, _ := snap.GetGlobal(file.Name); gf.IsDeleted() {
+ switch gf, ok := snap.GetGlobal(file.Name); {
+ case !ok:
+ case gf.IsReceiveOnlyChanged():
+ l.Debugln("removing deleted, receive-only item that is globally receive-only from db", file)
+ batch.Remove(file.Name)
+ changes++
+ case gf.IsDeleted():
// Our item is deleted and the global item is deleted too. We just
// pretend it is a normal deleted file (nobody cares about that).
- // Except if this is a receive-encrypted folder and it
- // is a locally added file. Those must never be sent
- // in index updates and thus must retain the flag.
- if f.Type == config.FolderTypeReceiveEncrypted && gf.IsReceiveOnlyChanged() {
- return true
- }
l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, file.Name)
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
- if batch.Append(file.ConvertDeletedToFileInfo(), snap) {
+ if batch.Update(file.ConvertDeletedToFileInfo(), snap) {
changes++
}
}
@@ -769,7 +797,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
// deleted and just the folder type/local flags changed.
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
l.Debugln("removing receive-only flag on deleted item", file)
- if batch.Append(file.ConvertDeletedToFileInfo(), snap) {
+ if batch.Update(file.ConvertDeletedToFileInfo(), snap) {
changes++
}
}
@@ -788,7 +816,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
for _, file := range toIgnore {
l.Debugln("marking file as ignored", f)
nf := file.ConvertToIgnoredFileInfo()
- if batch.Append(nf, snap) {
+ if batch.Update(nf, snap) {
changes++
}
if iterError = batch.FlushIfFull(); iterError != nil {
diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go
index 9a48c78f23c..740a31202c1 100644
--- a/lib/model/folder_recvonly_test.go
+++ b/lib/model/folder_recvonly_test.go
@@ -37,9 +37,9 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
must(t, ffs.MkdirAll(dir, 0755))
}
- must(t, writeFile(ffs, "ignDir/ignFile", []byte("hello\n"), 0644))
- must(t, writeFile(ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644))
- must(t, writeFile(ffs, ".stignore", []byte("ignDir\n"), 0644))
+ writeFilePerm(t, ffs, "ignDir/ignFile", []byte("hello\n"), 0644)
+ writeFilePerm(t, ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644)
+ writeFilePerm(t, ffs, ".stignore", []byte("ignDir\n"), 0644)
knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
@@ -151,7 +151,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Update the file.
newData := []byte("totally different data\n")
- must(t, writeFile(ffs, "knownDir/knownFile", newData, 0644))
+ writeFilePerm(t, ffs, "knownDir/knownFile", newData, 0644)
// Rescan.
@@ -241,8 +241,8 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Create a file and modify another
const file = "foo"
- must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
- must(t, writeFile(ffs, "knownDir/knownFile", []byte("bye\n"), 0644))
+ writeFilePerm(t, ffs, file, []byte("hello\n"), 0644)
+ writeFilePerm(t, ffs, "knownDir/knownFile", []byte("bye\n"), 0644)
must(t, m.ScanFolder("ro"))
@@ -254,7 +254,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Remove the file again and undo the modification
must(t, ffs.Remove(file))
- must(t, writeFile(ffs, "knownDir/knownFile", oldData, 0644))
+ writeFilePerm(t, ffs, "knownDir/knownFile", oldData, 0644)
must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
must(t, m.ScanFolder("ro"))
@@ -377,8 +377,8 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
const file = "foo"
knownFile := filepath.Join("knownDir", "knownFile")
- must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
- must(t, writeFile(ffs, knownFile, []byte("bye\n"), 0644))
+ writeFilePerm(t, ffs, file, []byte("hello\n"), 0644)
+ writeFilePerm(t, ffs, knownFile, []byte("bye\n"), 0644)
must(t, m.ScanFolder("ro"))
@@ -434,7 +434,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) {
must(t, ffs.MkdirAll(".stfolder", 0755))
data := []byte("hello\n")
name := "foo"
- must(t, writeFile(ffs, name, data, 0644))
+ writeFilePerm(t, ffs, name, data, 0644)
// Make sure the file is scanned and locally changed
must(t, m.ScanFolder("ro"))
@@ -484,7 +484,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi
t.Helper()
must(t, ffs.MkdirAll("knownDir", 0755))
- must(t, writeFile(ffs, "knownDir/knownFile", data, 0644))
+ writeFilePerm(t, ffs, "knownDir/knownFile", data, 0644)
t0 := time.Now().Add(-1 * time.Minute)
must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
@@ -541,18 +541,3 @@ func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.Cancel
return m, f, cancel
}
-
-func writeFile(fs fs.Filesystem, filename string, data []byte, perm fs.FileMode) error {
- fd, err := fs.Create(filename)
- if err != nil {
- return err
- }
- _, err = fd.Write(data)
- if err != nil {
- return err
- }
- if err := fd.Close(); err != nil {
- return err
- }
- return fs.Chmod(filename, perm)
-}
diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go
index 2894fe14f59..06bf7d6d79d 100644
--- a/lib/model/folder_sendrecv_test.go
+++ b/lib/model/folder_sendrecv_test.go
@@ -77,12 +77,10 @@ func setupFile(filename string, blockNumbers []int) protocol.FileInfo {
}
}
-func createFile(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo {
+func createEmptyFileInfo(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo {
t.Helper()
- f, err := fs.Create(name)
- must(t, err)
- f.Close()
+ writeFile(t, fs, name, nil)
fi, err := fs.Stat(name)
must(t, err)
file, err := scanner.CreateFileInfo(fi, name, fs)
@@ -913,7 +911,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
name := "foo"
// create local file
- file := createFile(t, name, ffs)
+ file := createEmptyFileInfo(t, name, ffs)
file.Version = protocol.Vector{}.Update(myID.Short())
f.updateLocalsFromScanning([]protocol.FileInfo{file})
@@ -945,7 +943,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
name := "foo"
// create local file
- file := createFile(t, name, ffs)
+ file := createEmptyFileInfo(t, name, ffs)
file.Version = protocol.Vector{}.Update(myID.Short())
f.updateLocalsFromScanning([]protocol.FileInfo{file})
@@ -983,7 +981,7 @@ func TestDeleteBehindSymlink(t *testing.T) {
file := filepath.Join(link, "file")
must(t, ffs.MkdirAll(link, 0755))
- fi := createFile(t, file, ffs)
+ fi := createEmptyFileInfo(t, file, ffs)
f.updateLocalsFromScanning([]protocol.FileInfo{fi})
must(t, osutil.RenameOrCopy(fs.CopyRangeMethodStandard, ffs, destFs, file, "file"))
must(t, ffs.RemoveAll(link))
@@ -1099,7 +1097,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) {
name := "foo"
contents := []byte("contents")
- must(t, writeFile(ffs, name, contents, 0644))
+ writeFile(t, ffs, name, contents)
must(t, f.scanSubdirs(nil))
var cur protocol.FileInfo
@@ -1122,7 +1120,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) {
remote.Version = protocol.Vector{}.Update(device1.Short())
remote.Name = strings.ToUpper(cur.Name)
temp := fs.TempName(remote.Name)
- must(t, writeFile(ffs, temp, contents, 0644))
+ writeFile(t, ffs, temp, contents)
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
diff --git a/lib/model/model.go b/lib/model/model.go
index c1b3ddaa810..28a3a5469e8 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -1053,7 +1053,6 @@ func (m *model) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, p
func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
m.fmut.RLock()
rf, ok := m.folderFiles[folder]
- cfg := m.folderCfgs[folder]
m.fmut.RUnlock()
if !ok {
@@ -1071,11 +1070,10 @@ func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.
}
p := newPager(page, perpage)
- recvEnc := cfg.Type == config.FolderTypeReceiveEncrypted
files := make([]db.FileInfoTruncated, 0, perpage)
snap.WithHaveTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool {
- if !f.IsReceiveOnlyChanged() || (recvEnc && f.IsDeleted()) {
+ if !f.IsReceiveOnlyChanged() {
return true
}
if p.skip() {
diff --git a/lib/model/model_test.go b/lib/model/model_test.go
index b665abc01d7..121bcdffeec 100644
--- a/lib/model/model_test.go
+++ b/lib/model/model_test.go
@@ -299,7 +299,7 @@ func BenchmarkRequestInSingleFile(b *testing.B) {
mustRemove(b, defaultFs.RemoveAll("request"))
defer func() { mustRemove(b, defaultFs.RemoveAll("request")) }()
must(b, defaultFs.MkdirAll("request/for/a/file/in/a/couple/of/dirs", 0755))
- writeFile(defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf, 0644)
+ writeFile(b, defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf)
b.ResetTimer()
@@ -1489,7 +1489,7 @@ func TestIgnores(t *testing.T) {
// Assure a clean start state
mustRemove(t, defaultFs.RemoveAll(config.DefaultMarkerName))
mustRemove(t, defaultFs.MkdirAll(config.DefaultMarkerName, 0644))
- writeFile(defaultFs, ".stignore", []byte(".*\nquux\n"), 0644)
+ writeFile(t, defaultFs, ".stignore", []byte(".*\nquux\n"))
m := setupModel(t, defaultCfgWrapper)
defer cleanupModel(m)
@@ -2030,8 +2030,8 @@ func benchmarkTree(b *testing.B, n1, n2 int) {
func TestIssue3028(t *testing.T) {
// Create two files that we'll delete, one with a name that is a prefix of the other.
- must(t, writeFile(defaultFs, "testrm", []byte("Hello"), 0644))
- must(t, writeFile(defaultFs, "testrm2", []byte("Hello"), 0644))
+ writeFile(t, defaultFs, "testrm", []byte("Hello"))
+ writeFile(t, defaultFs, "testrm2", []byte("Hello"))
defer func() {
mustRemove(t, defaultFs.Remove("testrm"))
mustRemove(t, defaultFs.Remove("testrm2"))
@@ -3403,7 +3403,7 @@ func TestRenameSequenceOrder(t *testing.T) {
ffs := fcfg.Filesystem()
for i := 0; i < numFiles; i++ {
v := fmt.Sprintf("%d", i)
- must(t, writeFile(ffs, v, []byte(v), 0644))
+ writeFile(t, ffs, v, []byte(v))
}
m.ScanFolders()
@@ -3426,7 +3426,7 @@ func TestRenameSequenceOrder(t *testing.T) {
continue
}
v := fmt.Sprintf("%d", i)
- must(t, writeFile(ffs, v, []byte(v+"-new"), 0644))
+ writeFile(t, ffs, v, []byte(v+"-new"))
}
// Rename
must(t, ffs.Rename("3", "17"))
@@ -3470,7 +3470,7 @@ func TestRenameSameFile(t *testing.T) {
defer cleanupModel(m)
ffs := fcfg.Filesystem()
- must(t, writeFile(ffs, "file", []byte("file"), 0644))
+ writeFile(t, ffs, "file", []byte("file"))
m.ScanFolders()
@@ -3522,8 +3522,8 @@ func TestRenameEmptyFile(t *testing.T) {
ffs := fcfg.Filesystem()
- must(t, writeFile(ffs, "file", []byte("data"), 0644))
- must(t, writeFile(ffs, "empty", nil, 0644))
+ writeFile(t, ffs, "file", []byte("data"))
+ writeFile(t, ffs, "empty", nil)
m.ScanFolders()
@@ -3598,11 +3598,11 @@ func TestBlockListMap(t *testing.T) {
defer cleanupModel(m)
ffs := fcfg.Filesystem()
- must(t, writeFile(ffs, "one", []byte("content"), 0644))
- must(t, writeFile(ffs, "two", []byte("content"), 0644))
- must(t, writeFile(ffs, "three", []byte("content"), 0644))
- must(t, writeFile(ffs, "four", []byte("content"), 0644))
- must(t, writeFile(ffs, "five", []byte("content"), 0644))
+ writeFile(t, ffs, "one", []byte("content"))
+ writeFile(t, ffs, "two", []byte("content"))
+ writeFile(t, ffs, "three", []byte("content"))
+ writeFile(t, ffs, "four", []byte("content"))
+ writeFile(t, ffs, "five", []byte("content"))
m.ScanFolders()
@@ -3631,7 +3631,7 @@ func TestBlockListMap(t *testing.T) {
// Modify
must(t, ffs.Remove("two"))
- must(t, writeFile(ffs, "two", []byte("mew-content"), 0644))
+ writeFile(t, ffs, "two", []byte("mew-content"))
// Rename
must(t, ffs.Rename("three", "new-three"))
@@ -3667,7 +3667,7 @@ func TestScanRenameCaseOnly(t *testing.T) {
ffs := fcfg.Filesystem()
name := "foo"
- must(t, writeFile(ffs, name, []byte("contents"), 0644))
+ writeFile(t, ffs, name, []byte("contents"))
m.ScanFolders()
@@ -3791,7 +3791,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
name := "foo"
- must(t, writeFile(ffs, name, []byte(name), 0644))
+ writeFile(t, ffs, name, []byte(name))
m.ScanFolders()
file, ok := m.testCurrentFolderFile(fcfg.ID, name)
@@ -4255,6 +4255,46 @@ func TestPendingFolder(t *testing.T) {
}
}
+func TestDeletedNotLocallyChangedReceiveOnly(t *testing.T) {
+ deletedNotLocallyChanged(t, config.FolderTypeReceiveOnly)
+}
+
+func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) {
+ deletedNotLocallyChanged(t, config.FolderTypeReceiveEncrypted)
+}
+
+func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) {
+ w, fcfg, wCancel := tmpDefaultWrapper()
+ tfs := fcfg.Filesystem()
+ fcfg.Type = ft
+ setFolder(t, w, fcfg)
+ defer wCancel()
+ m := setupModel(t, w)
+ defer cleanupModelAndRemoveDir(m, tfs.URI())
+
+ name := "foo"
+ writeFile(t, tfs, name, nil)
+ must(t, m.ScanFolder(fcfg.ID))
+
+ fi, ok, err := m.CurrentFolderFile(fcfg.ID, name)
+ must(t, err)
+ if !ok {
+ t.Fatal("File hasn't been added")
+ }
+ if !fi.IsReceiveOnlyChanged() {
+ t.Fatal("File isn't receive-only-changed")
+ }
+
+ must(t, tfs.Remove(name))
+ must(t, m.ScanFolder(fcfg.ID))
+
+ _, ok, err = m.CurrentFolderFile(fcfg.ID, name)
+ must(t, err)
+ if ok {
+ t.Error("Expected file to be removed from db")
+ }
+}
+
func equalStringsInAnyOrder(a, b []string) bool {
if len(a) != len(b) {
return false
diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go
index a0c491d71b6..35df4fdd9e9 100644
--- a/lib/model/requests_test.go
+++ b/lib/model/requests_test.go
@@ -330,9 +330,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
fc.deleteFile(invDel)
fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
- if err := writeFile(fss, ignExisting, otherContents, 0644); err != nil {
- panic(err)
- }
+ writeFile(t, fss, ignExisting, otherContents)
done := make(chan struct{})
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
@@ -486,7 +484,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
payload := []byte("hello")
- must(t, writeFile(tfs, "foo", payload, 0777))
+ writeFile(t, tfs, "foo", payload)
received := make(chan []protocol.FileInfo)
fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error {
@@ -526,7 +524,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
payload = []byte("bye")
buf = make([]byte, len(payload))
- must(t, writeFile(tfs, "foo", payload, 0777))
+ writeFile(t, tfs, "foo", payload)
_, err = m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
if err == nil {
@@ -1066,9 +1064,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
return nil
})
- if err := writeFile(fss, file, contents, 0644); err != nil {
- panic(err)
- }
+ writeFile(t, fss, file, contents)
m.ScanFolders()
select {
@@ -1430,6 +1426,14 @@ func TestRequestReceiveEncrypted(t *testing.T) {
// Simulate request from device that is untrusted too, i.e. with non-empty, but garbage hash
_, err := m.Request(device1, fcfg.ID, name, 0, 1064, 0, []byte("garbage"), 0, false)
must(t, err)
+
+ changed, err := m.LocalChangedFolderFiles(fcfg.ID, 1, 10)
+ must(t, err)
+ if l := len(changed); l != 1 {
+ t.Errorf("Expected one locally changed file, got %v", l)
+ } else if changed[0].Name != files[0].Name {
+ t.Errorf("Expected %v, got %v", files[0].Name, changed[0].Name)
+ }
}
func TestRequestGlobalInvalidToValid(t *testing.T) {
diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go
index 6c2a29071e6..4ee390f5d8e 100644
--- a/lib/model/testutils_test.go
+++ b/lib/model/testutils_test.go
@@ -435,3 +435,18 @@ func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration)
must(t, err)
waiter.Wait()
}
+
+func writeFile(t testing.TB, filesystem fs.Filesystem, name string, data []byte) {
+ t.Helper()
+ fd, err := filesystem.Create(name)
+ must(t, err)
+ defer fd.Close()
+ _, err = fd.Write(data)
+ must(t, err)
+}
+
+func writeFilePerm(t testing.TB, filesystem fs.Filesystem, name string, data []byte, perm fs.FileMode) {
+ t.Helper()
+ writeFile(t, filesystem, name, data)
+ must(t, filesystem.Chmod(name, perm))
+}
From f1bf4d899a0853aefc1ec869df07ecbc54b49f01 Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Wed, 17 Nov 2021 11:42:20 +0100
Subject: [PATCH 010/220] lib/model: Correct handling of fakefs cache
We looked under one cache key, then stored under another...
---
lib/fs/fakefs.go | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lib/fs/fakefs.go b/lib/fs/fakefs.go
index 4d75ab0f100..7bc356d98da 100644
--- a/lib/fs/fakefs.go
+++ b/lib/fs/fakefs.go
@@ -89,11 +89,9 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
fakeFSMut.Lock()
defer fakeFSMut.Unlock()
- root := rootURI
var params url.Values
uri, err := url.Parse(rootURI)
if err == nil {
- root = uri.Path
params = uri.Query()
}
@@ -157,7 +155,7 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
// the filesystem initially.
fs.latency, _ = time.ParseDuration(params.Get("latency"))
- fakeFSCache[root] = fs
+ fakeFSCache[rootURI] = fs
return fs
}
From 12fb7f2a0a58d720718df8d67bb959090d1e9ec2 Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Wed, 17 Nov 2021 12:10:06 +0100
Subject: [PATCH 011/220] lib/model: Correct "reverting folder" log entry
---
lib/model/folder_recvonly.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/model/folder_recvonly.go b/lib/model/folder_recvonly.go
index ee6f0c99a1f..827fbe5d863 100644
--- a/lib/model/folder_recvonly.go
+++ b/lib/model/folder_recvonly.go
@@ -68,7 +68,7 @@ func (f *receiveOnlyFolder) Revert() {
}
func (f *receiveOnlyFolder) revert() error {
- l.Infof("Reverting folder %v", f.Description)
+ l.Infof("Reverting folder %v", f.Description())
f.setState(FolderScanning)
defer f.setState(FolderIdle)
From 100870e14232985962f2fcb73ad531f0e6eb9512 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Thu, 18 Nov 2021 22:57:59 +0100
Subject: [PATCH 012/220] cmd/syncthing: Implement generate as a subcommand
with optional API credential setting (fixes #8021) (#8043)
Accept a subcommand as an alternative to the --generate option. It
accepts a custom config directory through either the --home or
--config options, using the default location if neither is given.
Add the options --gui-user and --gui-password to "generate", but not
the "serve --generate" option form. If either is given, an existing
config will not abort the command, but rather load, modify and save it
with the new credentials. The password can be read from standard
input by passing only a single dash as argument.
Config modification is skipped if the value matches what's already in
the config.
* cmd/syncthing: Utilize lib/locations package in generate().
Instead of manually joining paths with "magic" file names, get them
from the centralized locations helper lib.
* cmd/syncthing: Simplify logging for --generate option.
Visible change: No more timestamp prefixes.
---
cmd/syncthing/cmdutil/options_common.go | 15 ++
cmd/syncthing/{ => cmdutil}/options_others.go | 4 +-
.../{ => cmdutil}/options_windows.go | 4 +-
cmd/syncthing/generate/generate.go | 144 ++++++++++++++++++
cmd/syncthing/main.go | 86 ++---------
lib/syncthing/utils.go | 23 +++
6 files changed, 197 insertions(+), 79 deletions(-)
create mode 100644 cmd/syncthing/cmdutil/options_common.go
rename cmd/syncthing/{ => cmdutil}/options_others.go (86%)
rename cmd/syncthing/{ => cmdutil}/options_windows.go (86%)
create mode 100644 cmd/syncthing/generate/generate.go
diff --git a/cmd/syncthing/cmdutil/options_common.go b/cmd/syncthing/cmdutil/options_common.go
new file mode 100644
index 00000000000..34485e6a563
--- /dev/null
+++ b/cmd/syncthing/cmdutil/options_common.go
@@ -0,0 +1,15 @@
+// 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 cmdutil
+
+// CommonOptions are reused among several subcommands
+type CommonOptions struct {
+ buildCommonOptions
+ ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
+ HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
+ NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
+}
diff --git a/cmd/syncthing/options_others.go b/cmd/syncthing/cmdutil/options_others.go
similarity index 86%
rename from cmd/syncthing/options_others.go
rename to cmd/syncthing/cmdutil/options_others.go
index b1f4764cadf..e299ba48cf9 100644
--- a/cmd/syncthing/options_others.go
+++ b/cmd/syncthing/cmdutil/options_others.go
@@ -7,8 +7,8 @@
//go:build !windows
// +build !windows
-package main
+package cmdutil
-type buildServeOptions struct {
+type buildCommonOptions struct {
HideConsole bool `hidden:""`
}
diff --git a/cmd/syncthing/options_windows.go b/cmd/syncthing/cmdutil/options_windows.go
similarity index 86%
rename from cmd/syncthing/options_windows.go
rename to cmd/syncthing/cmdutil/options_windows.go
index 38d26a22a1e..99fb50d5b96 100644
--- a/cmd/syncthing/options_windows.go
+++ b/cmd/syncthing/cmdutil/options_windows.go
@@ -4,8 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
-package main
+package cmdutil
-type buildServeOptions struct {
+type buildCommonOptions struct {
HideConsole bool `name:"no-console" help:"Hide console window"`
}
diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go
new file mode 100644
index 00000000000..8c5bd1323fe
--- /dev/null
+++ b/cmd/syncthing/generate/generate.go
@@ -0,0 +1,144 @@
+// 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 generate implements the `syncthing generate` subcommand.
+package generate
+
+import (
+ "bufio"
+ "context"
+ "crypto/tls"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
+ "github.com/syncthing/syncthing/lib/config"
+ "github.com/syncthing/syncthing/lib/events"
+ "github.com/syncthing/syncthing/lib/fs"
+ "github.com/syncthing/syncthing/lib/locations"
+ "github.com/syncthing/syncthing/lib/osutil"
+ "github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/syncthing"
+)
+
+type CLI struct {
+ cmdutil.CommonOptions
+ GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"`
+ GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"`
+}
+
+func (c *CLI) Run() error {
+ log.SetFlags(0)
+
+ if c.HideConsole {
+ osutil.HideConsole()
+ }
+
+ if c.HomeDir != "" {
+ if c.ConfDir != "" {
+ return fmt.Errorf("--home must not be used together with --config")
+ }
+ c.ConfDir = c.HomeDir
+ }
+ if c.ConfDir == "" {
+ c.ConfDir = locations.GetBaseDir(locations.ConfigBaseDir)
+ }
+
+ // Support reading the password from a pipe or similar
+ if c.GUIPassword == "-" {
+ reader := bufio.NewReader(os.Stdin)
+ password, _, err := reader.ReadLine()
+ if err != nil {
+ return fmt.Errorf("Failed reading GUI password: %w", err)
+ }
+ c.GUIPassword = string(password)
+ }
+
+ if err := Generate(c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder); err != nil {
+ return fmt.Errorf("Failed to generate config and keys: %w", err)
+ }
+ return nil
+}
+
+func Generate(confDir, guiUser, guiPassword string, noDefaultFolder bool) error {
+ dir, err := fs.ExpandTilde(confDir)
+ if err != nil {
+ return err
+ }
+
+ if err := syncthing.EnsureDir(dir, 0700); err != nil {
+ return err
+ }
+ locations.SetBaseDir(locations.ConfigBaseDir, dir)
+
+ var myID protocol.DeviceID
+ certFile, keyFile := locations.Get(locations.CertFile), locations.Get(locations.KeyFile)
+ cert, err := tls.LoadX509KeyPair(certFile, keyFile)
+ if err == nil {
+ log.Println("WARNING: Key exists; will not overwrite.")
+ } else {
+ cert, err = syncthing.GenerateCertificate(certFile, keyFile)
+ if err != nil {
+ return fmt.Errorf("create certificate: %w", err)
+ }
+ }
+ myID = protocol.NewDeviceID(cert.Certificate[0])
+ log.Println("Device ID:", myID)
+
+ cfgFile := locations.Get(locations.ConfigFile)
+ var cfg config.Wrapper
+ if _, err := os.Stat(cfgFile); err == nil {
+ if guiUser == "" && guiPassword == "" {
+ log.Println("WARNING: Config exists; will not overwrite.")
+ return nil
+ }
+
+ if cfg, _, err = config.Load(cfgFile, myID, events.NoopLogger); err != nil {
+ return fmt.Errorf("load config: %w", err)
+ }
+ } else {
+ if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder); err != nil {
+ return fmt.Errorf("create config: %w", err)
+ }
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ go cfg.Serve(ctx)
+ defer cancel()
+
+ var updateErr error
+ waiter, err := cfg.Modify(func(cfg *config.Configuration) {
+ updateErr = updateGUIAuthentication(&cfg.GUI, guiUser, guiPassword)
+ })
+ if err != nil {
+ return fmt.Errorf("modify config: %w", err)
+ }
+
+ waiter.Wait()
+ if updateErr != nil {
+ return updateErr
+ }
+ if err := cfg.Save(); err != nil {
+ return fmt.Errorf("save config: %w", err)
+ }
+ return nil
+}
+
+func updateGUIAuthentication(guiCfg *config.GUIConfiguration, guiUser, guiPassword string) error {
+ if guiUser != "" && guiCfg.User != guiUser {
+ guiCfg.User = guiUser
+ log.Println("Updated GUI authentication user name:", guiUser)
+ }
+
+ if guiPassword != "" && guiCfg.Password != guiPassword {
+ if err := guiCfg.HashAndSetPassword(guiPassword); err != nil {
+ return fmt.Errorf("Failed to set GUI authentication password: %w", err)
+ }
+ log.Println("Updated GUI authentication password.")
+ }
+ return nil
+}
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 21a8fbe6bc5..127fcc54ff5 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -36,6 +36,7 @@ import (
"github.com/syncthing/syncthing/cmd/syncthing/cli"
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
+ "github.com/syncthing/syncthing/cmd/syncthing/generate"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
@@ -132,32 +133,30 @@ var (
// commands and options here are top level commands to syncthing.
// Cli is just a placeholder for the help text (see main).
var entrypoint struct {
- Serve serveOptions `cmd:"" help:"Run Syncthing"`
- Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
- Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
+ Serve serveOptions `cmd:"" help:"Run Syncthing"`
+ Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
+ Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
+ Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
}
// serveOptions are the options for the `syncthing serve` command.
type serveOptions struct {
- buildServeOptions
+ cmdutil.CommonOptions
AllowNewerConfig bool `help:"Allow loading newer than current config version"`
Audit bool `help:"Write events to audit file"`
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
BrowserOnly bool `help:"Open GUI in browser"`
- ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
DataDir string `name:"data" placeholder:"PATH" help:"Set data directory (database and logs)"`
DeviceID bool `help:"Show the device ID"`
- GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"`
+ GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` //DEPRECATED: replaced by subcommand!
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
- HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"`
LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"`
LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"`
NoBrowser bool `help:"Do not start browser"`
NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"`
- NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"`
Paths bool `help:"Show configuration paths"`
Paused bool `help:"Start with all devices and folders paused"`
@@ -339,7 +338,7 @@ func (options serveOptions) Run() error {
}
if options.GenerateDir != "" {
- if err := generate(options.GenerateDir, options.NoDefaultFolder); err != nil {
+ if err := generate.Generate(options.GenerateDir, "", "", options.NoDefaultFolder); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(svcutil.ExitError.AsInt())
}
@@ -347,7 +346,7 @@ func (options serveOptions) Run() error {
}
// Ensure that our home directory exists.
- if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
+ if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failure on home directory:", err)
os.Exit(svcutil.ExitError.AsInt())
}
@@ -413,8 +412,8 @@ func openGUI(myID protocol.DeviceID) error {
if err != nil {
return err
}
- if cfg.GUI().Enabled {
- if err := openURL(cfg.GUI().URL()); err != nil {
+ if guiCfg := cfg.GUI(); guiCfg.Enabled {
+ if err := openURL(guiCfg.URL()); err != nil {
return err
}
} else {
@@ -423,46 +422,6 @@ func openGUI(myID protocol.DeviceID) error {
return nil
}
-func generate(generateDir string, noDefaultFolder bool) error {
- dir, err := fs.ExpandTilde(generateDir)
- if err != nil {
- return err
- }
-
- if err := ensureDir(dir, 0700); err != nil {
- return err
- }
-
- var myID protocol.DeviceID
- certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
- cert, err := tls.LoadX509KeyPair(certFile, keyFile)
- if err == nil {
- l.Warnln("Key exists; will not overwrite.")
- } else {
- cert, err = syncthing.GenerateCertificate(certFile, keyFile)
- if err != nil {
- return errors.Wrap(err, "create certificate")
- }
- }
- myID = protocol.NewDeviceID(cert.Certificate[0])
- l.Infoln("Device ID:", myID)
-
- cfgFile := filepath.Join(dir, "config.xml")
- if _, err := os.Stat(cfgFile); err == nil {
- l.Warnln("Config exists; will not overwrite.")
- return nil
- }
- cfg, err := syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder)
- if err != nil {
- return err
- }
- err = cfg.Save()
- if err != nil {
- return errors.Wrap(err, "save config")
- }
- return nil
-}
-
func debugFacilities() string {
facilities := l.Facilities()
@@ -800,29 +759,6 @@ func resetDB() error {
return os.RemoveAll(locations.Get(locations.Database))
}
-func ensureDir(dir string, mode fs.FileMode) error {
- fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
- err := fs.MkdirAll(".", mode)
- if err != nil {
- return err
- }
-
- if fi, err := fs.Stat("."); err == nil {
- // Apprently the stat may fail even though the mkdirall passed. If it
- // does, we'll just assume things are in order and let other things
- // fail (like loading or creating the config...).
- currentMode := fi.Mode() & 0777
- if currentMode != mode {
- err := fs.Chmod(".", mode)
- // This can fail on crappy filesystems, nothing we can do about it.
- if err != nil {
- l.Warnln(err)
- }
- }
- }
- return nil
-}
-
func standbyMonitor(app *syncthing.App, cfg config.Wrapper) {
restartDelay := 60 * time.Second
now := time.Now()
diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go
index 468e94a045a..eecb563942f 100644
--- a/lib/syncthing/utils.go
+++ b/lib/syncthing/utils.go
@@ -24,6 +24,29 @@ import (
"github.com/syncthing/syncthing/lib/tlsutil"
)
+func EnsureDir(dir string, mode fs.FileMode) error {
+ fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
+ err := fs.MkdirAll(".", mode)
+ if err != nil {
+ return err
+ }
+
+ if fi, err := fs.Stat("."); err == nil {
+ // Apprently the stat may fail even though the mkdirall passed. If it
+ // does, we'll just assume things are in order and let other things
+ // fail (like loading or creating the config...).
+ currentMode := fi.Mode() & 0777
+ if currentMode != mode {
+ err := fs.Chmod(".", mode)
+ // This can fail on crappy filesystems, nothing we can do about it.
+ if err != nil {
+ l.Warnln(err)
+ }
+ }
+ }
+ return nil
+}
+
func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
From 8265dac1272b3690529bb2b532f82cda4fc43cc8 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Mon, 22 Nov 2021 08:29:44 +0100
Subject: [PATCH 013/220] lib/nat: Fix race condition in Mapping (#8042)
The locking protocol in nat.Mapping was racy:
* Mapping.addressMap RLock'd, but then returned a map shared between
caller and Mapping, so the lock didn't do anything.
* Operations inside Service.{verifyExistingMappings,acquireNewMappings}
would lock the map for every update, but that means callers to
Mapping.ExternalAddresses can be looping over the map while the
Service methods are concurrently modifying it. When the Go runtime
detects that happening, it panics.
* Mapping.expires was read and updated without locking.
The Service methods now lock the map once and release the lock only when
done.
Also, subscribers no longer get the added and removed addresses, because
none of them were using the information. This was changed for a previous
attempt to retain the fine-grained locking and not reverted because it
simplifies the code.
---
cmd/strelaysrv/main.go | 2 +-
lib/connections/tcp_listen.go | 2 +-
lib/nat/service.go | 66 ++++++++++++++++-------------------
lib/nat/structs.go | 34 ++++++------------
4 files changed, 43 insertions(+), 61 deletions(-)
diff --git a/cmd/strelaysrv/main.go b/cmd/strelaysrv/main.go
index fea34ccd440..507b3e6c01b 100644
--- a/cmd/strelaysrv/main.go
+++ b/cmd/strelaysrv/main.go
@@ -200,7 +200,7 @@ func main() {
go natSvc.Serve(ctx)
defer cancel()
found := make(chan struct{})
- mapping.OnChanged(func(_ *nat.Mapping, _, _ []nat.Address) {
+ mapping.OnChanged(func() {
select {
case found <- struct{}{}:
default:
diff --git a/lib/connections/tcp_listen.go b/lib/connections/tcp_listen.go
index 54b833a7723..13a0713343b 100644
--- a/lib/connections/tcp_listen.go
+++ b/lib/connections/tcp_listen.go
@@ -76,7 +76,7 @@ func (t *tcpListener) serve(ctx context.Context) error {
defer l.Infof("TCP listener (%v) shutting down", tcaddr)
mapping := t.natService.NewMapping(nat.TCP, tcaddr.IP, tcaddr.Port)
- mapping.OnChanged(func(_ *nat.Mapping, _, _ []nat.Address) {
+ mapping.OnChanged(func() {
t.notifyAddressesChanged(t)
})
// Should be called after t.mapping is nil'ed out.
diff --git a/lib/nat/service.go b/lib/nat/service.go
index 4c0269c48ec..0785879616a 100644
--- a/lib/nat/service.go
+++ b/lib/nat/service.go
@@ -125,11 +125,15 @@ func (s *Service) process(ctx context.Context) (int, time.Duration) {
s.mut.RLock()
for _, mapping := range s.mappings {
- if mapping.expires.Before(time.Now()) {
+ mapping.mut.RLock()
+ expires := mapping.expires
+ mapping.mut.RUnlock()
+
+ if expires.Before(time.Now()) {
toRenew = append(toRenew, mapping)
} else {
toUpdate = append(toUpdate, mapping)
- mappingRenewIn := time.Until(mapping.expires)
+ mappingRenewIn := time.Until(expires)
if mappingRenewIn < renewIn {
renewIn = mappingRenewIn
}
@@ -206,41 +210,36 @@ func (s *Service) RemoveMapping(mapping *Mapping) {
// Optionally takes renew flag which indicates whether or not we should renew
// mappings with existing natds
func (s *Service) updateMapping(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) {
- var added, removed []Address
-
renewalTime := time.Duration(s.cfg.Options().NATRenewalM) * time.Minute
- mapping.expires = time.Now().Add(renewalTime)
- newAdded, newRemoved := s.verifyExistingMappings(ctx, mapping, nats, renew)
- added = append(added, newAdded...)
- removed = append(removed, newRemoved...)
+ mapping.mut.Lock()
- newAdded, newRemoved = s.acquireNewMappings(ctx, mapping, nats)
- added = append(added, newAdded...)
- removed = append(removed, newRemoved...)
+ mapping.expires = time.Now().Add(renewalTime)
+ change := s.verifyExistingLocked(ctx, mapping, nats, renew)
+ add := s.acquireNewLocked(ctx, mapping, nats)
- if len(added) > 0 || len(removed) > 0 {
- mapping.notify(added, removed)
+ mapping.mut.Unlock()
+
+ if change || add {
+ mapping.notify()
}
}
-func (s *Service) verifyExistingMappings(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) ([]Address, []Address) {
- var added, removed []Address
-
+func (s *Service) verifyExistingLocked(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) (change bool) {
leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute
- for id, address := range mapping.addressMap() {
+ for id, address := range mapping.extAddresses {
select {
case <-ctx.Done():
- return nil, nil
+ return false
default:
}
// Delete addresses for NATDevice's that do not exist anymore
nat, ok := nats[id]
if !ok {
- mapping.removeAddress(id)
- removed = append(removed, address)
+ mapping.removeAddressLocked(id)
+ change = true
continue
} else if renew {
// Only perform renewals on the nat's that have the right local IP
@@ -256,35 +255,32 @@ func (s *Service) verifyExistingMappings(ctx context.Context, mapping *Mapping,
addr, err := s.tryNATDevice(ctx, nat, mapping.address.Port, address.Port, leaseTime)
if err != nil {
l.Debugf("Failed to renew %s -> mapping on %s", mapping, address, id)
- mapping.removeAddress(id)
- removed = append(removed, address)
+ mapping.removeAddressLocked(id)
+ change = true
continue
}
l.Debugf("Renewed %s -> %s mapping on %s", mapping, address, id)
if !addr.Equal(address) {
- mapping.removeAddress(id)
- mapping.setAddress(id, addr)
- removed = append(removed, address)
- added = append(added, address)
+ mapping.removeAddressLocked(id)
+ mapping.setAddressLocked(id, addr)
+ change = true
}
}
}
- return added, removed
+ return change
}
-func (s *Service) acquireNewMappings(ctx context.Context, mapping *Mapping, nats map[string]Device) ([]Address, []Address) {
- var added, removed []Address
-
+func (s *Service) acquireNewLocked(ctx context.Context, mapping *Mapping, nats map[string]Device) (change bool) {
leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute
- addrMap := mapping.addressMap()
+ addrMap := mapping.extAddresses
for id, nat := range nats {
select {
case <-ctx.Done():
- return nil, nil
+ return false
default:
}
@@ -310,11 +306,11 @@ func (s *Service) acquireNewMappings(ctx context.Context, mapping *Mapping, nats
l.Debugf("Acquired %s -> %s mapping on %s", mapping, addr, id)
- mapping.setAddress(id, addr)
- added = append(added, addr)
+ mapping.setAddressLocked(id, addr)
+ change = true
}
- return added, removed
+ return change
}
// tryNATDevice tries to acquire a port mapping for the given internal address to
diff --git a/lib/nat/structs.go b/lib/nat/structs.go
index 32dae4a5f2c..29be5b8d154 100644
--- a/lib/nat/structs.go
+++ b/lib/nat/structs.go
@@ -14,7 +14,7 @@ import (
"github.com/syncthing/syncthing/lib/sync"
)
-type MappingChangeSubscriber func(*Mapping, []Address, []Address)
+type MappingChangeSubscriber func()
type Mapping struct {
protocol Protocol
@@ -26,55 +26,41 @@ type Mapping struct {
mut sync.RWMutex
}
-func (m *Mapping) setAddress(id string, address Address) {
- m.mut.Lock()
- if existing, ok := m.extAddresses[id]; !ok || !existing.Equal(address) {
- l.Infof("New NAT port mapping: external %s address %s to local address %s.", m.protocol, address, m.address)
- m.extAddresses[id] = address
- }
- m.mut.Unlock()
+func (m *Mapping) setAddressLocked(id string, address Address) {
+ l.Infof("New NAT port mapping: external %s address %s to local address %s.", m.protocol, address, m.address)
+ m.extAddresses[id] = address
}
-func (m *Mapping) removeAddress(id string) {
- m.mut.Lock()
+func (m *Mapping) removeAddressLocked(id string) {
addr, ok := m.extAddresses[id]
if ok {
l.Infof("Removing NAT port mapping: external %s address %s, NAT %s is no longer available.", m.protocol, addr, id)
delete(m.extAddresses, id)
}
- m.mut.Unlock()
}
func (m *Mapping) clearAddresses() {
m.mut.Lock()
- var removed []Address
+ change := len(m.extAddresses) > 0
for id, addr := range m.extAddresses {
l.Debugf("Clearing mapping %s: ID: %s Address: %s", m, id, addr)
- removed = append(removed, addr)
delete(m.extAddresses, id)
}
m.expires = time.Time{}
m.mut.Unlock()
- if len(removed) > 0 {
- m.notify(nil, removed)
+ if change {
+ m.notify()
}
}
-func (m *Mapping) notify(added, removed []Address) {
+func (m *Mapping) notify() {
m.mut.RLock()
for _, subscriber := range m.subscribers {
- subscriber(m, added, removed)
+ subscriber()
}
m.mut.RUnlock()
}
-func (m *Mapping) addressMap() map[string]Address {
- m.mut.RLock()
- addrMap := m.extAddresses
- m.mut.RUnlock()
- return addrMap
-}
-
func (m *Mapping) Protocol() Protocol {
return m.protocol
}
From e2288fe441820794c983c3036829fd52c37ae1ea Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Mon, 22 Nov 2021 08:31:03 +0100
Subject: [PATCH 014/220] lib/relay: Send SNI when the address is a host name
(fixes #8014) (#8015)
---
lib/relay/client/static.go | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/lib/relay/client/static.go b/lib/relay/client/static.go
index 1f5cdb18547..a49768b84e9 100644
--- a/lib/relay/client/static.go
+++ b/lib/relay/client/static.go
@@ -141,7 +141,17 @@ func (c *staticClient) connect(ctx context.Context) error {
return err
}
- conn := tls.Client(tcpConn, c.config)
+ // Copy the TLS config and set the server name we're connecting to. In
+ // many cases this will be an IP address, in which case it's a no-op. In
+ // other cases it will be a hostname, which will cause the TLS stack to
+ // send SNI.
+ cfg := c.config
+ if host, _, err := net.SplitHostPort(c.uri.Host); err == nil {
+ cfg = cfg.Clone()
+ cfg.ServerName = host
+ }
+
+ conn := tls.Client(tcpConn, cfg)
if err := conn.SetDeadline(time.Now().Add(c.connectTimeout)); err != nil {
conn.Close()
From bf89bffb0b844cfafc8a38467870157e52e6f198 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Mon, 22 Nov 2021 08:45:29 +0100
Subject: [PATCH 015/220] lib/config: Decouple VerifyConfiguration from
Committer (#7939)
... and remove 8/10 implementations, which were no-ops. This saves code
and time copying configurations.
---
lib/api/api.go | 2 ++
lib/config/wrapper.go | 23 ++++++++++++++++-------
lib/connections/limiter.go | 4 ----
lib/connections/service.go | 4 ----
lib/discover/manager.go | 4 ----
lib/model/model.go | 2 ++
lib/model/progressemitter.go | 5 -----
lib/nat/service.go | 4 ----
lib/ur/failurereporting.go | 4 ----
lib/ur/usage_report.go | 4 ----
lib/watchaggregator/aggregator.go | 4 ----
11 files changed, 20 insertions(+), 40 deletions(-)
diff --git a/lib/api/api.go b/lib/api/api.go
index 360e94212c6..6ba4235f347 100644
--- a/lib/api/api.go
+++ b/lib/api/api.go
@@ -94,6 +94,8 @@ type service struct {
systemLog logger.Recorder
}
+var _ config.Verifier = &service{}
+
type Service interface {
suture.Service
config.Committer
diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go
index 3482b135a81..4d5c8f93ccf 100644
--- a/lib/config/wrapper.go
+++ b/lib/config/wrapper.go
@@ -31,14 +31,14 @@ const (
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.
+// The Committer and Verifier interfaces are implemented by objects
+// that need to know about or have a say in configuration changes.
//
// When the configuration is about to be changed, VerifyConfiguration() is
-// called for each subscribing object, with the old and new configuration. A
-// nil error is returned if the new configuration is acceptable (i.e. does not
-// contain any errors that would prevent it from being a valid config).
-// Otherwise an error describing the problem is returned.
+// called for each subscribing object that implements it, with copies of the
+// old and new configuration. A nil error is returned if the new configuration
+// is acceptable (i.e., does not contain any errors that would prevent it from
+// being a valid config). Otherwise an error describing the problem is returned.
//
// If any subscriber returns an error from VerifyConfiguration(), the
// configuration change is not committed and an error is returned to whoever
@@ -55,11 +55,16 @@ var errTooManyModifications = errors.New("too many concurrent config modificatio
// 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)
String() string
}
+// A Verifier can determine if a new configuration is acceptable.
+// See the description for Committer, above.
+type Verifier interface {
+ VerifyConfiguration(from, to Configuration) error
+}
+
// Waiter allows to wait for the given config operation to complete.
type Waiter interface {
Wait()
@@ -300,6 +305,10 @@ func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
}
for _, sub := range w.subs {
+ sub, ok := sub.(Verifier)
+ if !ok {
+ continue
+ }
l.Debugln(sub, "verifying configuration")
if err := sub.VerifyConfiguration(from.Copy(), to.Copy()); err != nil {
l.Debugln(sub, "rejected config:", err)
diff --git a/lib/connections/limiter.go b/lib/connections/limiter.go
index eac63d70e1b..66331c221f7 100644
--- a/lib/connections/limiter.go
+++ b/lib/connections/limiter.go
@@ -122,10 +122,6 @@ func (lim *limiter) processDevicesConfigurationLocked(from, to config.Configurat
}
}
-func (lim *limiter) VerifyConfiguration(from, to config.Configuration) error {
- return nil
-}
-
func (lim *limiter) CommitConfiguration(from, to config.Configuration) bool {
// to ensure atomic update of configuration
lim.mu.Lock()
diff --git a/lib/connections/service.go b/lib/connections/service.go
index 3848b261685..24a6f142045 100644
--- a/lib/connections/service.go
+++ b/lib/connections/service.go
@@ -706,10 +706,6 @@ func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) {
})
}
-func (s *service) VerifyConfiguration(from, to config.Configuration) error {
- return nil
-}
-
func (s *service) CommitConfiguration(from, to config.Configuration) bool {
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
for _, dev := range to.Devices {
diff --git a/lib/discover/manager.go b/lib/discover/manager.go
index ec43e889cbd..686d673104c 100644
--- a/lib/discover/manager.go
+++ b/lib/discover/manager.go
@@ -227,10 +227,6 @@ func (m *manager) Cache() map[protocol.DeviceID]CacheEntry {
return res
}
-func (m *manager) VerifyConfiguration(_, _ config.Configuration) error {
- return nil
-}
-
func (m *manager) CommitConfiguration(_, to config.Configuration) (handled bool) {
m.mut.Lock()
defer m.mut.Unlock()
diff --git a/lib/model/model.go b/lib/model/model.go
index 28a3a5469e8..e722a90cc19 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -168,6 +168,8 @@ type model struct {
foldersRunning int32
}
+var _ config.Verifier = &model{}
+
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *util.Semaphore) service
var (
diff --git a/lib/model/progressemitter.go b/lib/model/progressemitter.go
index 56bf92e27ce..fab9e72c96a 100644
--- a/lib/model/progressemitter.go
+++ b/lib/model/progressemitter.go
@@ -212,11 +212,6 @@ func (t *ProgressEmitter) computeProgressUpdates() []progressUpdate {
return progressUpdates
}
-// VerifyConfiguration implements the config.Committer interface
-func (t *ProgressEmitter) VerifyConfiguration(from, to config.Configuration) error {
- return nil
-}
-
// CommitConfiguration implements the config.Committer interface
func (t *ProgressEmitter) CommitConfiguration(_, to config.Configuration) bool {
t.mut.Lock()
diff --git a/lib/nat/service.go b/lib/nat/service.go
index 0785879616a..5e2f1c704e1 100644
--- a/lib/nat/service.go
+++ b/lib/nat/service.go
@@ -45,10 +45,6 @@ func NewService(id protocol.DeviceID, cfg config.Wrapper) *Service {
return s
}
-func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
- return nil
-}
-
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
s.mut.Lock()
if !s.enabled && to.Options.NATEnabled {
diff --git a/lib/ur/failurereporting.go b/lib/ur/failurereporting.go
index 899bd0e303c..f8d4d88507b 100644
--- a/lib/ur/failurereporting.go
+++ b/lib/ur/failurereporting.go
@@ -188,10 +188,6 @@ func (h *failureHandler) addReport(data FailureData, evTime time.Time) {
}
}
-func (h *failureHandler) VerifyConfiguration(_, _ config.Configuration) error {
- return nil
-}
-
func (h *failureHandler) CommitConfiguration(from, to config.Configuration) bool {
if from.Options.CREnabled != to.Options.CREnabled || from.Options.CRURL != to.Options.CRURL {
h.optsChan <- to.Options
diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go
index a2d2b0923a5..f6537f20f8a 100644
--- a/lib/ur/usage_report.go
+++ b/lib/ur/usage_report.go
@@ -390,10 +390,6 @@ func (s *Service) Serve(ctx context.Context) error {
}
}
-func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
- return nil
-}
-
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
if from.Options.URAccepted != to.Options.URAccepted || from.Options.URUniqueID != to.Options.URUniqueID || from.Options.URURL != to.Options.URURL {
select {
diff --git a/lib/watchaggregator/aggregator.go b/lib/watchaggregator/aggregator.go
index 93a38c4bad4..5c176fde8c5 100644
--- a/lib/watchaggregator/aggregator.go
+++ b/lib/watchaggregator/aggregator.go
@@ -419,10 +419,6 @@ func (a *aggregator) String() string {
return fmt.Sprintf("aggregator/%s:", a.folderCfg.Description())
}
-func (a *aggregator) VerifyConfiguration(from, to config.Configuration) error {
- return nil
-}
-
func (a *aggregator) CommitConfiguration(from, to config.Configuration) bool {
for _, folderCfg := range to.Folders {
if folderCfg.ID == a.folderID {
From 4b750b6dc3dc9904c6046d1c6370b57e1710cb4b Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Mon, 22 Nov 2021 08:59:47 +0100
Subject: [PATCH 016/220] all: Remove usage of deprecated io/ioutil (#7971)
As of Go 1.16 io/ioutil is deprecated. This replaces usage with the
corresponding functions in package os and package io.
---
build.go | 13 +++++------
cmd/stcrashreceiver/main.go | 3 +--
cmd/stcrashreceiver/sentry.go | 4 ++--
cmd/stcrashreceiver/sentry_test.go | 4 ++--
cmd/stcrashreceiver/sourcecodeloader.go | 4 ++--
cmd/stcrashreceiver/stcrashreceiver.go | 5 ++--
cmd/stcrashreceiver/util.go | 4 ++--
cmd/strelaypoolsrv/main.go | 7 +++---
cmd/strelaysrv/pool.go | 4 ++--
cmd/stsigtool/main.go | 7 +++---
cmd/syncthing/cli/main.go | 4 ++--
cmd/syncthing/cli/utils.go | 4 ++--
cmd/syncthing/crash_reporting.go | 3 +--
cmd/syncthing/decrypt/decrypt.go | 4 ++--
cmd/syncthing/main.go | 3 +--
cmd/syncthing/monitor_test.go | 7 +++---
cmd/ursrv/main.go | 5 ++--
lib/api/api.go | 15 ++++++------
lib/api/api_test.go | 13 +++++------
lib/api/auto/auto_test.go | 4 ++--
lib/api/confighandler.go | 3 +--
lib/assets/assets_test.go | 5 ++--
lib/config/config.go | 3 +--
lib/config/config_test.go | 9 ++++---
lib/discover/global.go | 3 +--
lib/discover/global_test.go | 4 ++--
lib/fs/basicfs_test.go | 3 +--
lib/fs/basicfs_watch_test.go | 3 +--
lib/fs/fakefs.go | 3 +--
lib/fs/fakefs_test.go | 13 +++++------
lib/fs/filesystem_copy_range_test.go | 5 ++--
lib/fs/mtimefs_test.go | 17 +++++++-------
lib/ignore/ignore_test.go | 13 +++++------
lib/logger/logger.go | 3 +--
lib/logger/logger_test.go | 6 ++---
lib/model/folder_sendrecv_test.go | 5 ++--
lib/model/model_test.go | 9 ++++---
lib/model/requests_test.go | 5 ++--
lib/model/testutils_test.go | 5 ++--
lib/nat/structs_test.go | 3 +--
lib/osutil/atomic.go | 2 +-
lib/osutil/atomic_test.go | 11 ++++-----
lib/osutil/atomic_unix_test.go | 3 +--
lib/osutil/osutil_test.go | 8 +++----
lib/osutil/traversessymlink_test.go | 5 ++--
lib/protocol/protocol_test.go | 6 ++---
lib/rc/rc.go | 3 +--
lib/scanner/walk_test.go | 7 +++---
lib/syncthing/syncthing_test.go | 3 +--
lib/syncthing/utils.go | 5 ++--
lib/upgrade/upgrade_supported.go | 7 +++---
lib/upnp/upnp.go | 4 ++--
lib/versioner/external_test.go | 3 +--
lib/versioner/simple_test.go | 4 ++--
lib/versioner/staggered_test.go | 6 ++---
lib/versioner/trashcan_test.go | 9 +++----
lib/versioner/versioner_test.go | 3 +--
lib/weakhash/weakhash_test.go | 3 +--
script/authors.go | 6 ++---
script/commit-msg.go | 3 +--
script/genassets.go | 3 +--
script/prune_mocks.go | 3 +--
script/transifexdl.go | 4 ++--
test/conflict_test.go | 31 ++++++++++++-------------
test/delay_scan_test.go | 4 ++--
test/http_test.go | 9 +++----
test/ignore_test.go | 5 ++--
test/override_test.go | 16 ++++++-------
test/parallell_scan_test.go | 4 ++--
test/reset_test.go | 3 +--
test/util.go | 5 ++--
71 files changed, 190 insertions(+), 235 deletions(-)
diff --git a/build.go b/build.go
index 791ca9a41e1..2a480a5464d 100644
--- a/build.go
+++ b/build.go
@@ -20,7 +20,6 @@ import (
"flag"
"fmt"
"io"
- "io/ioutil"
"log"
"os"
"os/exec"
@@ -723,7 +722,7 @@ func shouldBuildSyso(dir string) (string, error) {
}
jsonPath := filepath.Join(dir, "versioninfo.json")
- err = ioutil.WriteFile(jsonPath, bs, 0644)
+ err = os.WriteFile(jsonPath, bs, 0644)
if err != nil {
return "", errors.New("failed to create " + jsonPath + ": " + err.Error())
}
@@ -762,12 +761,12 @@ func shouldCleanupSyso(sysoFilePath string) {
// exists. The permission bits are copied as well. If dst already exists and
// the contents are identical to src the modification time is not updated.
func copyFile(src, dst string, perm os.FileMode) error {
- in, err := ioutil.ReadFile(src)
+ in, err := os.ReadFile(src)
if err != nil {
return err
}
- out, err := ioutil.ReadFile(dst)
+ out, err := os.ReadFile(dst)
if err != nil {
// The destination probably doesn't exist, we should create
// it.
@@ -783,7 +782,7 @@ func copyFile(src, dst string, perm os.FileMode) error {
copy:
os.MkdirAll(filepath.Dir(dst), 0777)
- if err := ioutil.WriteFile(dst, in, perm); err != nil {
+ if err := os.WriteFile(dst, in, perm); err != nil {
return err
}
@@ -958,7 +957,7 @@ func rmr(paths ...string) {
}
func getReleaseVersion() (string, error) {
- bs, err := ioutil.ReadFile("RELEASE")
+ bs, err := os.ReadFile("RELEASE")
if err != nil {
return "", err
}
@@ -1290,7 +1289,7 @@ func zipFile(out string, files []archiveFile) {
if strings.HasSuffix(f.dst, ".txt") {
// Text file. Read it and convert line endings.
- bs, err := ioutil.ReadAll(sf)
+ bs, err := io.ReadAll(sf)
if err != nil {
log.Fatal(err)
}
diff --git a/cmd/stcrashreceiver/main.go b/cmd/stcrashreceiver/main.go
index 31ee52ae654..c6a1871614f 100644
--- a/cmd/stcrashreceiver/main.go
+++ b/cmd/stcrashreceiver/main.go
@@ -17,7 +17,6 @@ import (
"flag"
"fmt"
"io"
- "io/ioutil"
"log"
"net/http"
"os"
@@ -58,7 +57,7 @@ func main() {
func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
lr := io.LimitReader(req.Body, maxRequestSize)
- bs, err := ioutil.ReadAll(lr)
+ bs, err := io.ReadAll(lr)
req.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
diff --git a/cmd/stcrashreceiver/sentry.go b/cmd/stcrashreceiver/sentry.go
index 5aa8a7c28d3..e49eba88824 100644
--- a/cmd/stcrashreceiver/sentry.go
+++ b/cmd/stcrashreceiver/sentry.go
@@ -9,7 +9,7 @@ package main
import (
"bytes"
"errors"
- "io/ioutil"
+ "io"
"regexp"
"strings"
"sync"
@@ -93,7 +93,7 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
}
r := bytes.NewReader(report)
- ctx, err := stack.ParseDump(r, ioutil.Discard, false)
+ ctx, err := stack.ParseDump(r, io.Discard, false)
if err != nil {
return nil, err
}
diff --git a/cmd/stcrashreceiver/sentry_test.go b/cmd/stcrashreceiver/sentry_test.go
index fbfd2c42132..e052197c913 100644
--- a/cmd/stcrashreceiver/sentry_test.go
+++ b/cmd/stcrashreceiver/sentry_test.go
@@ -8,7 +8,7 @@ package main
import (
"fmt"
- "io/ioutil"
+ "os"
"testing"
)
@@ -59,7 +59,7 @@ func TestParseVersion(t *testing.T) {
}
func TestParseReport(t *testing.T) {
- bs, err := ioutil.ReadFile("_testdata/panic.log")
+ bs, err := os.ReadFile("_testdata/panic.log")
if err != nil {
t.Fatal(err)
}
diff --git a/cmd/stcrashreceiver/sourcecodeloader.go b/cmd/stcrashreceiver/sourcecodeloader.go
index 1da310d3ce5..e51d8335bc6 100644
--- a/cmd/stcrashreceiver/sourcecodeloader.go
+++ b/cmd/stcrashreceiver/sourcecodeloader.go
@@ -9,7 +9,7 @@ package main
import (
"bytes"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"path/filepath"
"strings"
@@ -80,7 +80,7 @@ func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]b
fmt.Println("Loading source:", resp.Status)
return nil, 0
}
- data, err := ioutil.ReadAll(resp.Body)
+ data, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if err != nil {
fmt.Println("Loading source:", err.Error())
diff --git a/cmd/stcrashreceiver/stcrashreceiver.go b/cmd/stcrashreceiver/stcrashreceiver.go
index 9a39296c12e..f6f988f3e08 100644
--- a/cmd/stcrashreceiver/stcrashreceiver.go
+++ b/cmd/stcrashreceiver/stcrashreceiver.go
@@ -10,7 +10,6 @@ import (
"bytes"
"compress/gzip"
"io"
- "io/ioutil"
"log"
"net/http"
"os"
@@ -96,7 +95,7 @@ func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWrite
// Read at most maxRequestSize of report data.
log.Println("Receiving report", reportID)
lr := io.LimitReader(req.Body, maxRequestSize)
- bs, err := ioutil.ReadAll(lr)
+ bs, err := io.ReadAll(lr)
if err != nil {
log.Println("Reading report:", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
@@ -110,7 +109,7 @@ func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWrite
gw.Close()
// Create an output file with the compressed report
- err = ioutil.WriteFile(fullPath, buf.Bytes(), 0644)
+ err = os.WriteFile(fullPath, buf.Bytes(), 0644)
if err != nil {
log.Println("Saving report:", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
diff --git a/cmd/stcrashreceiver/util.go b/cmd/stcrashreceiver/util.go
index ac164a1c611..ecd0fde914f 100644
--- a/cmd/stcrashreceiver/util.go
+++ b/cmd/stcrashreceiver/util.go
@@ -10,9 +10,9 @@ import (
"bytes"
"compress/gzip"
"fmt"
- "io/ioutil"
"net"
"net/http"
+ "os"
"path/filepath"
"time"
@@ -53,5 +53,5 @@ func compressAndWrite(bs []byte, fullPath string) error {
gw.Close()
// Create an output file with the compressed report
- return ioutil.WriteFile(fullPath, buf.Bytes(), 0644)
+ return os.WriteFile(fullPath, buf.Bytes(), 0644)
}
diff --git a/cmd/strelaypoolsrv/main.go b/cmd/strelaypoolsrv/main.go
index a409b3c686e..a0364a81c55 100644
--- a/cmd/strelaypoolsrv/main.go
+++ b/cmd/strelaypoolsrv/main.go
@@ -11,7 +11,6 @@ import (
"flag"
"fmt"
"io"
- "io/ioutil"
"log"
"net"
"net/http"
@@ -560,7 +559,7 @@ func limit(addr string, cache *lru.Cache, lock sync.Mutex, intv time.Duration, b
}
func loadRelays(file string) []*relay {
- content, err := ioutil.ReadFile(file)
+ content, err := os.ReadFile(file)
if err != nil {
log.Println("Failed to load relays: " + err.Error())
return nil
@@ -598,11 +597,11 @@ func saveRelays(file string, relays []*relay) error {
for _, relay := range relays {
content += relay.uri.String() + "\n"
}
- return ioutil.WriteFile(file, []byte(content), 0777)
+ return os.WriteFile(file, []byte(content), 0777)
}
func createTestCertificate() tls.Certificate {
- tmpDir, err := ioutil.TempDir("", "relaypoolsrv")
+ tmpDir, err := os.MkdirTemp("", "relaypoolsrv")
if err != nil {
log.Fatal(err)
}
diff --git a/cmd/strelaysrv/pool.go b/cmd/strelaysrv/pool.go
index 9ddde16c58d..8aa48c935b7 100644
--- a/cmd/strelaysrv/pool.go
+++ b/cmd/strelaysrv/pool.go
@@ -6,7 +6,7 @@ import (
"bytes"
"crypto/tls"
"encoding/json"
- "io/ioutil"
+ "io"
"log"
"net/http"
"net/url"
@@ -56,7 +56,7 @@ func poolHandler(pool string, uri *url.URL, mapping mapping, ownCert tls.Certifi
continue
}
- bs, err := ioutil.ReadAll(resp.Body)
+ bs, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Printf("Error joining pool %v: reading response: %v", pool, err)
diff --git a/cmd/stsigtool/main.go b/cmd/stsigtool/main.go
index 0217f17e801..ab4c46b3e83 100644
--- a/cmd/stsigtool/main.go
+++ b/cmd/stsigtool/main.go
@@ -9,7 +9,6 @@ package main
import (
"flag"
"io"
- "io/ioutil"
"log"
"os"
@@ -69,7 +68,7 @@ func gen() {
}
func sign(keyname, dataname string) {
- privkey, err := ioutil.ReadFile(keyname)
+ privkey, err := os.ReadFile(keyname)
if err != nil {
log.Fatal(err)
}
@@ -95,7 +94,7 @@ func sign(keyname, dataname string) {
}
func verifyWithFile(signame, dataname, keyname string) {
- pubkey, err := ioutil.ReadFile(keyname)
+ pubkey, err := os.ReadFile(keyname)
if err != nil {
log.Fatal(err)
}
@@ -103,7 +102,7 @@ func verifyWithFile(signame, dataname, keyname string) {
}
func verifyWithKey(signame, dataname string, pubkey []byte) {
- sig, err := ioutil.ReadFile(signame)
+ sig, err := os.ReadFile(signame)
if err != nil {
log.Fatal(err)
}
diff --git a/cmd/syncthing/cli/main.go b/cmd/syncthing/cli/main.go
index 8e08b92ef5a..b6599c893b1 100644
--- a/cmd/syncthing/cli/main.go
+++ b/cmd/syncthing/cli/main.go
@@ -9,7 +9,7 @@ package cli
import (
"bufio"
"fmt"
- "io/ioutil"
+ "io"
"os"
"strings"
@@ -151,7 +151,7 @@ func parseFlags(c *preCli) error {
}
}
// We don't want kong to print anything nor os.Exit (e.g. on -h)
- parser, err := kong.New(c, kong.Writers(ioutil.Discard, ioutil.Discard), kong.Exit(func(int) {}))
+ parser, err := kong.New(c, kong.Writers(io.Discard, io.Discard), kong.Exit(func(int) {}))
if err != nil {
return err
}
diff --git a/cmd/syncthing/cli/utils.go b/cmd/syncthing/cli/utils.go
index 17bde14379c..c495458e10c 100644
--- a/cmd/syncthing/cli/utils.go
+++ b/cmd/syncthing/cli/utils.go
@@ -10,7 +10,7 @@ import (
"encoding/json"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"mime"
"net/http"
"os"
@@ -23,7 +23,7 @@ import (
)
func responseToBArray(response *http.Response) ([]byte, error) {
- bytes, err := ioutil.ReadAll(response.Body)
+ bytes, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
diff --git a/cmd/syncthing/crash_reporting.go b/cmd/syncthing/crash_reporting.go
index 828695f1aad..27896ff8a61 100644
--- a/cmd/syncthing/crash_reporting.go
+++ b/cmd/syncthing/crash_reporting.go
@@ -10,7 +10,6 @@ import (
"bytes"
"context"
"fmt"
- "io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -62,7 +61,7 @@ func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
// the log contents. A HEAD request is made to see if the log has already
// been reported. If not, a PUT is made with the log contents.
func uploadPanicLog(ctx context.Context, urlBase, file string) error {
- data, err := ioutil.ReadFile(file)
+ data, err := os.ReadFile(file)
if err != nil {
return err
}
diff --git a/cmd/syncthing/decrypt/decrypt.go b/cmd/syncthing/decrypt/decrypt.go
index 119583dbd70..bb7651e7257 100644
--- a/cmd/syncthing/decrypt/decrypt.go
+++ b/cmd/syncthing/decrypt/decrypt.go
@@ -12,8 +12,8 @@ import (
"encoding/json"
"fmt"
"io"
- "io/ioutil"
"log"
+ "os"
"path/filepath"
"github.com/syncthing/syncthing/lib/config"
@@ -112,7 +112,7 @@ func (c *CLI) withContinue(err error) error {
// error.
func (c *CLI) getFolderID() (string, error) {
tokenPath := filepath.Join(c.Path, c.TokenPath)
- bs, err := ioutil.ReadFile(tokenPath)
+ bs, err := os.ReadFile(tokenPath)
if err != nil {
return "", fmt.Errorf("reading folder token: %w", err)
}
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 127fcc54ff5..3e3604d60a8 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -12,7 +12,6 @@ import (
"crypto/tls"
"fmt"
"io"
- "io/ioutil"
"log"
"net/http"
_ "net/http/pprof" // Need to import this to support STPROFILER.
@@ -500,7 +499,7 @@ func upgradeViaRest() error {
return err
}
if resp.StatusCode != 200 {
- bs, err := ioutil.ReadAll(resp.Body)
+ bs, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return err
diff --git a/cmd/syncthing/monitor_test.go b/cmd/syncthing/monitor_test.go
index 345a9a06ceb..6ad8e4ee2d1 100644
--- a/cmd/syncthing/monitor_test.go
+++ b/cmd/syncthing/monitor_test.go
@@ -8,7 +8,6 @@ package main
import (
"io"
- "io/ioutil"
"os"
"path/filepath"
"testing"
@@ -18,7 +17,7 @@ import (
func TestRotatedFile(t *testing.T) {
// Verify that log rotation happens.
- dir, err := ioutil.TempDir("", "syncthing")
+ dir, err := os.MkdirTemp("", "syncthing")
if err != nil {
t.Fatal(err)
}
@@ -179,7 +178,7 @@ func TestAutoClosedFile(t *testing.T) {
}
// The file should have both writes in it.
- bs, err := ioutil.ReadFile(file)
+ bs, err := os.ReadFile(file)
if err != nil {
t.Fatal(err)
}
@@ -199,7 +198,7 @@ func TestAutoClosedFile(t *testing.T) {
}
// It should now contain three writes, as the file is always opened for appending
- bs, err = ioutil.ReadFile(file)
+ bs, err = os.ReadFile(file)
if err != nil {
t.Fatal(err)
}
diff --git a/cmd/ursrv/main.go b/cmd/ursrv/main.go
index 6cbf4429932..0d24ee1be94 100644
--- a/cmd/ursrv/main.go
+++ b/cmd/ursrv/main.go
@@ -13,7 +13,6 @@ import (
"encoding/json"
"html/template"
"io"
- "io/ioutil"
"log"
"net"
"net/http"
@@ -162,7 +161,7 @@ func main() {
if err != nil {
log.Fatalln("template:", err)
}
- bs, err := ioutil.ReadAll(fd)
+ bs, err := io.ReadAll(fd)
if err != nil {
log.Fatalln("template:", err)
}
@@ -324,7 +323,7 @@ func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
rep.Address = addr
lr := &io.LimitedReader{R: r.Body, N: 40 * 1024}
- bs, _ := ioutil.ReadAll(lr)
+ bs, _ := io.ReadAll(lr)
if err := json.Unmarshal(bs, &rep); err != nil {
log.Println("decode:", err)
if debug {
diff --git a/lib/api/api.go b/lib/api/api.go
index 6ba4235f347..1e8c4747b85 100644
--- a/lib/api/api.go
+++ b/lib/api/api.go
@@ -15,7 +15,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"log"
"net"
"net/http"
@@ -382,7 +381,7 @@ func (s *service) Serve(ctx context.Context) error {
ReadTimeout: 15 * time.Second,
// Prevent the HTTP server from logging stuff on its own. The things we
// care about we log ourselves from the handlers.
- ErrorLog: log.New(ioutil.Discard, "", 0),
+ ErrorLog: log.New(io.Discard, "", 0),
}
l.Infoln("GUI and API listening on", listener.Addr())
@@ -1098,7 +1097,7 @@ func (s *service) getSystemError(w http.ResponseWriter, r *http.Request) {
}
func (s *service) postSystemError(w http.ResponseWriter, r *http.Request) {
- bs, _ := ioutil.ReadAll(r.Body)
+ bs, _ := io.ReadAll(r.Body)
r.Body.Close()
l.Warnln(string(bs))
}
@@ -1165,7 +1164,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
// Panic files
if panicFiles, err := filepath.Glob(filepath.Join(locations.GetBaseDir(locations.ConfigBaseDir), "panic*")); err == nil {
for _, f := range panicFiles {
- if panicFile, err := ioutil.ReadFile(f); err != nil {
+ if panicFile, err := os.ReadFile(f); err != nil {
l.Warnf("Support bundle: failed to load %s: %s", filepath.Base(f), err)
} else {
files = append(files, fileEntry{name: filepath.Base(f), data: panicFile})
@@ -1174,7 +1173,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
}
// Archived log (default on Windows)
- if logFile, err := ioutil.ReadFile(locations.Get(locations.LogFile)); err == nil {
+ if logFile, err := os.ReadFile(locations.Get(locations.LogFile)); err == nil {
files = append(files, fileEntry{name: "log-ondisk.txt", data: logFile})
}
@@ -1233,7 +1232,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
zipFilePath := filepath.Join(locations.GetBaseDir(locations.ConfigBaseDir), zipFileName)
// Write buffer zip to local zip file (back up)
- if err := ioutil.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0600); err != nil {
+ if err := os.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0600); err != nil {
l.Warnln("Support bundle: support bundle zip could not be created:", err)
}
@@ -1323,7 +1322,7 @@ func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
func (s *service) postDBIgnores(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
- bs, err := ioutil.ReadAll(r.Body)
+ bs, err := io.ReadAll(r.Body)
r.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
@@ -1614,7 +1613,7 @@ func (s *service) getFolderVersions(w http.ResponseWriter, r *http.Request) {
func (s *service) postFolderVersionsRestore(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
- bs, err := ioutil.ReadAll(r.Body)
+ bs, err := io.ReadAll(r.Body)
r.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
diff --git a/lib/api/api_test.go b/lib/api/api_test.go
index f9ecc15c5cc..1bfe5df0839 100644
--- a/lib/api/api_test.go
+++ b/lib/api/api_test.go
@@ -13,7 +13,6 @@ import (
"encoding/json"
"fmt"
"io"
- "io/ioutil"
"net"
"net/http"
"net/http/httptest"
@@ -223,7 +222,7 @@ func expectURLToContain(t *testing.T, url, exp string) {
return
}
- data, err := ioutil.ReadAll(res.Body)
+ data, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Error(err)
@@ -508,7 +507,7 @@ func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase, apikey strin
return
}
- data, err := ioutil.ReadAll(resp.Body)
+ data, err := io.ReadAll(resp.Body)
if err != nil {
t.Errorf("Unexpected error reading %s: %v", tc.URL, err)
return
@@ -1137,7 +1136,7 @@ func TestBrowse(t *testing.T) {
pathSep := string(os.PathSeparator)
- tmpDir, err := ioutil.TempDir("", "syncthing")
+ tmpDir, err := os.MkdirTemp("", "syncthing")
if err != nil {
t.Fatal(err)
}
@@ -1146,7 +1145,7 @@ func TestBrowse(t *testing.T) {
if err := os.Mkdir(filepath.Join(tmpDir, "dir"), 0755); err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(tmpDir, "MiXEDCase"), 0755); err != nil {
@@ -1251,7 +1250,7 @@ func TestConfigChanges(t *testing.T) {
APIKey: testAPIKey,
},
}
- tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
+ tmpFile, err := os.CreateTemp("", "syncthing-testConfig-")
if err != nil {
panic(err)
}
@@ -1393,7 +1392,7 @@ func runningInContainer() bool {
return false
}
- bs, err := ioutil.ReadFile("/proc/1/cgroup")
+ bs, err := os.ReadFile("/proc/1/cgroup")
if err != nil {
return false
}
diff --git a/lib/api/auto/auto_test.go b/lib/api/auto/auto_test.go
index 2e997d5e3a3..db7e276f954 100644
--- a/lib/api/auto/auto_test.go
+++ b/lib/api/auto/auto_test.go
@@ -9,7 +9,7 @@ package auto_test
import (
"bytes"
"compress/gzip"
- "io/ioutil"
+ "io"
"strings"
"testing"
@@ -28,7 +28,7 @@ func TestAssets(t *testing.T) {
var gr *gzip.Reader
gr, _ = gzip.NewReader(strings.NewReader(idx.Content))
- html, _ := ioutil.ReadAll(gr)
+ html, _ := io.ReadAll(gr)
if !bytes.Contains(html, []byte(" 0 {
lr := io.LimitReader(f.rng, diff)
- io.Copy(ioutil.Discard, lr)
+ io.Copy(io.Discard, lr)
}
f.offset = offs
diff --git a/lib/fs/fakefs_test.go b/lib/fs/fakefs_test.go
index 1b095f88128..63cae9d40fa 100644
--- a/lib/fs/fakefs_test.go
+++ b/lib/fs/fakefs_test.go
@@ -10,7 +10,6 @@ import (
"bytes"
"fmt"
"io"
- "io/ioutil"
"os"
"path"
"path/filepath"
@@ -88,7 +87,7 @@ func TestFakeFS(t *testing.T) {
}
// Read
- bs0, err := ioutil.ReadAll(fd)
+ bs0, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -101,7 +100,7 @@ func TestFakeFS(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs1, err := ioutil.ReadAll(fd)
+ bs1, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -139,7 +138,7 @@ func testFakeFSRead(t *testing.T, fs Filesystem) {
// Read
fd.Seek(0, io.SeekStart)
- bs0, err := ioutil.ReadAll(fd)
+ bs0, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -154,7 +153,7 @@ func testFakeFSRead(t *testing.T, fs Filesystem) {
if n != len(buf0) {
t.Fatal("short read")
}
- buf1, err := ioutil.ReadAll(fd)
+ buf1, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -252,7 +251,7 @@ func TestFakeFSCaseInsensitive(t *testing.T) {
func createTestDir(t *testing.T) (string, bool) {
t.Helper()
- testDir, err := ioutil.TempDir("", "")
+ testDir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatalf("could not create temporary dir for testing: %s", err)
}
@@ -328,7 +327,7 @@ func testFakeFSCaseInsensitive(t *testing.T, fs Filesystem) {
t.Fatal(err)
}
- bs2, err := ioutil.ReadAll(fd2)
+ bs2, err := io.ReadAll(fd2)
if err != nil {
t.Fatal(err)
}
diff --git a/lib/fs/filesystem_copy_range_test.go b/lib/fs/filesystem_copy_range_test.go
index e2db04fb928..5831753df7a 100644
--- a/lib/fs/filesystem_copy_range_test.go
+++ b/lib/fs/filesystem_copy_range_test.go
@@ -9,7 +9,6 @@ package fs
import (
"bytes"
"io"
- "io/ioutil"
"math/rand"
"os"
"path/filepath"
@@ -257,7 +256,7 @@ func TestCopyRange(tttt *testing.T) {
paths = []string{""}
}
for _, path := range paths {
- testPath, err := ioutil.TempDir(path, "")
+ testPath, err := os.MkdirTemp(path, "")
if err != nil {
tttt.Fatal(err)
}
@@ -273,7 +272,7 @@ func TestCopyRange(tttt *testing.T) {
tt.Run(testCase.name, func(t *testing.T) {
srcBuf := make([]byte, testCase.srcSize)
dstBuf := make([]byte, testCase.dstSize)
- td, err := ioutil.TempDir(testPath, "")
+ td, err := os.MkdirTemp(testPath, "")
if err != nil {
t.Fatal(err)
}
diff --git a/lib/fs/mtimefs_test.go b/lib/fs/mtimefs_test.go
index 3f7dc6c0704..bf14f514ef2 100644
--- a/lib/fs/mtimefs_test.go
+++ b/lib/fs/mtimefs_test.go
@@ -8,7 +8,6 @@ package fs
import (
"errors"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -20,9 +19,9 @@ func TestMtimeFS(t *testing.T) {
os.RemoveAll("testdata")
defer os.RemoveAll("testdata")
os.Mkdir("testdata", 0755)
- ioutil.WriteFile("testdata/exists0", []byte("hello"), 0644)
- ioutil.WriteFile("testdata/exists1", []byte("hello"), 0644)
- ioutil.WriteFile("testdata/exists2", []byte("hello"), 0644)
+ os.WriteFile("testdata/exists0", []byte("hello"), 0644)
+ os.WriteFile("testdata/exists1", []byte("hello"), 0644)
+ os.WriteFile("testdata/exists2", []byte("hello"), 0644)
// a random time with nanosecond precision
testTime := time.Unix(1234567890, 123456789)
@@ -83,7 +82,7 @@ func TestMtimeFS(t *testing.T) {
}
func TestMtimeFSWalk(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -93,7 +92,7 @@ func TestMtimeFSWalk(t *testing.T) {
mtimefs := newMtimeFS(underlying, make(mapStore))
mtimefs.chtimes = failChtimes
- if err := ioutil.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
@@ -137,7 +136,7 @@ func TestMtimeFSWalk(t *testing.T) {
}
func TestMtimeFSOpen(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -147,7 +146,7 @@ func TestMtimeFSOpen(t *testing.T) {
mtimefs := newMtimeFS(underlying, make(mapStore))
mtimefs.chtimes = failChtimes
- if err := ioutil.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
@@ -200,7 +199,7 @@ func TestMtimeFSInsensitive(t *testing.T) {
os.RemoveAll("testdata")
defer os.RemoveAll("testdata")
os.Mkdir("testdata", 0755)
- ioutil.WriteFile("testdata/FiLe", []byte("hello"), 0644)
+ os.WriteFile("testdata/FiLe", []byte("hello"), 0644)
// a random time with nanosecond precision
testTime := time.Unix(1234567890, 123456789)
diff --git a/lib/ignore/ignore_test.go b/lib/ignore/ignore_test.go
index d9564ec6634..9781cbdfafc 100644
--- a/lib/ignore/ignore_test.go
+++ b/lib/ignore/ignore_test.go
@@ -10,7 +10,6 @@ import (
"bytes"
"fmt"
"io"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -232,7 +231,7 @@ func TestCaseSensitivity(t *testing.T) {
}
func TestCaching(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -425,7 +424,7 @@ flamingo
*.crow
`
// Caches per file, hence write the patterns to a file.
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
if err != nil {
b.Fatal(err)
}
@@ -466,7 +465,7 @@ flamingo
}
func TestCacheReload(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -989,7 +988,7 @@ func TestIssue4689(t *testing.T) {
}
func TestIssue4901(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -1001,7 +1000,7 @@ func TestIssue4901(t *testing.T) {
puppy
`
- if err := ioutil.WriteFile(filepath.Join(dir, ".stignore"), []byte(stignore), 0777); err != nil {
+ if err := os.WriteFile(filepath.Join(dir, ".stignore"), []byte(stignore), 0777); err != nil {
t.Fatalf(err.Error())
}
@@ -1020,7 +1019,7 @@ func TestIssue4901(t *testing.T) {
}
}
- if err := ioutil.WriteFile(filepath.Join(dir, "unicorn-lazor-death"), []byte(" "), 0777); err != nil {
+ if err := os.WriteFile(filepath.Join(dir, "unicorn-lazor-death"), []byte(" "), 0777); err != nil {
t.Fatalf(err.Error())
}
diff --git a/lib/logger/logger.go b/lib/logger/logger.go
index cd3e3f23777..731be426b09 100644
--- a/lib/logger/logger.go
+++ b/lib/logger/logger.go
@@ -10,7 +10,6 @@ package logger
import (
"fmt"
"io"
- "io/ioutil"
"log"
"os"
"strings"
@@ -75,7 +74,7 @@ func New() Logger {
if os.Getenv("LOGGER_DISCARD") != "" {
// Hack to completely disable logging, for example when running
// benchmarks.
- return newLogger(ioutil.Discard)
+ return newLogger(io.Discard)
}
return newLogger(controlStripper{os.Stdout})
}
diff --git a/lib/logger/logger_test.go b/lib/logger/logger_test.go
index 1937aa646db..cc3b72e67a0 100644
--- a/lib/logger/logger_test.go
+++ b/lib/logger/logger_test.go
@@ -6,7 +6,7 @@ package logger
import (
"bytes"
"fmt"
- "io/ioutil"
+ "io"
"log"
"strings"
"testing"
@@ -186,12 +186,12 @@ func TestControlStripper(t *testing.T) {
}
func BenchmarkLog(b *testing.B) {
- l := newLogger(controlStripper{ioutil.Discard})
+ l := newLogger(controlStripper{io.Discard})
benchmarkLogger(b, l)
}
func BenchmarkLogNoStripper(b *testing.B) {
- l := newLogger(ioutil.Discard)
+ l := newLogger(io.Discard)
benchmarkLogger(b, l)
}
diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go
index 06bf7d6d79d..c3a76424286 100644
--- a/lib/model/folder_sendrecv_test.go
+++ b/lib/model/folder_sendrecv_test.go
@@ -13,7 +13,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -680,8 +679,8 @@ func TestIssue3164(t *testing.T) {
ignDir := filepath.Join("issue3164", "oktodelete")
subDir := filepath.Join(ignDir, "foobar")
must(t, ffs.MkdirAll(subDir, 0777))
- must(t, ioutil.WriteFile(filepath.Join(tmpDir, subDir, "file"), []byte("Hello"), 0644))
- must(t, ioutil.WriteFile(filepath.Join(tmpDir, ignDir, "file"), []byte("Hello"), 0644))
+ must(t, os.WriteFile(filepath.Join(tmpDir, subDir, "file"), []byte("Hello"), 0644))
+ must(t, os.WriteFile(filepath.Join(tmpDir, ignDir, "file"), []byte("Hello"), 0644))
file := protocol.FileInfo{
Name: "issue3164",
}
diff --git a/lib/model/model_test.go b/lib/model/model_test.go
index 121bcdffeec..78fa8e9f9af 100644
--- a/lib/model/model_test.go
+++ b/lib/model/model_test.go
@@ -12,7 +12,6 @@ import (
"encoding/json"
"fmt"
"io"
- "io/ioutil"
"math/rand"
"os"
"path/filepath"
@@ -2152,7 +2151,7 @@ func TestIssue2782(t *testing.T) {
if err := os.MkdirAll(testDir+"/syncdir", 0755); err != nil {
t.Skip(err)
}
- if err := ioutil.WriteFile(testDir+"/syncdir/file", []byte("hello, world\n"), 0644); err != nil {
+ if err := os.WriteFile(testDir+"/syncdir/file", []byte("hello, world\n"), 0644); err != nil {
t.Skip(err)
}
if err := os.Symlink("syncdir", testDir+"/synclink"); err != nil {
@@ -2763,7 +2762,7 @@ func TestVersionRestore(t *testing.T) {
// In each file, we write the filename as the content
// We verify that the content matches at the expected filenames
// after the restore operation.
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
must(t, err)
defer os.RemoveAll(dir)
@@ -2900,7 +2899,7 @@ func TestVersionRestore(t *testing.T) {
}
defer fd.Close()
- content, err := ioutil.ReadAll(fd)
+ content, err := io.ReadAll(fd)
if err != nil {
t.Error(err)
}
@@ -2930,7 +2929,7 @@ func TestVersionRestore(t *testing.T) {
must(t, err)
defer fd.Close()
- content, err := ioutil.ReadAll(fd)
+ content, err := io.ReadAll(fd)
if err != nil {
t.Error(err)
}
diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go
index 35df4fdd9e9..76868748a2f 100644
--- a/lib/model/requests_test.go
+++ b/lib/model/requests_test.go
@@ -10,7 +10,6 @@ import (
"bytes"
"context"
"errors"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -236,7 +235,7 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
// Create a temporary directory that we will use as target to see if
// we can escape to it
- tmpdir, err := ioutil.TempDir("", "syncthing-test")
+ tmpdir, err := os.MkdirTemp("", "syncthing-test")
if err != nil {
t.Fatal(err)
}
@@ -681,7 +680,7 @@ func TestRequestSymlinkWindows(t *testing.T) {
}
func equalContents(path string, contents []byte) error {
- if bs, err := ioutil.ReadFile(path); err != nil {
+ if bs, err := os.ReadFile(path); err != nil {
return err
} else if !bytes.Equal(bs, contents) {
return errors.New("incorrect data")
diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go
index 4ee390f5d8e..a31e57fdd32 100644
--- a/lib/model/testutils_test.go
+++ b/lib/model/testutils_test.go
@@ -8,7 +8,6 @@ package model
import (
"context"
- "io/ioutil"
"os"
"testing"
"time"
@@ -76,7 +75,7 @@ func init() {
}
func createTmpWrapper(cfg config.Configuration) (config.Wrapper, context.CancelFunc) {
- tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
+ tmpFile, err := os.CreateTemp("", "syncthing-testConfig-")
if err != nil {
panic(err)
}
@@ -215,7 +214,7 @@ func cleanupModelAndRemoveDir(m *testModel, dir string) {
}
func createTmpDir() string {
- tmpDir, err := ioutil.TempDir("", "syncthing_testFolder-")
+ tmpDir, err := os.MkdirTemp("", "syncthing_testFolder-")
if err != nil {
panic("Failed to create temporary testing dir")
}
diff --git a/lib/nat/structs_test.go b/lib/nat/structs_test.go
index 661a552f856..b43e52e08cf 100644
--- a/lib/nat/structs_test.go
+++ b/lib/nat/structs_test.go
@@ -7,7 +7,6 @@
package nat
import (
- "io/ioutil"
"net"
"os"
"testing"
@@ -60,7 +59,7 @@ func TestMappingValidGateway(t *testing.T) {
}
func TestMappingClearAddresses(t *testing.T) {
- tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
+ tmpFile, err := os.CreateTemp("", "syncthing-testConfig-")
if err != nil {
t.Fatal(err)
}
diff --git a/lib/osutil/atomic.go b/lib/osutil/atomic.go
index c2f6dd6e942..bf9730daf94 100644
--- a/lib/osutil/atomic.go
+++ b/lib/osutil/atomic.go
@@ -43,7 +43,7 @@ func CreateAtomic(path string) (*AtomicWriter, error) {
// permissions.
func CreateAtomicFilesystem(filesystem fs.Filesystem, path string) (*AtomicWriter, error) {
// The security of this depends on the tempfile having secure
- // permissions, 0600, from the beginning. This is what ioutil.TempFile
+ // permissions, 0600, from the beginning. This is what os.CreateTemp
// does. We have a test that verifies that that is the case, should this
// ever change in the standard library in the future.
fd, err := TempFile(filesystem, filepath.Dir(path), TempPrefix)
diff --git a/lib/osutil/atomic_test.go b/lib/osutil/atomic_test.go
index 70c5b90d6ab..c9737784ad7 100644
--- a/lib/osutil/atomic_test.go
+++ b/lib/osutil/atomic_test.go
@@ -8,7 +8,6 @@ package osutil
import (
"bytes"
- "io/ioutil"
"os"
"path/filepath"
"testing"
@@ -35,7 +34,7 @@ func TestCreateAtomicCreate(t *testing.T) {
t.Fatal("written bytes", n, "!= 5")
}
- if _, err := ioutil.ReadFile("testdata/file"); err == nil {
+ if _, err := os.ReadFile("testdata/file"); err == nil {
t.Fatal("file should not exist")
}
@@ -43,7 +42,7 @@ func TestCreateAtomicCreate(t *testing.T) {
t.Fatal(err)
}
- bs, err := ioutil.ReadFile("testdata/file")
+ bs, err := os.ReadFile("testdata/file")
if err != nil {
t.Fatal(err)
}
@@ -62,7 +61,7 @@ func TestCreateAtomicReplaceReadOnly(t *testing.T) {
func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
t.Helper()
- testdir, err := ioutil.TempDir("", "syncthing")
+ testdir, err := os.MkdirTemp("", "syncthing")
if err != nil {
t.Fatal(err)
}
@@ -75,7 +74,7 @@ func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
t.Fatal(err)
}
- if err := ioutil.WriteFile(testfile, []byte("some old data"), oldPerms); err != nil {
+ if err := os.WriteFile(testfile, []byte("some old data"), oldPerms); err != nil {
t.Fatal(err)
}
@@ -103,7 +102,7 @@ func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
t.Fatal(err)
}
- bs, err := ioutil.ReadFile(testfile)
+ bs, err := os.ReadFile(testfile)
if err != nil {
t.Fatal(err)
}
diff --git a/lib/osutil/atomic_unix_test.go b/lib/osutil/atomic_unix_test.go
index ec4f8e68e65..1d2e17da152 100644
--- a/lib/osutil/atomic_unix_test.go
+++ b/lib/osutil/atomic_unix_test.go
@@ -12,7 +12,6 @@
package osutil
import (
- "io/ioutil"
"os"
"syscall"
"testing"
@@ -24,7 +23,7 @@ func TestTempFilePermissions(t *testing.T) {
oldMask := syscall.Umask(0)
defer syscall.Umask(oldMask)
- fd, err := ioutil.TempFile("", "test")
+ fd, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal(err)
}
diff --git a/lib/osutil/osutil_test.go b/lib/osutil/osutil_test.go
index 772097e53a5..db1e608fddc 100644
--- a/lib/osutil/osutil_test.go
+++ b/lib/osutil/osutil_test.go
@@ -7,7 +7,7 @@
package osutil_test
import (
- "io/ioutil"
+ "io"
"os"
"path/filepath"
"runtime"
@@ -83,7 +83,7 @@ func TestIsDeleted(t *testing.T) {
func TestRenameOrCopy(t *testing.T) {
mustTempDir := func() string {
t.Helper()
- tmpDir, err := ioutil.TempDir("", "")
+ tmpDir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -131,7 +131,7 @@ func TestRenameOrCopy(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- buf, err := ioutil.ReadAll(fd)
+ buf, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -147,7 +147,7 @@ func TestRenameOrCopy(t *testing.T) {
if fd, err := test.dst.Open("new"); err != nil {
t.Fatal(err)
} else {
- if buf, err := ioutil.ReadAll(fd); err != nil {
+ if buf, err := io.ReadAll(fd); err != nil {
t.Fatal(err)
} else if string(buf) != content {
t.Fatalf("expected %s got %s", content, string(buf))
diff --git a/lib/osutil/traversessymlink_test.go b/lib/osutil/traversessymlink_test.go
index 5bf6e7b59f4..374c471fd58 100644
--- a/lib/osutil/traversessymlink_test.go
+++ b/lib/osutil/traversessymlink_test.go
@@ -7,7 +7,6 @@
package osutil_test
import (
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -18,7 +17,7 @@ import (
)
func TestTraversesSymlink(t *testing.T) {
- tmpDir, err := ioutil.TempDir(".", ".test-TraversesSymlink-")
+ tmpDir, err := os.MkdirTemp(".", ".test-TraversesSymlink-")
if err != nil {
panic("Failed to create temporary testing dir")
}
@@ -71,7 +70,7 @@ func TestTraversesSymlink(t *testing.T) {
}
func TestIssue4875(t *testing.T) {
- tmpDir, err := ioutil.TempDir("", ".test-Issue4875-")
+ tmpDir, err := os.MkdirTemp("", ".test-Issue4875-")
if err != nil {
panic("Failed to create temporary testing dir")
}
diff --git a/lib/protocol/protocol_test.go b/lib/protocol/protocol_test.go
index 1c1d617f75f..81a74bb9f9a 100644
--- a/lib/protocol/protocol_test.go
+++ b/lib/protocol/protocol_test.go
@@ -10,7 +10,7 @@ import (
"encoding/json"
"errors"
"io"
- "io/ioutil"
+ "os"
"runtime"
"sync"
"testing"
@@ -432,8 +432,8 @@ func testMarshal(t *testing.T, prefix string, m1, m2 message) bool {
bs1, _ := json.MarshalIndent(m1, "", " ")
bs2, _ := json.MarshalIndent(m2, "", " ")
if !bytes.Equal(bs1, bs2) {
- ioutil.WriteFile(prefix+"-1.txt", bs1, 0644)
- ioutil.WriteFile(prefix+"-2.txt", bs2, 0644)
+ os.WriteFile(prefix+"-1.txt", bs1, 0644)
+ os.WriteFile(prefix+"-2.txt", bs2, 0644)
return false
}
diff --git a/lib/rc/rc.go b/lib/rc/rc.go
index f21d0e31df7..875ee849e7c 100644
--- a/lib/rc/rc.go
+++ b/lib/rc/rc.go
@@ -14,7 +14,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"log"
"net/http"
"net/url"
@@ -393,7 +392,7 @@ func (p *Process) Model(folder string) (Model, error) {
}
func (p *Process) readResponse(resp *http.Response) ([]byte, error) {
- bs, err := ioutil.ReadAll(resp.Body)
+ bs, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return bs, err
diff --git a/lib/scanner/walk_test.go b/lib/scanner/walk_test.go
index ba7e9a33138..4edad2b4778 100644
--- a/lib/scanner/walk_test.go
+++ b/lib/scanner/walk_test.go
@@ -13,7 +13,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -376,7 +375,7 @@ func TestWalkSymlinkWindows(t *testing.T) {
func TestWalkRootSymlink(t *testing.T) {
// Create a folder with a symlink in it
- tmp, err := ioutil.TempDir("", "")
+ tmp, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -709,7 +708,7 @@ func TestStopWalk(t *testing.T) {
}
func TestIssue4799(t *testing.T) {
- tmp, err := ioutil.TempDir("", "")
+ tmp, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -771,7 +770,7 @@ func TestRecurseInclude(t *testing.T) {
}
func TestIssue4841(t *testing.T) {
- tmp, err := ioutil.TempDir("", "")
+ tmp, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
diff --git a/lib/syncthing/syncthing_test.go b/lib/syncthing/syncthing_test.go
index 8add69ee821..e9feedf4487 100644
--- a/lib/syncthing/syncthing_test.go
+++ b/lib/syncthing/syncthing_test.go
@@ -7,7 +7,6 @@
package syncthing
import (
- "io/ioutil"
"os"
"testing"
"time"
@@ -22,7 +21,7 @@ import (
func tempCfgFilename(t *testing.T) string {
t.Helper()
- f, err := ioutil.TempFile("", "syncthing-testConfig-")
+ f, err := os.CreateTemp("", "syncthing-testConfig-")
if err != nil {
t.Fatal(err)
}
diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go
index eecb563942f..5f59af2b64c 100644
--- a/lib/syncthing/utils.go
+++ b/lib/syncthing/utils.go
@@ -10,7 +10,6 @@ import (
"crypto/tls"
"fmt"
"io"
- "io/ioutil"
"os"
"github.com/pkg/errors"
@@ -133,12 +132,12 @@ func archiveAndSaveConfig(cfg config.Wrapper, originalVersion int) error {
}
func copyFile(src, dst string) error {
- bs, err := ioutil.ReadFile(src)
+ bs, err := os.ReadFile(src)
if err != nil {
return err
}
- if err := ioutil.WriteFile(dst, bs, 0600); err != nil {
+ if err := os.WriteFile(dst, bs, 0600); err != nil {
// Attempt to clean up
os.Remove(dst)
return err
diff --git a/lib/upgrade/upgrade_supported.go b/lib/upgrade/upgrade_supported.go
index 45eeaf426ce..80312a6facd 100644
--- a/lib/upgrade/upgrade_supported.go
+++ b/lib/upgrade/upgrade_supported.go
@@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net/http"
"os"
"path"
@@ -284,7 +283,7 @@ func readTarGz(archiveName, dir string, r io.Reader) (string, error) {
}
func readZip(archiveName, dir string, r io.Reader) (string, error) {
- body, err := ioutil.ReadAll(r)
+ body, err := io.ReadAll(r)
if err != nil {
return "", err
}
@@ -357,7 +356,7 @@ func archiveFileVisitor(dir string, tempFile *string, signature *[]byte, archive
case "release.sig":
l.Debugf("found signature %s", archivePath)
- *signature, err = ioutil.ReadAll(io.LimitReader(filedata, maxSignatureSize))
+ *signature, err = io.ReadAll(io.LimitReader(filedata, maxSignatureSize))
if err != nil {
return err
}
@@ -407,7 +406,7 @@ func verifyUpgrade(archiveName, tempName string, sig []byte) error {
func writeBinary(dir string, inFile io.Reader) (filename string, err error) {
// Write the binary to a temporary file.
- outFile, err := ioutil.TempFile(dir, "syncthing")
+ outFile, err := os.CreateTemp(dir, "syncthing")
if err != nil {
return "", err
}
diff --git a/lib/upnp/upnp.go b/lib/upnp/upnp.go
index 72fc22fedc9..9cf3b5afb42 100644
--- a/lib/upnp/upnp.go
+++ b/lib/upnp/upnp.go
@@ -38,7 +38,7 @@ import (
"context"
"encoding/xml"
"fmt"
- "io/ioutil"
+ "io"
"net"
"net/http"
"net/url"
@@ -467,7 +467,7 @@ func soapRequest(ctx context.Context, url, service, function, message string) ([
return resp, err
}
- resp, _ = ioutil.ReadAll(r.Body)
+ resp, _ = io.ReadAll(r.Body)
l.Debugf("SOAP Response: %s\n\n%s\n\n", r.Status, resp)
r.Body.Close()
diff --git a/lib/versioner/external_test.go b/lib/versioner/external_test.go
index 5979d5e7659..e4283fa3e97 100644
--- a/lib/versioner/external_test.go
+++ b/lib/versioner/external_test.go
@@ -7,7 +7,6 @@
package versioner
import (
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -85,7 +84,7 @@ func prepForRemoval(t *testing.T, file string) {
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(file, []byte("hello\n"), 0644); err != nil {
+ if err := os.WriteFile(file, []byte("hello\n"), 0644); err != nil {
t.Fatal(err)
}
}
diff --git a/lib/versioner/simple_test.go b/lib/versioner/simple_test.go
index 21949213806..12dc2c81ac2 100644
--- a/lib/versioner/simple_test.go
+++ b/lib/versioner/simple_test.go
@@ -7,8 +7,8 @@
package versioner
import (
- "io/ioutil"
"math"
+ "os"
"path/filepath"
"testing"
"time"
@@ -55,7 +55,7 @@ func TestSimpleVersioningVersionCount(t *testing.T) {
t.Skip("Test takes some time, skipping.")
}
- dir, err := ioutil.TempDir("", "")
+ dir, err := os.MkdirTemp("", "")
//defer os.RemoveAll(dir)
if err != nil {
t.Error(err)
diff --git a/lib/versioner/staggered_test.go b/lib/versioner/staggered_test.go
index fbf78d4dfe0..360c29ceb06 100644
--- a/lib/versioner/staggered_test.go
+++ b/lib/versioner/staggered_test.go
@@ -7,7 +7,7 @@
package versioner
import (
- "io/ioutil"
+ "os"
"path/filepath"
"sort"
"strconv"
@@ -133,11 +133,11 @@ func TestCreateVersionPath(t *testing.T) {
)
// Create a test dir and file
- tmpDir, err := ioutil.TempDir("", "")
+ tmpDir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(filepath.Join(tmpDir, archiveFile), []byte("sup"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(tmpDir, archiveFile), []byte("sup"), 0644); err != nil {
t.Fatal(err)
}
diff --git a/lib/versioner/trashcan_test.go b/lib/versioner/trashcan_test.go
index eb59f0954ad..891c64ecf3f 100644
--- a/lib/versioner/trashcan_test.go
+++ b/lib/versioner/trashcan_test.go
@@ -7,7 +7,8 @@
package versioner
import (
- "io/ioutil"
+ "io"
+ "os"
"testing"
"time"
@@ -19,12 +20,12 @@ func TestTrashcanArchiveRestoreSwitcharoo(t *testing.T) {
// This tests that trashcan versioner restoration correctly archives existing file, because trashcan versioner
// files are untagged, archiving existing file to replace with a restored version technically should collide in
// in names.
- tmpDir1, err := ioutil.TempDir("", "")
+ tmpDir1, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
- tmpDir2, err := ioutil.TempDir("", "")
+ tmpDir2, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -105,7 +106,7 @@ func readFile(t *testing.T, filesystem fs.Filesystem, name string) string {
t.Fatal(err)
}
defer fd.Close()
- buf, err := ioutil.ReadAll(fd)
+ buf, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
diff --git a/lib/versioner/versioner_test.go b/lib/versioner/versioner_test.go
index 4dfaaf32225..4ba73c36c6a 100644
--- a/lib/versioner/versioner_test.go
+++ b/lib/versioner/versioner_test.go
@@ -9,7 +9,6 @@ package versioner
import (
"context"
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"testing"
@@ -56,7 +55,7 @@ func TestVersionerCleanOut(t *testing.T) {
oldTime := time.Now().Add(-8 * 24 * time.Hour)
for file, shouldRemove := range testcases {
os.MkdirAll(filepath.Dir(file), 0777)
- if err := ioutil.WriteFile(file, []byte("data"), 0644); err != nil {
+ if err := os.WriteFile(file, []byte("data"), 0644); err != nil {
t.Fatal(err)
}
if shouldRemove {
diff --git a/lib/weakhash/weakhash_test.go b/lib/weakhash/weakhash_test.go
index 0d9ca91c295..78c2d3906dc 100644
--- a/lib/weakhash/weakhash_test.go
+++ b/lib/weakhash/weakhash_test.go
@@ -13,7 +13,6 @@ import (
"bytes"
"context"
"io"
- "io/ioutil"
"os"
"reflect"
"testing"
@@ -22,7 +21,7 @@ import (
var payload = []byte("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")
func TestFinder(t *testing.T) {
- f, err := ioutil.TempFile("", "")
+ f, err := os.CreateTemp("", "")
if err != nil {
t.Error(err)
}
diff --git a/script/authors.go b/script/authors.go
index 3068aebd56b..37fa76bbedc 100644
--- a/script/authors.go
+++ b/script/authors.go
@@ -15,7 +15,7 @@ package main
import (
"bytes"
"fmt"
- "io/ioutil"
+ "io"
"log"
"math"
"os"
@@ -108,7 +108,7 @@ func main() {
bs := readAll(htmlFile)
bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n "))
- if err := ioutil.WriteFile(htmlFile, bs, 0644); err != nil {
+ if err := os.WriteFile(htmlFile, bs, 0644); err != nil {
log.Fatal(err)
}
@@ -173,7 +173,7 @@ func readAll(path string) []byte {
}
defer fd.Close()
- bs, err := ioutil.ReadAll(fd)
+ bs, err := io.ReadAll(fd)
if err != nil {
log.Fatal(err)
}
diff --git a/script/commit-msg.go b/script/commit-msg.go
index c5e178dea42..fa1a13d167f 100644
--- a/script/commit-msg.go
+++ b/script/commit-msg.go
@@ -12,7 +12,6 @@ package main
import (
"bytes"
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"regexp"
@@ -31,7 +30,7 @@ func main() {
os.Exit(exitError)
}
- bs, err := ioutil.ReadFile(os.Args[1])
+ bs, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Println("Reading input:", err)
os.Exit(exitError)
diff --git a/script/genassets.go b/script/genassets.go
index 71f965ab941..4d1b636f62d 100644
--- a/script/genassets.go
+++ b/script/genassets.go
@@ -16,7 +16,6 @@ import (
"fmt"
"go/format"
"io"
- "io/ioutil"
"os"
"path/filepath"
"strconv"
@@ -74,7 +73,7 @@ func walkerFor(basePath string) filepath.WalkFunc {
}
if info.Mode().IsRegular() {
- data, err := ioutil.ReadFile(name)
+ data, err := os.ReadFile(name)
if err != nil {
return err
}
diff --git a/script/prune_mocks.go b/script/prune_mocks.go
index ba924102778..ab56c3e08a9 100644
--- a/script/prune_mocks.go
+++ b/script/prune_mocks.go
@@ -12,7 +12,6 @@ package main
import (
"bufio"
"flag"
- "io/ioutil"
"log"
"os"
"os/exec"
@@ -51,7 +50,7 @@ func pruneInterfaceCheck(path string, size int64) error {
}
defer fd.Close()
- tmp, err := ioutil.TempFile(".", "")
+ tmp, err := os.CreateTemp(".", "")
if err != nil {
return err
}
diff --git a/script/transifexdl.go b/script/transifexdl.go
index 321bdab6fc8..abb5291cfc1 100644
--- a/script/transifexdl.go
+++ b/script/transifexdl.go
@@ -12,7 +12,7 @@ package main
import (
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"log"
"net/http"
"os"
@@ -140,7 +140,7 @@ func loadValidLangs() []string {
log.Fatal(err)
}
defer fd.Close()
- bs, err := ioutil.ReadAll(fd)
+ bs, err := io.ReadAll(fd)
if err != nil {
log.Fatal(err)
}
diff --git a/test/conflict_test.go b/test/conflict_test.go
index c163dbb561f..de93fe3542a 100644
--- a/test/conflict_test.go
+++ b/test/conflict_test.go
@@ -11,7 +11,6 @@ package integration
import (
"bytes"
- "io/ioutil"
"log"
"os"
"path/filepath"
@@ -189,7 +188,7 @@ func TestConflictsDefault(t *testing.T) {
if len(files) != 1 {
t.Errorf("Expected 1 conflicted files instead of %d", len(files))
}
- bs, err := ioutil.ReadFile("s1/testfile.txt")
+ bs, err := os.ReadFile("s1/testfile.txt")
if err != nil {
t.Error("reading file:", err)
}
@@ -216,26 +215,26 @@ func TestConflictsInitialMerge(t *testing.T) {
// File 1 is a conflict
- err = ioutil.WriteFile("s1/file1", []byte("hello\n"), 0644)
+ err = os.WriteFile("s1/file1", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile("s2/file1", []byte("goodbye\n"), 0644)
+ err = os.WriteFile("s2/file1", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
// File 2 exists on s1 only
- err = ioutil.WriteFile("s1/file2", []byte("hello\n"), 0644)
+ err = os.WriteFile("s1/file2", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
// File 3 exists on s2 only
- err = ioutil.WriteFile("s2/file3", []byte("goodbye\n"), 0644)
+ err = os.WriteFile("s2/file3", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -315,15 +314,15 @@ func TestConflictsIndexReset(t *testing.T) {
// Three files on s1
- err = ioutil.WriteFile("s1/file1", []byte("hello\n"), 0644)
+ err = os.WriteFile("s1/file1", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile("s1/file2", []byte("hello\n"), 0644)
+ err = os.WriteFile("s1/file2", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile("s2/file3", []byte("hello\n"), 0644)
+ err = os.WriteFile("s2/file3", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -371,7 +370,7 @@ func TestConflictsIndexReset(t *testing.T) {
// locally after we rest the index, unless we have a fix for that.
for i := 0; i < 5; i++ {
- err = ioutil.WriteFile("s2/file2", []byte("hello1\n"), 0644)
+ err = os.WriteFile("s2/file2", []byte("hello1\n"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -393,7 +392,7 @@ func TestConflictsIndexReset(t *testing.T) {
// s1/file1 (remote) changes while receiver is down
- err = ioutil.WriteFile("s1/file1", []byte("goodbye\n"), 0644)
+ err = os.WriteFile("s1/file1", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -406,7 +405,7 @@ func TestConflictsIndexReset(t *testing.T) {
// s2/file2 (local) changes while receiver is down
- err = ioutil.WriteFile("s2/file2", []byte("goodbye\n"), 0644)
+ err = os.WriteFile("s2/file2", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -468,22 +467,22 @@ func TestConflictsSameContent(t *testing.T) {
// Two files on s1
- err = ioutil.WriteFile("s1/file1", []byte("hello\n"), 0644)
+ err = os.WriteFile("s1/file1", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile("s1/file2", []byte("hello\n"), 0644)
+ err = os.WriteFile("s1/file2", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
// Two files on s2, content differs in file1 only, timestamps differ on both.
- err = ioutil.WriteFile("s2/file1", []byte("goodbye\n"), 0644)
+ err = os.WriteFile("s2/file1", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile("s2/file2", []byte("hello\n"), 0644)
+ err = os.WriteFile("s2/file2", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
diff --git a/test/delay_scan_test.go b/test/delay_scan_test.go
index 8b451c9824e..1cb90e2c179 100644
--- a/test/delay_scan_test.go
+++ b/test/delay_scan_test.go
@@ -10,8 +10,8 @@
package integration
import (
- "io/ioutil"
"log"
+ "os"
"sync"
"testing"
"time"
@@ -31,7 +31,7 @@ func TestRescanWithDelay(t *testing.T) {
}
log.Println("Generating .stignore...")
- err = ioutil.WriteFile("s1/.stignore", []byte("some ignore data\n"), 0644)
+ err = os.WriteFile("s1/.stignore", []byte("some ignore data\n"), 0644)
if err != nil {
t.Fatal(err)
}
diff --git a/test/http_test.go b/test/http_test.go
index a00bdb7d2ef..4541c75b472 100644
--- a/test/http_test.go
+++ b/test/http_test.go
@@ -11,8 +11,9 @@ package integration
import (
"bytes"
- "io/ioutil"
+ "io"
"net/http"
+ "os"
"strings"
"testing"
@@ -33,7 +34,7 @@ func TestHTTPGetIndex(t *testing.T) {
if res.StatusCode != 200 {
t.Errorf("Status %d != 200", res.StatusCode)
}
- bs, err := ioutil.ReadAll(res.Body)
+ bs, err := io.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
@@ -57,7 +58,7 @@ func TestHTTPGetIndex(t *testing.T) {
if res.StatusCode != 200 {
t.Errorf("Status %d != 200", res.StatusCode)
}
- bs, err = ioutil.ReadAll(res.Body)
+ bs, err = io.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
@@ -222,7 +223,7 @@ func setupAPIBench() *rc.Process {
panic(err)
}
- err = ioutil.WriteFile("s1/knownfile", []byte("somedatahere"), 0644)
+ err = os.WriteFile("s1/knownfile", []byte("somedatahere"), 0644)
if err != nil {
panic(err)
}
diff --git a/test/ignore_test.go b/test/ignore_test.go
index 5022026c0be..abc2faab419 100644
--- a/test/ignore_test.go
+++ b/test/ignore_test.go
@@ -10,7 +10,6 @@
package integration
import (
- "io/ioutil"
"log"
"os"
"path/filepath"
@@ -67,7 +66,7 @@ func TestIgnores(t *testing.T) {
// Add some of them to an ignore file
- err = ioutil.WriteFile("s1/.stignore",
+ err = os.WriteFile("s1/.stignore",
[]byte("f1*\nf2\nd1*\nd2\ns1*\ns2\n(?i)*.txt"), // [fds][34] only non-ignored items
0644)
if err != nil {
@@ -92,7 +91,7 @@ func TestIgnores(t *testing.T) {
// Change the pattern to include some of the files and dirs previously ignored
time.Sleep(1100 * time.Millisecond)
- err = ioutil.WriteFile("s1/.stignore", []byte("f2\nd2\ns2\n"), 0644)
+ err = os.WriteFile("s1/.stignore", []byte("f2\nd2\ns2\n"), 0644)
// Rescan and verify that we see them
diff --git a/test/override_test.go b/test/override_test.go
index b2fa14f11cd..5851dfbe0db 100644
--- a/test/override_test.go
+++ b/test/override_test.go
@@ -10,7 +10,7 @@
package integration
import (
- "io/ioutil"
+ "io"
"log"
"os"
"strings"
@@ -127,7 +127,7 @@ func TestOverride(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err := ioutil.ReadAll(fd)
+ bs, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -141,7 +141,7 @@ func TestOverride(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err = ioutil.ReadAll(fd)
+ bs, err = io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -295,7 +295,7 @@ func TestOverrideIgnores(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err := ioutil.ReadAll(fd)
+ bs, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -309,7 +309,7 @@ func TestOverrideIgnores(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err = ioutil.ReadAll(fd)
+ bs, err = io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -337,7 +337,7 @@ func TestOverrideIgnores(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err = ioutil.ReadAll(fd)
+ bs, err = io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -388,7 +388,7 @@ func TestOverrideIgnores(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err = ioutil.ReadAll(fd)
+ bs, err = io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
@@ -423,7 +423,7 @@ func TestOverrideIgnores(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- bs, err = ioutil.ReadAll(fd)
+ bs, err = io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}
diff --git a/test/parallell_scan_test.go b/test/parallell_scan_test.go
index e3f35f2b710..13a07d5b639 100644
--- a/test/parallell_scan_test.go
+++ b/test/parallell_scan_test.go
@@ -10,8 +10,8 @@
package integration
import (
- "io/ioutil"
"log"
+ "os"
"sync"
"testing"
"time"
@@ -31,7 +31,7 @@ func TestRescanInParallel(t *testing.T) {
}
log.Println("Generating .stignore...")
- err = ioutil.WriteFile("s1/.stignore", []byte("some ignore data\n"), 0644)
+ err = os.WriteFile("s1/.stignore", []byte("some ignore data\n"), 0644)
if err != nil {
t.Fatal(err)
}
diff --git a/test/reset_test.go b/test/reset_test.go
index 1e781c7e385..35d471571ef 100644
--- a/test/reset_test.go
+++ b/test/reset_test.go
@@ -13,7 +13,6 @@ import (
"bytes"
"fmt"
"io"
- "io/ioutil"
"log"
"os"
"path/filepath"
@@ -147,7 +146,7 @@ func createFiles(t *testing.T) int {
const n = 8
for i := 0; i < n; i++ {
file := fmt.Sprintf("f%d", i)
- if err := ioutil.WriteFile(filepath.Join("s1", file), []byte("data"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join("s1", file), []byte("data"), 0644); err != nil {
t.Fatal(err)
}
}
diff --git a/test/util.go b/test/util.go
index 8f69dacdaa1..99a0f73861a 100644
--- a/test/util.go
+++ b/test/util.go
@@ -14,7 +14,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"log"
"math/rand"
"os"
@@ -206,7 +205,7 @@ func alterFiles(dir string) error {
return err
}
d1 := []byte("I used to be a dir: " + path)
- err := ioutil.WriteFile(path, d1, 0644)
+ err := os.WriteFile(path, d1, 0644)
if err != nil {
return err
}
@@ -551,7 +550,7 @@ func startInstance(t *testing.T, i int) *rc.Process {
}
func symlinksSupported() bool {
- tmp, err := ioutil.TempDir("", "symlink-test")
+ tmp, err := os.MkdirTemp("", "symlink-test")
if err != nil {
return false
}
From 1754c933706792c3945009d87d1e560cead1ade1 Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Mon, 22 Nov 2021 09:38:24 +0100
Subject: [PATCH 017/220] lib/config, lib/ignore: Write Windows line endings
(fixes #7115) (#8052)
---
build.go | 2 +-
lib/config/config_test.go | 35 +++++++++++++++++++++++++++++++
lib/config/wrapper.go | 2 +-
lib/ignore/ignore.go | 3 ++-
lib/ignore/ignore_test.go | 39 +++++++++++++++++++++++++++++++++++
lib/osutil/replacingwriter.go | 14 +++++++++++++
6 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/build.go b/build.go
index 2a480a5464d..c2e10593982 100644
--- a/build.go
+++ b/build.go
@@ -1293,7 +1293,7 @@ func zipFile(out string, files []archiveFile) {
if err != nil {
log.Fatal(err)
}
- bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\n', '\r'}, -1)
+ bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\r', '\n'}, -1)
fh.UncompressedSize = uint32(len(bs))
fh.UncompressedSize64 = uint64(len(bs))
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index ec7db576304..861c5783980 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -594,6 +594,41 @@ func TestNewSaveLoad(t *testing.T) {
}
}
+func TestWindowsLineEndings(t *testing.T) {
+ if runtime.GOOS != "windows" {
+ t.Skip("Windows specific")
+ }
+
+ dir, err := os.MkdirTemp("", "syncthing-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ path := filepath.Join(dir, "config.xml")
+ os.Remove(path)
+ defer os.Remove(path)
+
+ intCfg := New(device1)
+ cfg := wrap(path, intCfg, device1)
+ defer cfg.stop()
+
+ if err := cfg.Save(); err != nil {
+ t.Error(err)
+ }
+
+ bs, err := os.ReadFile(path)
+ if err != nil {
+ t.Error(err)
+ }
+
+ unixLineEndings := bytes.Count(bs, []byte("\n"))
+ windowsLineEndings := bytes.Count(bs, []byte("\r\n"))
+ if unixLineEndings == 0 || windowsLineEndings != unixLineEndings {
+ t.Error("expected there to be a non-zero number of Windows line endings")
+ }
+}
+
func TestPrepare(t *testing.T) {
var cfg Configuration
diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go
index 4d5c8f93ccf..4c52b0ce069 100644
--- a/lib/config/wrapper.go
+++ b/lib/config/wrapper.go
@@ -502,7 +502,7 @@ func (w *wrapper) Save() error {
return err
}
- if err := w.cfg.WriteXML(fd); err != nil {
+ if err := w.cfg.WriteXML(osutil.LineEndingsWriter(fd)); err != nil {
l.Debugln("WriteXML:", err)
fd.Close()
return err
diff --git a/lib/ignore/ignore.go b/lib/ignore/ignore.go
index 9b13c1e6315..243f178d925 100644
--- a/lib/ignore/ignore.go
+++ b/lib/ignore/ignore.go
@@ -595,8 +595,9 @@ func WriteIgnores(filesystem fs.Filesystem, path string, content []string) error
return err
}
+ wr := osutil.LineEndingsWriter(fd)
for _, line := range content {
- fmt.Fprintln(fd, line)
+ fmt.Fprintln(wr, line)
}
if err := fd.Close(); err != nil {
diff --git a/lib/ignore/ignore_test.go b/lib/ignore/ignore_test.go
index 9781cbdfafc..b1f19c5a99d 100644
--- a/lib/ignore/ignore_test.go
+++ b/lib/ignore/ignore_test.go
@@ -1192,3 +1192,42 @@ func TestEmptyPatterns(t *testing.T) {
}
}
}
+
+func TestWindowsLineEndings(t *testing.T) {
+ if runtime.GOOS != "windows" {
+ t.Skip("Windows specific")
+ }
+
+ lines := "foo\nbar\nbaz\n"
+
+ dir, err := os.MkdirTemp("", "syncthing-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ ffs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
+ m := New(ffs)
+ if err := m.Parse(strings.NewReader(lines), ".stignore"); err != nil {
+ t.Fatal(err)
+ }
+ if err := WriteIgnores(ffs, ".stignore", m.Lines()); err != nil {
+ t.Fatal(err)
+ }
+
+ fd, err := ffs.Open(".stignore")
+ if err != nil {
+ t.Fatal(err)
+ }
+ bs, err := io.ReadAll(fd)
+ fd.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ unixLineEndings := bytes.Count(bs, []byte("\n"))
+ windowsLineEndings := bytes.Count(bs, []byte("\r\n"))
+ if unixLineEndings == 0 || windowsLineEndings != unixLineEndings {
+ t.Error("expected there to be a non-zero number of Windows line endings")
+ }
+}
diff --git a/lib/osutil/replacingwriter.go b/lib/osutil/replacingwriter.go
index 71e86fd631d..18be86f3eb1 100644
--- a/lib/osutil/replacingwriter.go
+++ b/lib/osutil/replacingwriter.go
@@ -9,6 +9,7 @@ package osutil
import (
"bytes"
"io"
+ "runtime"
)
type ReplacingWriter struct {
@@ -46,3 +47,16 @@ func (w ReplacingWriter) Write(bs []byte) (int, error) {
return written, err
}
+
+// LineEndingsWriter returns a writer that writes platform-appropriate line
+// endings. (This is a no-op on non-Windows platforms.)
+func LineEndingsWriter(w io.Writer) io.Writer {
+ if runtime.GOOS != "windows" {
+ return w
+ }
+ return &ReplacingWriter{
+ Writer: w,
+ From: '\n',
+ To: []byte{'\r', '\n'},
+ }
+}
From 7161a99b04b1370aeed3e7fb39c8d7f370e4307b Mon Sep 17 00:00:00 2001
From: tomasz1986
Date: Mon, 22 Nov 2021 17:41:43 +0900
Subject: [PATCH 018/220] gui: Allow text wrapping in remove folder/device
modals (#7976)
Remove the embedded CSS that prevents the lines from wrapping, resulting
in device and folder names being hidden on small screens.
---
gui/default/syncthing/device/removeDeviceDialogView.html | 8 +++++---
gui/default/syncthing/folder/removeFolderDialogView.html | 4 ++--
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/gui/default/syncthing/device/removeDeviceDialogView.html b/gui/default/syncthing/device/removeDeviceDialogView.html
index bc9f20292c5..c8197d3ce24 100644
--- a/gui/default/syncthing/device/removeDeviceDialogView.html
+++ b/gui/default/syncthing/device/removeDeviceDialogView.html
@@ -1,9 +1,11 @@
-
- Are you sure you want to remove device {%name%}?
+
+ Are you sure you want to remove device {%name%}?
+
+
+ {%reintroducer%} might reintroduce this device.
- {%reintroducer%} might reintroduce this device.
|
From 6a9716e8a146b55d2580d989c34c8cfbb086b45b Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Wed, 10 Nov 2021 19:56:29 +0100
Subject: [PATCH 023/220] lib/model: Return index from deviceActivity.leastBusy
This way, we don't need a second loop over the Availabilities to remove
the selected item.
---
lib/model/deviceactivity.go | 15 +++++++--------
lib/model/deviceactivity_test.go | 28 ++++++++++++++--------------
lib/model/folder_sendrecv.go | 18 +++++-------------
3 files changed, 26 insertions(+), 35 deletions(-)
diff --git a/lib/model/deviceactivity.go b/lib/model/deviceactivity.go
index 524ba8501a7..4685f020c11 100644
--- a/lib/model/deviceactivity.go
+++ b/lib/model/deviceactivity.go
@@ -26,20 +26,19 @@ func newDeviceActivity() *deviceActivity {
}
}
-func (m *deviceActivity) leastBusy(availability []Availability) (Availability, bool) {
+// Returns the index of the least busy device, or -1 if all are too busy.
+func (m *deviceActivity) leastBusy(availability []Availability) int {
m.mut.Lock()
low := 2<<30 - 1
- found := false
- var selected Availability
- for _, info := range availability {
- if usage := m.act[info.ID]; usage < low {
+ best := -1
+ for i := range availability {
+ if usage := m.act[availability[i].ID]; usage < low {
low = usage
- selected = info
- found = true
+ best = i
}
}
m.mut.Unlock()
- return selected, found
+ return best
}
func (m *deviceActivity) using(availability Availability) {
diff --git a/lib/model/deviceactivity_test.go b/lib/model/deviceactivity_test.go
index 02d9010802c..d61228035d8 100644
--- a/lib/model/deviceactivity_test.go
+++ b/lib/model/deviceactivity_test.go
@@ -19,42 +19,42 @@ func TestDeviceActivity(t *testing.T) {
devices := []Availability{n0, n1, n2}
na := newDeviceActivity()
- if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
+ if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
}
- if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
+ if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should still be n0 (%v) not %v", n0, lb)
}
- lb, _ := na.leastBusy(devices)
- na.using(lb)
- if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
+ lb := na.leastBusy(devices)
+ na.using(devices[lb])
+ if lb := na.leastBusy(devices); lb != 1 {
t.Errorf("Least busy device should be n1 (%v) not %v", n1, lb)
}
- lb, _ = na.leastBusy(devices)
- na.using(lb)
- if lb, ok := na.leastBusy(devices); !ok || lb != n2 {
+ lb = na.leastBusy(devices)
+ na.using(devices[lb])
+ if lb := na.leastBusy(devices); lb != 2 {
t.Errorf("Least busy device should be n2 (%v) not %v", n2, lb)
}
- lb, _ = na.leastBusy(devices)
- na.using(lb)
- if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
+ lb = na.leastBusy(devices)
+ na.using(devices[lb])
+ if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
}
na.done(n1)
- if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
+ if lb := na.leastBusy(devices); lb != 1 {
t.Errorf("Least busy device should be n1 (%v) not %v", n1, lb)
}
na.done(n2)
- if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
+ if lb := na.leastBusy(devices); lb != 1 {
t.Errorf("Least busy device should still be n1 (%v) not %v", n1, lb)
}
na.done(n0)
- if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
+ if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
}
}
diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go
index cb3971470a6..763f7efe786 100644
--- a/lib/model/folder_sendrecv.go
+++ b/lib/model/folder_sendrecv.go
@@ -1532,8 +1532,8 @@ loop:
// Select the least busy device to pull the block from. If we found no
// feasible device at all, fail the block (and in the long run, the
// file).
- selected, found := activity.leastBusy(candidates)
- if !found {
+ found := activity.leastBusy(candidates)
+ if found == -1 {
if lastError != nil {
state.fail(errors.Wrap(lastError, "pull"))
} else {
@@ -1542,7 +1542,9 @@ loop:
break
}
- candidates = removeAvailability(candidates, selected)
+ selected := candidates[found]
+ candidates[found] = candidates[len(candidates)-1]
+ candidates = candidates[:len(candidates)-1]
// Fetch the block, while marking the selected device as in use so that
// leastBusy can select another device when someone else asks.
@@ -1804,16 +1806,6 @@ func (f *sendReceiveFolder) inConflict(current, replacement protocol.Vector) boo
return false
}
-func removeAvailability(availabilities []Availability, availability Availability) []Availability {
- for i := range availabilities {
- if availabilities[i] == availability {
- availabilities[i] = availabilities[len(availabilities)-1]
- return availabilities[:len(availabilities)-1]
- }
- }
- return availabilities
-}
-
func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan chan<- string) error {
if isConflict(name) {
l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")
From c366933416f138305ae3f9eddd9ebfece86f8fba Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Mon, 8 Nov 2021 15:12:57 +0100
Subject: [PATCH 024/220] lib/sync: Make the clock a function pointer
---
lib/sync/sync.go | 26 ++++++++------------------
lib/sync/sync_test.go | 18 +++++++++---------
2 files changed, 17 insertions(+), 27 deletions(-)
diff --git a/lib/sync/sync.go b/lib/sync/sync.go
index e1b401e1639..9d538c037fd 100644
--- a/lib/sync/sync.go
+++ b/lib/sync/sync.go
@@ -19,11 +19,7 @@ import (
"github.com/sasha-s/go-deadlock"
)
-type clock interface {
- Now() time.Time
-}
-
-var defaultClock clock = (*standardClock)(nil)
+var timeNow = time.Now
type Mutex interface {
Lock()
@@ -86,7 +82,7 @@ func (h holder) String() string {
if h.at == "" {
return "not held"
}
- return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, defaultClock.Now().Sub(h.time))
+ return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, timeNow().Sub(h.time))
}
type loggedMutex struct {
@@ -101,7 +97,7 @@ func (m *loggedMutex) Lock() {
func (m *loggedMutex) Unlock() {
currentHolder := m.holder.Load().(holder)
- duration := defaultClock.Now().Sub(currentHolder.time)
+ duration := timeNow().Sub(currentHolder.time)
if duration >= threshold {
l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
}
@@ -125,7 +121,7 @@ type loggedRWMutex struct {
}
func (m *loggedRWMutex) Lock() {
- start := defaultClock.Now()
+ start := timeNow()
atomic.StoreInt32(&m.logUnlockers, 1)
m.RWMutex.Lock()
@@ -153,7 +149,7 @@ func (m *loggedRWMutex) Lock() {
func (m *loggedRWMutex) Unlock() {
currentHolder := m.holder.Load().(holder)
- duration := defaultClock.Now().Sub(currentHolder.time)
+ duration := timeNow().Sub(currentHolder.time)
if duration >= threshold {
l.Debugf("RWMutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
}
@@ -205,9 +201,9 @@ type loggedWaitGroup struct {
}
func (wg *loggedWaitGroup) Wait() {
- start := defaultClock.Now()
+ start := timeNow()
wg.WaitGroup.Wait()
- duration := defaultClock.Now().Sub(start)
+ duration := timeNow().Sub(start)
if duration >= threshold {
l.Debugf("WaitGroup took %v at %s", duration, getHolder())
}
@@ -219,7 +215,7 @@ func getHolder() holder {
return holder{
at: fmt.Sprintf("%s:%d", file, line),
goid: goid(),
- time: defaultClock.Now(),
+ time: timeNow(),
}
}
@@ -300,9 +296,3 @@ func (w *TimeoutCondWaiter) Wait() bool {
func (w *TimeoutCondWaiter) Stop() {
w.timer.Stop()
}
-
-type standardClock struct{}
-
-func (*standardClock) Now() time.Time {
- return time.Now()
-}
diff --git a/lib/sync/sync_test.go b/lib/sync/sync_test.go
index 4b109bdd0b0..54e11a641b2 100644
--- a/lib/sync/sync_test.go
+++ b/lib/sync/sync_test.go
@@ -57,10 +57,10 @@ func TestTypes(t *testing.T) {
}
func TestMutex(t *testing.T) {
- oldClock := defaultClock
+ oldClock := timeNow
clock := newTestClock()
- defaultClock = clock
- defer func() { defaultClock = oldClock }()
+ timeNow = clock.Now
+ defer func() { timeNow = oldClock }()
debug = true
l.SetDebug("sync", true)
@@ -97,10 +97,10 @@ func TestMutex(t *testing.T) {
}
func TestRWMutex(t *testing.T) {
- oldClock := defaultClock
+ oldClock := timeNow
clock := newTestClock()
- defaultClock = clock
- defer func() { defaultClock = oldClock }()
+ timeNow = clock.Now
+ defer func() { timeNow = oldClock }()
debug = true
l.SetDebug("sync", true)
@@ -170,10 +170,10 @@ func TestRWMutex(t *testing.T) {
}
func TestWaitGroup(t *testing.T) {
- oldClock := defaultClock
+ oldClock := timeNow
clock := newTestClock()
- defaultClock = clock
- defer func() { defaultClock = oldClock }()
+ timeNow = clock.Now
+ defer func() { timeNow = oldClock }()
debug = true
l.SetDebug("sync", true)
From ae70046b49ad1aa211be32321fcc87ad8934473e Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Sun, 7 Nov 2021 10:00:23 +0100
Subject: [PATCH 025/220] lib/protocol: Remove unused sorting boilerplate
---
lib/protocol/deviceid.go | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/lib/protocol/deviceid.go b/lib/protocol/deviceid.go
index 7dd6efaf3dd..11ee13c51ca 100644
--- a/lib/protocol/deviceid.go
+++ b/lib/protocol/deviceid.go
@@ -214,18 +214,3 @@ func untypeoify(s string) string {
s = strings.ReplaceAll(s, "8", "B")
return s
}
-
-// DeviceIDs is a sortable slice of DeviceID
-type DeviceIDs []DeviceID
-
-func (l DeviceIDs) Len() int {
- return len(l)
-}
-
-func (l DeviceIDs) Less(a, b int) bool {
- return l[a].Compare(l[b]) == -1
-}
-
-func (l DeviceIDs) Swap(a, b int) {
- l[a], l[b] = l[b], l[a]
-}
From 286a25ae491f2c9f3b73cd4fb0cb3f16cb1c458c Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Sun, 7 Nov 2021 20:05:18 +0100
Subject: [PATCH 026/220] lib/upgrade: Use strings.Reader instead of
bytes.Buffer
---
lib/upgrade/upgrade_supported.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/upgrade/upgrade_supported.go b/lib/upgrade/upgrade_supported.go
index 80312a6facd..74b1ae89935 100644
--- a/lib/upgrade/upgrade_supported.go
+++ b/lib/upgrade/upgrade_supported.go
@@ -391,7 +391,7 @@ func verifyUpgrade(archiveName, tempName string, sig []byte) error {
// multireader. This ensures that it is not only a bonafide syncthing
// binary, but it is also of exactly the platform and version we expect.
- mr := io.MultiReader(bytes.NewBufferString(archiveName+"\n"), fd)
+ mr := io.MultiReader(strings.NewReader(archiveName+"\n"), fd)
err = signature.Verify(SigningKey, sig, mr)
fd.Close()
From e7620e951d35f57f67cbbb79e6535cfb24a8ffe1 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Sun, 7 Nov 2021 09:58:37 +0100
Subject: [PATCH 027/220] cmd/stdiscosrv: use strconv.Itoa
---
cmd/stdiscosrv/apisrv.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/stdiscosrv/apisrv.go b/cmd/stdiscosrv/apisrv.go
index f3fcedb95c9..3c9b5b3abf6 100644
--- a/cmd/stdiscosrv/apisrv.go
+++ b/cmd/stdiscosrv/apisrv.go
@@ -419,7 +419,7 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
// If zero port was specified, use remote port.
if port == "0" && remote.Port > 0 {
- port = fmt.Sprintf("%d", remote.Port)
+ port = strconv.Itoa(remote.Port)
}
}
From eb857dbc45e1d1080544f63a33a8ad0bc1493040 Mon Sep 17 00:00:00 2001
From: greatroar <61184462+greatroar@users.noreply.github.com>
Date: Sat, 27 Nov 2021 12:51:46 +0100
Subject: [PATCH 028/220] lib/osutil: Use x/sys/windows for SetLowPriority
---
lib/osutil/lowprio_windows.go | 34 +++++-----------------------------
1 file changed, 5 insertions(+), 29 deletions(-)
diff --git a/lib/osutil/lowprio_windows.go b/lib/osutil/lowprio_windows.go
index 2402e369edc..902101f867e 100644
--- a/lib/osutil/lowprio_windows.go
+++ b/lib/osutil/lowprio_windows.go
@@ -7,43 +7,19 @@
package osutil
import (
- "syscall"
-
"github.com/pkg/errors"
-)
-
-const (
- // https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-setpriorityclass
- aboveNormalPriorityClass = 0x00008000
- belowNormalPriorityClass = 0x00004000
- highPriorityClass = 0x00000080
- idlePriorityClass = 0x00000040
- normalPriorityClass = 0x00000020
- processModeBackgroundBegin = 0x00100000
- processModeBackgroundEnd = 0x00200000
- realtimePriorityClass = 0x00000100
+ "golang.org/x/sys/windows"
)
// SetLowPriority lowers the process CPU scheduling priority, and possibly
// I/O priority depending on the platform and OS.
func SetLowPriority() error {
- modkernel32 := syscall.NewLazyDLL("kernel32.dll")
- setPriorityClass := modkernel32.NewProc("SetPriorityClass")
-
- if err := setPriorityClass.Find(); err != nil {
- return errors.Wrap(err, "find proc")
- }
-
- handle, err := syscall.GetCurrentProcess()
+ handle, err := windows.GetCurrentProcess()
if err != nil {
- return errors.Wrap(err, "get process handler")
+ return errors.Wrap(err, "get process handle")
}
- defer syscall.CloseHandle(handle)
+ defer windows.CloseHandle(handle)
- res, _, err := setPriorityClass.Call(uintptr(handle), belowNormalPriorityClass)
- if res != 0 {
- // "If the function succeeds, the return value is nonzero."
- return nil
- }
+ err = windows.SetPriorityClass(handle, windows.BELOW_NORMAL_PRIORITY_CLASS)
return errors.Wrap(err, "set priority class") // wraps nil as nil
}
From bf7f82f7b255c80cfb0bf93110b361657ec02f3b Mon Sep 17 00:00:00 2001
From: ignacy123
Date: Thu, 9 Dec 2021 21:18:47 +0100
Subject: [PATCH 029/220] cmd/syncthing/cli: Add command to show pending
devices/folders (fixes #8068) (#8069)
---
cmd/syncthing/cli/pending.go | 29 +++++++++++++++++++++++++++++
cmd/syncthing/cli/show.go | 1 +
2 files changed, 30 insertions(+)
create mode 100644 cmd/syncthing/cli/pending.go
diff --git a/cmd/syncthing/cli/pending.go b/cmd/syncthing/cli/pending.go
new file mode 100644
index 00000000000..362ba1a5974
--- /dev/null
+++ b/cmd/syncthing/cli/pending.go
@@ -0,0 +1,29 @@
+// 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 cli
+
+import (
+ "github.com/urfave/cli"
+)
+
+var pendingCommand = cli.Command{
+ Name: "pending",
+ HideHelp: true,
+ Usage: "Pending subcommand group",
+ Subcommands: []cli.Command{
+ {
+ Name: "devices",
+ Usage: "Show pending devices",
+ Action: expects(0, indexDumpOutput("cluster/pending/devices")),
+ },
+ {
+ Name: "folders",
+ Usage: "Show pending folders",
+ Action: expects(0, indexDumpOutput("cluster/pending/folders")),
+ },
+ },
+}
diff --git a/cmd/syncthing/cli/show.go b/cmd/syncthing/cli/show.go
index 08f79471955..dc739250aa3 100644
--- a/cmd/syncthing/cli/show.go
+++ b/cmd/syncthing/cli/show.go
@@ -35,6 +35,7 @@ var showCommand = cli.Command{
Usage: "Report about connections to other devices",
Action: expects(0, indexDumpOutput("system/connections")),
},
+ pendingCommand,
{
Name: "usage",
Usage: "Show usage report",
From cc39341eb9b6408091e7ede9cdcfc757b18f6398 Mon Sep 17 00:00:00 2001
From: Gahl Saraf
Date: Wed, 22 Dec 2021 21:16:21 +0200
Subject: [PATCH 030/220] lib: Fix panic due to closed event subscriptions on
shutdown (#8079)
---
lib/model/folder_summary.go | 6 +++++-
lib/syncthing/auditservice.go | 6 +++++-
lib/syncthing/verboseservice.go | 6 +++++-
lib/watchaggregator/aggregator.go | 6 ++++--
4 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/lib/model/folder_summary.go b/lib/model/folder_summary.go
index c9d229b50bb..58427b31835 100644
--- a/lib/model/folder_summary.go
+++ b/lib/model/folder_summary.go
@@ -178,7 +178,11 @@ func (c *folderSummaryService) listenForUpdates(ctx context.Context) error {
// This loop needs to be fast so we don't miss too many events.
select {
- case ev := <-sub.C():
+ case ev, ok := <-sub.C():
+ if !ok {
+ <-ctx.Done()
+ return ctx.Err()
+ }
c.processUpdate(ev)
case <-ctx.Done():
return ctx.Err()
diff --git a/lib/syncthing/auditservice.go b/lib/syncthing/auditservice.go
index c56dbaf4e87..273333be0d4 100644
--- a/lib/syncthing/auditservice.go
+++ b/lib/syncthing/auditservice.go
@@ -38,7 +38,11 @@ func (s *auditService) Serve(ctx context.Context) error {
for {
select {
- case ev := <-sub.C():
+ case ev, ok := <-sub.C():
+ if !ok {
+ <-ctx.Done()
+ return ctx.Err()
+ }
enc.Encode(ev)
case <-ctx.Done():
return ctx.Err()
diff --git a/lib/syncthing/verboseservice.go b/lib/syncthing/verboseservice.go
index 7bcb196b79c..b23027df314 100644
--- a/lib/syncthing/verboseservice.go
+++ b/lib/syncthing/verboseservice.go
@@ -31,7 +31,11 @@ func (s *verboseService) Serve(ctx context.Context) error {
defer sub.Unsubscribe()
for {
select {
- case ev := <-sub.C():
+ case ev, ok := <-sub.C():
+ if !ok {
+ <-ctx.Done()
+ return ctx.Err()
+ }
formatted := s.formatEvent(ev)
if formatted != "" {
l.Verboseln(formatted)
diff --git a/lib/watchaggregator/aggregator.go b/lib/watchaggregator/aggregator.go
index 5c176fde8c5..322aa502e42 100644
--- a/lib/watchaggregator/aggregator.go
+++ b/lib/watchaggregator/aggregator.go
@@ -162,8 +162,10 @@ func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg confi
select {
case event := <-in:
a.newEvent(event, inProgress)
- case event := <-inProgressItemSubscription.C():
- updateInProgressSet(event, inProgress)
+ case event, ok := <-inProgressItemSubscription.C():
+ if ok {
+ updateInProgressSet(event, inProgress)
+ }
case <-a.notifyTimer.C:
a.actOnTimer(out)
case interval := <-a.notifyTimerResetChan:
From c00c6b395721e6c193a64bf7ca45e9028c0b8871 Mon Sep 17 00:00:00 2001
From: Simon Frei
Date: Tue, 28 Dec 2021 11:58:23 +0100
Subject: [PATCH 031/220] build: Bump quic-go to 0.24.0
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index cb83c20f20f..b40c10ac008 100644
--- a/go.mod
+++ b/go.mod
@@ -30,7 +30,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/lib/pq v1.10.3
- github.com/lucas-clemente/quic-go v0.23.0
+ github.com/lucas-clemente/quic-go v0.24.0
github.com/maruel/panicparse v1.6.1
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
github.com/minio/sha256-simd v1.0.0
diff --git a/go.sum b/go.sum
index 7c64c098bd0..7d28a9f4f78 100644
--- a/go.sum
+++ b/go.sum
@@ -242,8 +242,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucas-clemente/quic-go v0.22.0/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q=
-github.com/lucas-clemente/quic-go v0.23.0 h1:5vFnKtZ6nHDFsc/F3uuiF4T3y/AXaQdxjUqiVw26GZE=
-github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
+github.com/lucas-clemente/quic-go v0.24.0 h1:ToR7SIIEdrgOhgVTHvPgdVRJfgVy+N0wQAagH7L4d5g=
+github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
From 083fa1803a99197cfd9b6d656ad606e73b9eedbf Mon Sep 17 00:00:00 2001
From: Kebin Liu
Date: Wed, 5 Jan 2022 22:17:13 +0800
Subject: [PATCH 032/220] cmd/stdiscosrv: Support deploying behind Caddyserver
(#8092)
---
cmd/stdiscosrv/apisrv.go | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/cmd/stdiscosrv/apisrv.go b/cmd/stdiscosrv/apisrv.go
index 3c9b5b3abf6..ad2b1f4788a 100644
--- a/cmd/stdiscosrv/apisrv.go
+++ b/cmd/stdiscosrv/apisrv.go
@@ -10,8 +10,10 @@ import (
"bytes"
"context"
"crypto/tls"
+ "encoding/base64"
"encoding/json"
"encoding/pem"
+ "errors"
"fmt"
"log"
"math/rand"
@@ -229,10 +231,10 @@ func (s *apiSrv) handleGET(ctx context.Context, w http.ResponseWriter, req *http
func (s *apiSrv) handlePOST(ctx context.Context, remoteAddr *net.TCPAddr, w http.ResponseWriter, req *http.Request) {
reqID := ctx.Value(idKey).(requestID)
- rawCert := certificateBytes(req)
- if rawCert == nil {
+ rawCert, err := certificateBytes(req)
+ if err != nil {
if debug {
- log.Println(reqID, "no certificates")
+ log.Println(reqID, "no certificates:", err)
}
announceRequestsTotal.WithLabelValues("no_certificate").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
@@ -304,9 +306,9 @@ func handlePing(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
}
-func certificateBytes(req *http.Request) []byte {
+func certificateBytes(req *http.Request) ([]byte, error) {
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
- return req.TLS.PeerCertificates[0].Raw
+ return req.TLS.PeerCertificates[0].Raw, nil
}
var bs []byte
@@ -319,7 +321,7 @@ func certificateBytes(req *http.Request) []byte {
hdr, err := url.QueryUnescape(hdr)
if err != nil {
// Decoding failed
- return nil
+ return nil, err
}
bs = []byte(hdr)
@@ -338,6 +340,15 @@ func certificateBytes(req *http.Request) []byte {
}
}
}
+ } else if hdr := req.Header.Get("X-Tls-Client-Cert-Der-Base64"); hdr != "" {
+ // Caddy {tls_client_certificate_der_base64}
+ hdr, err := base64.StdEncoding.DecodeString(hdr)
+ if err != nil {
+ // Decoding failed
+ return nil, err
+ }
+
+ bs = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: hdr})
} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
// Traefik 2 passtlsclientcert
// The certificate is in PEM format with url encoding but without newlines
@@ -346,7 +357,7 @@ func certificateBytes(req *http.Request) []byte {
hdr, err := url.QueryUnescape(hdr)
if err != nil {
// Decoding failed
- return nil
+ return nil, err
}
for i := 64; i < len(hdr); i += 65 {
@@ -359,16 +370,16 @@ func certificateBytes(req *http.Request) []byte {
}
if bs == nil {
- return nil
+ return nil, errors.New("empty certificate header")
}
block, _ := pem.Decode(bs)
if block == nil {
// Decoding failed
- return nil
+ return nil, errors.New("certificate decode result is empty")
}
- return block.Bytes
+ return block.Bytes, nil
}
// fixupAddresses checks the list of addresses, removing invalid ones and
From 0f93e76e80d067f4afcb33d61c372bffae0a4eb6 Mon Sep 17 00:00:00 2001
From: tomasz1986
Date: Thu, 6 Jan 2022 12:46:15 +0100
Subject: [PATCH 033/220] gui: Use indexOf instead of startsWith for IE11
compatibility (ref #6940) (#8097)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Use indexOf instead of startsWith to make the now translatable theme
names appear correctly in IE11. This also prevents console log error
spam in the browser. The same problem was previously reported in #6940.
Signed-off-by: Tomasz Wilczyński
---
gui/default/syncthing/core/syncthingController.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index fa565b8b8d3..69200735181 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -2808,7 +2808,7 @@ angular.module('syncthing.core')
$scope.themeName = function (theme) {
var translation = $translate.instant("theme-name-" + theme);
- if (translation.startsWith("theme-name-")) {
+ if (translation.indexOf("theme-name-") == 0) {
// Fall back to simple Title Casing on missing translation
translation = theme.toLowerCase().replace(/(?:^|\s)\S/g, function (a) {
return a.toUpperCase();
From 368094e15d9144172ee9bca96cdacc37cd3ec0d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Fri, 7 Jan 2022 11:02:12 +0100
Subject: [PATCH 034/220] cmd/syncthing: Always update config.xml with generate
subcommand (ref #8090) (#8098)
* cmd/syncthing: Remove unnecessary function arguments.
The openGUI() function does not need a device ID to work, and there is
only one caller anyway which uses EmptyDeviceID.
The loadOrDefaultConfig() function is always called with the same
dummy values.
* cmd/syncthing: Avoid misleading info messages from monitor process.
In order to check whether panic reporting is enabled, the monitor
process utilizes the loadOrDefaultConfig() function. In case there is
no config file yet, info messages may be logged during creation if the
config Wrapper, which is discarded immediately after.
Stop using the DefaultConfig() utility function from lib/syncthing and
directly generate a minimal config instead to avoid these.
Add comments to loadOrDefaultConfig() explaining its limited purpose.
* cmd/syncthing/generate: Always write updated config file.
Previously, an existing config file was left untouched unless either
of the --gui-user or --gui-password options was given. Remove that
condition and simplify the checking code.
---
cmd/syncthing/generate/generate.go | 15 ++++-----------
cmd/syncthing/main.go | 19 +++++++++++--------
cmd/syncthing/monitor.go | 4 +---
3 files changed, 16 insertions(+), 22 deletions(-)
diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go
index 8c5bd1323fe..4f5c2fa140b 100644
--- a/cmd/syncthing/generate/generate.go
+++ b/cmd/syncthing/generate/generate.go
@@ -90,20 +90,13 @@ func Generate(confDir, guiUser, guiPassword string, noDefaultFolder bool) error
log.Println("Device ID:", myID)
cfgFile := locations.Get(locations.ConfigFile)
- var cfg config.Wrapper
- if _, err := os.Stat(cfgFile); err == nil {
- if guiUser == "" && guiPassword == "" {
- log.Println("WARNING: Config exists; will not overwrite.")
- return nil
- }
-
- if cfg, _, err = config.Load(cfgFile, myID, events.NoopLogger); err != nil {
- return fmt.Errorf("load config: %w", err)
- }
- } else {
+ cfg, _, err := config.Load(cfgFile, myID, events.NoopLogger)
+ if fs.IsNotExist(err) {
if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder); err != nil {
return fmt.Errorf("create config: %w", err)
}
+ } else if err != nil {
+ return fmt.Errorf("load config: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 3e3604d60a8..07deb9be88f 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -329,7 +329,7 @@ func (options serveOptions) Run() error {
}
if options.BrowserOnly {
- if err := openGUI(protocol.EmptyDeviceID); err != nil {
+ if err := openGUI(); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(svcutil.ExitError.AsInt())
}
@@ -406,8 +406,8 @@ func (options serveOptions) Run() error {
return nil
}
-func openGUI(myID protocol.DeviceID) error {
- cfg, err := loadOrDefaultConfig(myID, events.NoopLogger)
+func openGUI() error {
+ cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
@@ -452,7 +452,7 @@ func (e *errNoUpgrade) Error() string {
}
func checkUpgrade() (upgrade.Release, error) {
- cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
+ cfg, err := loadOrDefaultConfig()
if err != nil {
return upgrade.Release{}, err
}
@@ -471,7 +471,7 @@ func checkUpgrade() (upgrade.Release, error) {
}
func upgradeViaRest() error {
- cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
+ cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
@@ -711,12 +711,15 @@ func setupSignalHandling(app *syncthing.App) {
}()
}
-func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger) (config.Wrapper, error) {
+// loadOrDefaultConfig creates a temporary, minimal configuration wrapper if no file
+// exists. As it disregards some command-line options, that should never be persisted.
+func loadOrDefaultConfig() (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile)
- cfg, _, err := config.Load(cfgFile, myID, evLogger)
+ cfg, _, err := config.Load(cfgFile, protocol.EmptyDeviceID, events.NoopLogger)
if err != nil {
- cfg, err = syncthing.DefaultConfig(cfgFile, myID, evLogger, true)
+ newCfg := config.New(protocol.EmptyDeviceID)
+ return config.Wrap(cfgFile, newCfg, protocol.EmptyDeviceID, events.NoopLogger), nil
}
return cfg, err
diff --git a/cmd/syncthing/monitor.go b/cmd/syncthing/monitor.go
index 61f335e2111..b30ece39b55 100644
--- a/cmd/syncthing/monitor.go
+++ b/cmd/syncthing/monitor.go
@@ -20,11 +20,9 @@ import (
"syscall"
"time"
- "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"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"
)
@@ -564,7 +562,7 @@ func childEnv() []string {
// panicUploadMaxWait uploading panics...
func maybeReportPanics() {
// Try to get a config to see if/where panics should be reported.
- cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
+ cfg, err := loadOrDefaultConfig()
if err != nil {
l.Warnln("Couldn't load config; not reporting crash")
return
From 5237337626ce5c28afb9a5d56e5f4fb305a0f73b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Fri, 7 Jan 2022 11:19:17 +0100
Subject: [PATCH 035/220] cmd/syncthing: Add --skip-port-probing (fixes #8090)
(#8099)
* cmd/syncthing: Remove unnecessary function arguments.
The openGUI() function does not need a device ID to work, and there is
only one caller anyway which uses EmptyDeviceID.
The loadOrDefaultConfig() function is always called with the same
dummy values.
* cmd/syncthing: Avoid misleading info messages from monitor process.
In order to check whether panic reporting is enabled, the monitor
process utilizes the loadOrDefaultConfig() function. In case there is
no config file yet, info messages may be logged during creation if the
config Wrapper, which is discarded immediately after.
Stop using the DefaultConfig() utility function from lib/syncthing and
directly generate a minimal config instead to avoid these.
Add comments to loadOrDefaultConfig() explaining its limited purpose.
* cmd/syncthing/generate: Always write updated config file.
Previously, an existing config file was left untouched unless either
of the --gui-user or --gui-password options was given. Remove that
condition and simplify the checking code.
* lib/config: Factor out ProbeFreePorts().
* cmd/syncthing: Add option --skip-port-probing.
Applies to both the "generate" and "serve" subcommands, as well as the
deprecated --generate option, just as the --no-default-folder flag.
---
cmd/syncthing/cmdutil/options_common.go | 1 +
cmd/syncthing/generate/generate.go | 6 +++---
cmd/syncthing/main.go | 4 ++--
lib/config/config.go | 10 ++++------
lib/syncthing/utils.go | 13 ++++++++-----
5 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/cmd/syncthing/cmdutil/options_common.go b/cmd/syncthing/cmdutil/options_common.go
index 34485e6a563..0751892d8b4 100644
--- a/cmd/syncthing/cmdutil/options_common.go
+++ b/cmd/syncthing/cmdutil/options_common.go
@@ -12,4 +12,5 @@ type CommonOptions struct {
ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
+ SkipPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup"`
}
diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go
index 4f5c2fa140b..4df7c3d46d1 100644
--- a/cmd/syncthing/generate/generate.go
+++ b/cmd/syncthing/generate/generate.go
@@ -58,13 +58,13 @@ func (c *CLI) Run() error {
c.GUIPassword = string(password)
}
- if err := Generate(c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder); err != nil {
+ if err := Generate(c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.SkipPortProbing); err != nil {
return fmt.Errorf("Failed to generate config and keys: %w", err)
}
return nil
}
-func Generate(confDir, guiUser, guiPassword string, noDefaultFolder bool) error {
+func Generate(confDir, guiUser, guiPassword string, noDefaultFolder, skipPortProbing bool) error {
dir, err := fs.ExpandTilde(confDir)
if err != nil {
return err
@@ -92,7 +92,7 @@ func Generate(confDir, guiUser, guiPassword string, noDefaultFolder bool) error
cfgFile := locations.Get(locations.ConfigFile)
cfg, _, err := config.Load(cfgFile, myID, events.NoopLogger)
if fs.IsNotExist(err) {
- if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder); err != nil {
+ if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder, skipPortProbing); err != nil {
return fmt.Errorf("create config: %w", err)
}
} else if err != nil {
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 07deb9be88f..98175f80fd4 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -337,7 +337,7 @@ func (options serveOptions) Run() error {
}
if options.GenerateDir != "" {
- if err := generate.Generate(options.GenerateDir, "", "", options.NoDefaultFolder); err != nil {
+ if err := generate.Generate(options.GenerateDir, "", "", options.NoDefaultFolder, options.SkipPortProbing); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(svcutil.ExitError.AsInt())
}
@@ -551,7 +551,7 @@ func syncthingMain(options serveOptions) {
evLogger := events.NewLogger()
earlyService.Add(evLogger)
- cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder)
+ cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder, options.SkipPortProbing)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(svcutil.ExitError.AsInt())
diff --git a/lib/config/config.go b/lib/config/config.go
index 67ea8a02425..302cc760c2b 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -113,18 +113,16 @@ func New(myID protocol.DeviceID) Configuration {
return cfg
}
-func NewWithFreePorts(myID protocol.DeviceID) (Configuration, error) {
- cfg := New(myID)
-
+func (cfg *Configuration) ProbeFreePorts() error {
port, err := getFreePort("127.0.0.1", DefaultGUIPort)
if err != nil {
- return Configuration{}, errors.Wrap(err, "get free port (GUI)")
+ return errors.Wrap(err, "get free port (GUI)")
}
cfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port)
port, err = getFreePort("0.0.0.0", DefaultTCPPort)
if err != nil {
- return Configuration{}, errors.Wrap(err, "get free port (BEP)")
+ return errors.Wrap(err, "get free port (BEP)")
}
if port == DefaultTCPPort {
cfg.Options.RawListenAddresses = []string{"default"}
@@ -136,7 +134,7 @@ func NewWithFreePorts(myID protocol.DeviceID) (Configuration, error) {
}
}
- return cfg, nil
+ return nil
}
type xmlConfiguration struct {
diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go
index 5f59af2b64c..bf4752494e1 100644
--- a/lib/syncthing/utils.go
+++ b/lib/syncthing/utils.go
@@ -59,9 +59,12 @@ func GenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
return tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
}
-func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder bool) (config.Wrapper, error) {
- newCfg, err := config.NewWithFreePorts(myID)
- if err != nil {
+func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder, skipPortProbing bool) (config.Wrapper, error) {
+ newCfg := config.New(myID)
+
+ if skipPortProbing {
+ l.Infoln("Using default network port numbers instead of probing for free ports")
+ } else if err := newCfg.ProbeFreePorts(); err != nil {
return nil, err
}
@@ -84,11 +87,11 @@ func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger,
// creates a default one, without the default folder if noDefaultFolder is ture.
// Otherwise it checks the version, and archives and upgrades the config if
// necessary or returns an error, if the version isn't compatible.
-func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logger, allowNewerConfig, noDefaultFolder bool) (config.Wrapper, error) {
+func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logger, allowNewerConfig, noDefaultFolder, skipPortProbing bool) (config.Wrapper, error) {
myID := protocol.NewDeviceID(cert.Certificate[0])
cfg, originalVersion, err := config.Load(path, myID, evLogger)
if fs.IsNotExist(err) {
- cfg, err = DefaultConfig(path, myID, evLogger, noDefaultFolder)
+ cfg, err = DefaultConfig(path, myID, evLogger, noDefaultFolder, skipPortProbing)
if err != nil {
return nil, errors.Wrap(err, "failed to generate default config")
}
From bc3d306dd73037a85c4180181d9c451a881119b4 Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Fri, 7 Jan 2022 11:50:19 +0100
Subject: [PATCH 036/220] cmd/syncthing: Error capitalization
---
cmd/syncthing/generate/generate.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go
index 4df7c3d46d1..b3df5fe9808 100644
--- a/cmd/syncthing/generate/generate.go
+++ b/cmd/syncthing/generate/generate.go
@@ -53,13 +53,13 @@ func (c *CLI) Run() error {
reader := bufio.NewReader(os.Stdin)
password, _, err := reader.ReadLine()
if err != nil {
- return fmt.Errorf("Failed reading GUI password: %w", err)
+ return fmt.Errorf("failed reading GUI password: %w", err)
}
c.GUIPassword = string(password)
}
if err := Generate(c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.SkipPortProbing); err != nil {
- return fmt.Errorf("Failed to generate config and keys: %w", err)
+ return fmt.Errorf("failed to generate config and keys: %w", err)
}
return nil
}
@@ -129,7 +129,7 @@ func updateGUIAuthentication(guiCfg *config.GUIConfiguration, guiUser, guiPasswo
if guiPassword != "" && guiCfg.Password != guiPassword {
if err := guiCfg.HashAndSetPassword(guiPassword); err != nil {
- return fmt.Errorf("Failed to set GUI authentication password: %w", err)
+ return fmt.Errorf("failed to set GUI authentication password: %w", err)
}
log.Println("Updated GUI authentication password.")
}
From fec476cc80e21fedafba847e73a008e5cafd695a Mon Sep 17 00:00:00 2001
From: tomasz1986
Date: Sat, 8 Jan 2022 16:51:29 +0100
Subject: [PATCH 037/220] gui: Sort language names vertically and don't
truncate on small screens (#8101)
---
gui/default/assets/css/overrides.css | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/gui/default/assets/css/overrides.css b/gui/default/assets/css/overrides.css
index 252bae92c31..aa4a1e7b1e3 100644
--- a/gui/default/assets/css/overrides.css
+++ b/gui/default/assets/css/overrides.css
@@ -166,16 +166,13 @@ table.table-auto td {
display: none;
}
-*[language-select] > .dropdown-menu {
+li[language-select] > .dropdown-menu {
+ column-count: 2;
+ column-gap: 0;
width: 450px;
}
-*[language-select] > .dropdown-menu > li {
- float: left;
- width: 50%;
-}
-
-*[language-select] > .dropdown-menu > li > a {
+li[language-select] > .dropdown-menu > li > a {
overflow: hidden;
text-overflow: ellipsis;
}
@@ -351,17 +348,19 @@ ul.three-columns li, ul.two-columns li {
border-radius: 2px;
}
- *[language-select] {
+ li[language-select] {
position: static !important;
}
- *[language-select] > .dropdown-menu {
+ li[language-select] > .dropdown-menu {
+ column-count: auto;
margin-left: 15px;
margin-right: 15px;
margin-top: -12px !important;
max-width: 450px;
- height: 265px;
overflow-y: scroll;
+ /* height of 5.5 elements + negative margin-top */
+ height: 276px;
}
table.table-condensed td,
From 0cba3154f0f52ed7277e1065a93422a947e812a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Colomb?=
Date: Mon, 10 Jan 2022 10:26:45 +0100
Subject: [PATCH 038/220] lib/model: Remove bogus fields from connections API
endpoint (fixes #8103) (#8104)
* lib/model: Remove bogus fields from connections API endpoint.
Switch the returned data type for the /rest/system/connections element
"total" to use only the Statistics struct. The other fields of the
ConnectionInfo struct are not populated and misleading.
* Lowercase JSON field names.
* lib/model: Get rid of ConnectionInfo.MarshalJSON().
It was missing the StartedAt field from the embedded Statistics
struct. Just lowercasing the JSON attribute names can be done just as
easily with annotations.
* lib/model: Remove bogus startedAt field from totals.
Instead of using the Statistics type with one field empty, just switch
to a free-form map with the three needed fields.
---
lib/model/model.go | 36 ++++++++++--------------------------
lib/protocol/protocol.go | 8 ++++----
2 files changed, 14 insertions(+), 30 deletions(-)
diff --git a/lib/model/model.go b/lib/model/model.go
index e722a90cc19..4bcfb1c1a9f 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -706,26 +706,12 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe
type ConnectionInfo struct {
protocol.Statistics
- Connected bool
- Paused bool
- Address string
- ClientVersion string
- Type string
- Crypto string
-}
-
-func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
- return json.Marshal(map[string]interface{}{
- "at": info.At,
- "inBytesTotal": info.InBytesTotal,
- "outBytesTotal": info.OutBytesTotal,
- "connected": info.Connected,
- "paused": info.Paused,
- "address": info.Address,
- "clientVersion": info.ClientVersion,
- "type": info.Type,
- "crypto": info.Crypto,
- })
+ Connected bool `json:"connected"`
+ Paused bool `json:"paused"`
+ Address string `json:"address"`
+ ClientVersion string `json:"clientVersion"`
+ Type string `json:"type"`
+ Crypto string `json:"crypto"`
}
// NumConnections returns the current number of active connected devices.
@@ -769,12 +755,10 @@ func (m *model) ConnectionStats() map[string]interface{} {
res["connections"] = conns
in, out := protocol.TotalInOut()
- res["total"] = ConnectionInfo{
- Statistics: protocol.Statistics{
- At: time.Now().Truncate(time.Second),
- InBytesTotal: in,
- OutBytesTotal: out,
- },
+ res["total"] = map[string]interface{}{
+ "at": time.Now().Truncate(time.Second),
+ "inBytesTotal": in,
+ "outBytesTotal": out,
}
return res
diff --git a/lib/protocol/protocol.go b/lib/protocol/protocol.go
index a7f9408a067..7afca5a1e22 100644
--- a/lib/protocol/protocol.go
+++ b/lib/protocol/protocol.go
@@ -994,10 +994,10 @@ func (c *rawConnection) pingReceiver() {
}
type Statistics struct {
- At time.Time
- InBytesTotal int64
- OutBytesTotal int64
- StartedAt time.Time
+ At time.Time `json:"at"`
+ InBytesTotal int64 `json:"inBytesTotal"`
+ OutBytesTotal int64 `json:"outBytesTotal"`
+ StartedAt time.Time `json:"startedAt"`
}
func (c *rawConnection) Statistics() Statistics {
From dc0dbed96eab56a087cbdab99b13dc2e36abbe62 Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Mon, 10 Jan 2022 14:28:39 +0100
Subject: [PATCH 039/220] github: Add docs update workflow (#8105)
---
.../workflows/update-docs-translations.yaml | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 .github/workflows/update-docs-translations.yaml
diff --git a/.github/workflows/update-docs-translations.yaml b/.github/workflows/update-docs-translations.yaml
new file mode 100644
index 00000000000..12506203545
--- /dev/null
+++ b/.github/workflows/update-docs-translations.yaml
@@ -0,0 +1,26 @@
+name: Update translations and documentation
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '42 3 * * 1'
+
+jobs:
+
+ update_transifex_docs:
+ runs-on: ubuntu-latest
+ name: Update translations and documentation
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-go@v2
+ with:
+ go-version: ^1.17.6
+ - run: |
+ set -euo pipefail
+ git config --global user.name 'Syncthing Automation'
+ git config --global user.email 'automation@syncthing.net'
+ bash build.sh translate
+ bash build.sh prerelease
+ git push
+ env:
+ TRANSIFEX_USER: ${{ secrets.TRANSIFEX_USER }}
+ TRANSIFEX_PASS: ${{ secrets.TRANSIFEX_PASS }}
From 4fb7e046869b1d395be867b43ccc0ffeaa34e17a Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Mon, 10 Jan 2022 14:45:47 +0100
Subject: [PATCH 040/220] github: Use specific token to override branch
protection
---
.github/workflows/update-docs-translations.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/update-docs-translations.yaml b/.github/workflows/update-docs-translations.yaml
index 12506203545..ff9a0135e64 100644
--- a/.github/workflows/update-docs-translations.yaml
+++ b/.github/workflows/update-docs-translations.yaml
@@ -11,6 +11,8 @@ jobs:
name: Update translations and documentation
steps:
- uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- uses: actions/setup-go@v2
with:
go-version: ^1.17.6
From 3bfd41c48b5b4cc86aa580abb7a956c149be1d34 Mon Sep 17 00:00:00 2001
From: Syncthing Automation
Date: Mon, 10 Jan 2022 13:48:17 +0000
Subject: [PATCH 041/220] gui, man, authors: Update docs, translations, and
contributors
---
gui/default/assets/lang/lang-bg.json | 4 +
gui/default/assets/lang/lang-ca@valencia.json | 4 +
gui/default/assets/lang/lang-cs.json | 4 +
gui/default/assets/lang/lang-da.json | 6 +-
gui/default/assets/lang/lang-de.json | 22 +-
gui/default/assets/lang/lang-el.json | 4 +
gui/default/assets/lang/lang-en-AU.json | 4 +
gui/default/assets/lang/lang-en-GB.json | 4 +
gui/default/assets/lang/lang-en.json | 1 +
gui/default/assets/lang/lang-eo.json | 22 +-
gui/default/assets/lang/lang-es-ES.json | 4 +
gui/default/assets/lang/lang-es.json | 4 +
gui/default/assets/lang/lang-eu.json | 4 +
gui/default/assets/lang/lang-fi.json | 4 +
gui/default/assets/lang/lang-fr.json | 6 +-
gui/default/assets/lang/lang-fy.json | 4 +
gui/default/assets/lang/lang-hu.json | 4 +
gui/default/assets/lang/lang-id.json | 24 +-
gui/default/assets/lang/lang-it.json | 4 +
gui/default/assets/lang/lang-ja.json | 34 +-
gui/default/assets/lang/lang-ko-KR.json | 116 ++---
gui/default/assets/lang/lang-lt.json | 6 +-
gui/default/assets/lang/lang-nb.json | 4 +
gui/default/assets/lang/lang-nl.json | 4 +
gui/default/assets/lang/lang-pl.json | 34 +-
gui/default/assets/lang/lang-pt-BR.json | 4 +
gui/default/assets/lang/lang-pt-PT.json | 4 +
gui/default/assets/lang/lang-ro-RO.json | 4 +
gui/default/assets/lang/lang-ru.json | 46 +-
gui/default/assets/lang/lang-sk.json | 4 +
gui/default/assets/lang/lang-sl.json | 446 ++++++++++++++++++
gui/default/assets/lang/lang-sv.json | 12 +-
gui/default/assets/lang/lang-tr.json | 4 +
gui/default/assets/lang/lang-uk.json | 4 +
gui/default/assets/lang/lang-zh-CN.json | 10 +-
gui/default/assets/lang/lang-zh-HK.json | 4 +
gui/default/assets/lang/lang-zh-TW.json | 4 +
gui/default/assets/lang/prettyprint.js | 2 +-
gui/default/assets/lang/valid-langs.js | 2 +-
.../syncthing/core/aboutModalView.html | 2 +-
man/stdiscosrv.1 | 2 +-
man/strelaysrv.1 | 2 +-
man/syncthing-bep.7 | 2 +-
man/syncthing-config.5 | 208 ++++++--
man/syncthing-device-ids.7 | 2 +-
man/syncthing-event-api.7 | 2 +-
man/syncthing-faq.7 | 49 +-
man/syncthing-globaldisco.7 | 2 +-
man/syncthing-localdisco.7 | 2 +-
man/syncthing-networking.7 | 2 +-
man/syncthing-relay.7 | 2 +-
man/syncthing-rest-api.7 | 169 +++++--
man/syncthing-security.7 | 2 +-
man/syncthing-stignore.5 | 2 +-
man/syncthing-versioning.7 | 2 +-
man/syncthing.1 | 253 ++++++++--
56 files changed, 1307 insertions(+), 279 deletions(-)
create mode 100644 gui/default/assets/lang/lang-sl.json
diff --git a/gui/default/assets/lang/lang-bg.json b/gui/default/assets/lang/lang-bg.json
index fd7e8a1203e..176addd48f2 100644
--- a/gui/default/assets/lang/lang-bg.json
+++ b/gui/default/assets/lang/lang-bg.json
@@ -436,6 +436,10 @@
"full documentation": "пълна документация",
"items": "елемента",
"seconds": "секунди",
+ "theme-name-black": "Черна",
+ "theme-name-dark": "Тъмна",
+ "theme-name-default": "По подразбиране",
+ "theme-name-light": "Светла",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} споделя папката „{{folder}}“.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} споделя папката „{{folder}}“. ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "Поръчителят {{reintroducer}} може отново да предложи това устройство."
diff --git a/gui/default/assets/lang/lang-ca@valencia.json b/gui/default/assets/lang/lang-ca@valencia.json
index 50748878401..657473820f8 100644
--- a/gui/default/assets/lang/lang-ca@valencia.json
+++ b/gui/default/assets/lang/lang-ca@valencia.json
@@ -436,6 +436,10 @@
"full documentation": "Documentació completa",
"items": "Elements",
"seconds": "seconds",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-cs.json b/gui/default/assets/lang/lang-cs.json
index 6854d43d56a..a62d0928647 100644
--- a/gui/default/assets/lang/lang-cs.json
+++ b/gui/default/assets/lang/lang-cs.json
@@ -436,6 +436,10 @@
"full documentation": "úplná dokumentace",
"items": "položky",
"seconds": "sekund",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} může toto zařízení znovu uvést."
diff --git a/gui/default/assets/lang/lang-da.json b/gui/default/assets/lang/lang-da.json
index 6faea6360e1..98224c49f66 100644
--- a/gui/default/assets/lang/lang-da.json
+++ b/gui/default/assets/lang/lang-da.json
@@ -91,7 +91,7 @@
"Disabled periodic scanning and disabled watching for changes": "Deaktiverede periodisk skanning og deaktiverede overvågning af ændringer",
"Disabled periodic scanning and enabled watching for changes": "Deaktiverede periodisk skanning og aktiverede overvågning af ændringer",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Deaktiverede periodisk skanning fra og lykkedes ikke med at opsætte overvågning af ændringer; prøver igen hvert minut:",
- "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).",
+ "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Deaktiverer sammenligning og synkronisering af fil tilladelser. Nyttigt på systemer med ikke-eksisterende eller tilpasset tilladelser (f.eks. FAT, exFAT, Synology, Android).",
"Discard": "Behold ikke",
"Disconnected": "Ikke tilsluttet",
"Disconnected (Unused)": "Ikke tilsluttet (ubrugt)",
@@ -436,6 +436,10 @@
"full documentation": "fuld dokumentation",
"items": "filer",
"seconds": "sekunder",
+ "theme-name-black": "Sort",
+ "theme-name-dark": "Mørk",
+ "theme-name-default": "Standard",
+ "theme-name-light": "Lys",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} vil muligvis genindføre denne enhed."
diff --git a/gui/default/assets/lang/lang-de.json b/gui/default/assets/lang/lang-de.json
index ff6e4188d69..7a2f1064970 100644
--- a/gui/default/assets/lang/lang-de.json
+++ b/gui/default/assets/lang/lang-de.json
@@ -18,7 +18,7 @@
"Advanced": "Erweitert",
"Advanced Configuration": "Erweiterte Konfiguration",
"All Data": "Alle Daten",
- "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alle Ordner, welche mit diesem Gerät geteilt werden, müssen von einem Passwort geschützt werden, sodass die gesendeten Daten ohne Kenntnis des Passworts nicht gelesen werden können. ",
+ "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alle Ordner, welche mit diesem Gerät geteilt werden, müssen von einem Passwort geschützt werden, sodass keine gesendeten Daten ohne Kenntnis des Passworts gelesen werden können. ",
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
"Allowed Networks": "Erlaubte Netzwerke",
"Alphabetic": "Alphabetisch",
@@ -26,12 +26,12 @@
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Das Format des anonymen Nutzungsberichts hat sich geändert. Möchten Sie auf das neue Format umsteigen?",
"Are you sure you want to continue?": "Sind Sie sicher, dass Sie fortfahren möchten?",
- "Are you sure you want to override all remote changes?": "Are you sure you want to override all remote changes?",
+ "Are you sure you want to override all remote changes?": "Sind Sie sicher, dass Sie alle entfernten Änderungen überschreiben möchten?",
"Are you sure you want to permanently delete all these files?": "Sind Sie sicher, dass Sie all diese Dateien dauerhaft löschen möchten?",
"Are you sure you want to remove device {%name%}?": "Sind Sie sicher, dass sie das Gerät {{name}} entfernen möchten?",
"Are you sure you want to remove folder {%label%}?": "Sind Sie sicher, dass sie den Ordner {{label}} entfernen möchten?",
"Are you sure you want to restore {%count%} files?": "Sind Sie sicher, dass Sie {{count}} Dateien wiederherstellen möchten?",
- "Are you sure you want to revert all local changes?": "Are you sure you want to revert all local changes?",
+ "Are you sure you want to revert all local changes?": "Sind Sie sicher, dass Sie alle lokalen Änderungen zurücksetzen möchten?",
"Are you sure you want to upgrade?": "Sind Sie sicher, dass Sie ein Upgrade durchführen möchten?",
"Auto Accept": "Automatische Annahme",
"Automatic Crash Reporting": "Automatische Absturzmeldung",
@@ -98,7 +98,7 @@
"Discovered": "Ermittelt",
"Discovery": "Gerätesuche",
"Discovery Failures": "Gerätesuchfehler",
- "Discovery Status": "Discovery Status",
+ "Discovery Status": "Status der Gerätesuche",
"Dismiss": "Ausblenden",
"Do not add it to the ignore list, so this notification may recur.": "Nicht zur Ignorierliste hinzufügen, diese Benachrichtigung kann erneut auftauchen.",
"Do not restore": "Nicht wiederherstellen",
@@ -126,7 +126,7 @@
"Error": "Fehler",
"External File Versioning": "Externe Dateiversionierung",
"Failed Items": "Fehlgeschlagene Elemente",
- "Failed to load ignore patterns.": "Failed to load ignore patterns.",
+ "Failed to load ignore patterns.": "Fehler beim Laden der Ignoriermuster.",
"Failed to setup, retrying": "Fehler beim Installieren, versuche erneut",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Ein Verbindungsfehler zu IPv6-Servern ist zu erwarten, wenn es keine IPv6-Konnektivität gibt.",
"File Pull Order": "Dateiübertragungsreihenfolge",
@@ -184,8 +184,8 @@
"Latest Change": "Letzte Änderung",
"Learn more": "Mehr erfahren",
"Limit": "Limit",
- "Listener Failures": "Listener Failures",
- "Listener Status": "Listener Status",
+ "Listener Failures": "Fehler bei Listener",
+ "Listener Status": "Status der Listener",
"Listeners": "Zuhörer",
"Loading data...": "Daten werden geladen...",
"Loading...": "Wird geladen...",
@@ -224,7 +224,7 @@
"Out of Sync": "Nicht synchronisiert",
"Out of Sync Items": "Nicht synchronisierte Elemente",
"Outgoing Rate Limit (KiB/s)": "Ausgehendes Datenratelimit (KiB/s)",
- "Override": "Override",
+ "Override": "Überschreiben",
"Override Changes": "Änderungen überschreiben",
"Path": "Pfad",
"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": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
@@ -274,7 +274,7 @@
"Resume": "Fortsetzen",
"Resume All": "Alles fortsetzen",
"Reused": "Erneut benutzt",
- "Revert": "Revert",
+ "Revert": "Zurücksetzen",
"Revert Local Changes": "Lokale Änderungen zurücksetzen",
"Save": "Speichern",
"Scan Time Remaining": "Verbleibende Scanzeit",
@@ -358,7 +358,7 @@
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Die folgenden Methoden werden verwendet, um andere Geräte im Netzwerk aufzufinden und dieses Gerät anzukündigen, damit es von anderen gefunden wird:",
"The following unexpected items were found.": "Die folgenden unerwarteten Elemente wurden gefunden.",
"The interval must be a positive number of seconds.": "Das Intervall muss eine positive Zahl von Sekunden sein.",
- "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Das Intervall, in Sekunden, zwischen den Bereinigungen im Versionsverzeichnis. 0 um das regelmäßige Bereinigen zu deaktivieren.",
+ "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Das Intervall, in Sekunden, zwischen den Bereinigungen im Versionsverzeichnis. Null um das regelmäßige Bereinigen zu deaktivieren.",
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen) (0 um alte Versionen für immer zu behalten).",
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
@@ -402,7 +402,7 @@
"Usage reporting is always enabled for candidate releases.": "Nutzungsbericht ist für Veröffentlichungskandidaten immer aktiviert.",
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche verwenden",
"Use notifications from the filesystem to detect changed items.": "Benachrichtigungen des Dateisystems nutzen, um Änderungen zu erkennen.",
- "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Benutzername und Passwort für die Benutzeroberfläche sind nicht gesetzt. Setzen Sie diese zum Schutz ihrer Daten. ",
+ "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Benutzername und Passwort für die Benutzeroberfläche sind nicht gesetzt. Bitte richten Sie diese zur Absicherung ein.",
"Version": "Version",
"Versions": "Versionen",
"Versions Path": "Versionierungspfad",
diff --git a/gui/default/assets/lang/lang-el.json b/gui/default/assets/lang/lang-el.json
index a03d6f88abe..4b000e6577a 100644
--- a/gui/default/assets/lang/lang-el.json
+++ b/gui/default/assets/lang/lang-el.json
@@ -436,6 +436,10 @@
"full documentation": "πλήρης τεκμηρίωση",
"items": "εγγραφές",
"seconds": "δευτερόλεπτα",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}».",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "Η συσκευή {{device}} επιθυμεί να διαμοιράσει τον φάκελο \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-en-AU.json b/gui/default/assets/lang/lang-en-AU.json
index 556ba53e3e2..45b9c3dd2b7 100644
--- a/gui/default/assets/lang/lang-en-AU.json
+++ b/gui/default/assets/lang/lang-en-AU.json
@@ -436,6 +436,10 @@
"full documentation": "full documentation",
"items": "items",
"seconds": "seconds",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-en-GB.json b/gui/default/assets/lang/lang-en-GB.json
index 124c54611c0..460a0572b1e 100644
--- a/gui/default/assets/lang/lang-en-GB.json
+++ b/gui/default/assets/lang/lang-en-GB.json
@@ -436,6 +436,10 @@
"full documentation": "full documentation",
"items": "items",
"seconds": "seconds",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-en.json b/gui/default/assets/lang/lang-en.json
index 11e0a3ccc27..43842ec9640 100644
--- a/gui/default/assets/lang/lang-en.json
+++ b/gui/default/assets/lang/lang-en.json
@@ -22,6 +22,7 @@
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Allowed Networks": "Allowed Networks",
"Alphabetic": "Alphabetic",
+ "Altered by ignoring deletes.": "Altered by ignoring deletes.",
"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.": "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 Reporting",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
diff --git a/gui/default/assets/lang/lang-eo.json b/gui/default/assets/lang/lang-eo.json
index 4a01f67b997..5bfbb712315 100644
--- a/gui/default/assets/lang/lang-eo.json
+++ b/gui/default/assets/lang/lang-eo.json
@@ -18,20 +18,20 @@
"Advanced": "Altnivela",
"Advanced Configuration": "Altnivela Agordo",
"All Data": "Ĉiuj Datumoj",
- "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.": "Ĉiuj dosierujoj, kiuj estas dividitaj kun ĉi tiu aparato devas esti protektitaj per pasvorto, tiel ĉiuj senditaj datumoj estas nelegeblaj sen la pasvorto.",
"Allow Anonymous Usage Reporting?": "Permesi Anoniman Raporton de Uzado?",
"Allowed Networks": "Permesitaj Retoj",
"Alphabetic": "Alfabeta",
"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.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la komunigita dosierujo. Se la vojo al la apliko elhavas blankoj, ĝi devas esti inter citiloj.",
"Anonymous Usage Reporting": "Anonima Raporto de Uzado",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formato de anonima raporto de uzado ŝanĝis. Ĉu vi ŝatus transiri al la nova formato?",
- "Are you sure you want to continue?": "Are you sure you want to continue?",
- "Are you sure you want to override all remote changes?": "Are you sure you want to override all remote changes?",
- "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 continue?": "Ĉu vi certas, ke vi volas daŭrigi?",
+ "Are you sure you want to override all remote changes?": "Ĉu vi certas, ke vi volas transpasi ĉiujn forajn ŝanĝojn?",
+ "Are you sure you want to permanently delete all these files?": "Ĉu vi certas, ke vi volas porĉiame forigi ĉiujn ĉi tiujn dosierojn?",
"Are you sure you want to remove device {%name%}?": "Ĉu vi certas, ke vi volas forigi aparaton {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Ĉu vi certas, ke vi volas forigi dosierujon {{label}}?",
"Are you sure you want to restore {%count%} files?": "Ĉu vi certas, ke vi volas restarigi {{count}} dosierojn?",
- "Are you sure you want to revert all local changes?": "Are you sure you want to revert all local changes?",
+ "Are you sure you want to revert all local changes?": "Ĉu vi certas, ke vi volas malfari ĉiujn lokajn ŝanĝojn?",
"Are you sure you want to upgrade?": "Ĉu vi certe volas plinovigi ?",
"Auto Accept": "Akcepti Aŭtomate",
"Automatic Crash Reporting": "Aŭtomata raportado de kraŝoj",
@@ -42,7 +42,7 @@
"Available debug logging facilities:": "Disponeblaj elpurigadaj protokoliloj:",
"Be careful!": "Atentu!",
"Bugs": "Cimoj",
- "Cancel": "Cancel",
+ "Cancel": "Nuligi",
"Changelog": "Ŝanĝoprotokolo",
"Clean out after": "Purigi poste",
"Cleaning Versions": "Cleaning Versions",
@@ -66,9 +66,9 @@
"Currently Shared With Devices": "Nune komunigita kun aparatoj",
"Danger!": "Danĝero!",
"Debugging Facilities": "Elpurigadiloj",
- "Default Configuration": "Default Configuration",
+ "Default Configuration": "Defaŭlta agordo",
"Default Device": "Default Device",
- "Default Folder": "Default Folder",
+ "Default Folder": "Defaŭlta Dosierujo",
"Default Folder Path": "Defaŭlta Dosieruja Vojo",
"Defaults": "Defaults",
"Delete": "Delete",
@@ -164,7 +164,7 @@
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
"Identification": "Identification",
"If untrusted, enter encryption password": "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.": "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.",
+ "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vi volas malhelpi aliajn uzantojn sur ĉi tiu komputilo atingi Syncthing kaj per ĝi viajn dosierojn, konsideru agordi aŭtentokontrolon.",
"Ignore": "Ignoru",
"Ignore Patterns": "Ignorantaj Ŝablonoj",
"Ignore Permissions": "Ignori Permesojn",
@@ -436,6 +436,10 @@
"full documentation": "tuta dokumentado",
"items": "eroj",
"seconds": "seconds",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas komunigi dosierujon \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas komunigi dosierujon \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-es-ES.json b/gui/default/assets/lang/lang-es-ES.json
index c20aabf4d36..e839abdf07f 100644
--- a/gui/default/assets/lang/lang-es-ES.json
+++ b/gui/default/assets/lang/lang-es-ES.json
@@ -436,6 +436,10 @@
"full documentation": "Documentación completa",
"items": "Elementos",
"seconds": "segundos",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} puede reintroducir este equipo."
diff --git a/gui/default/assets/lang/lang-es.json b/gui/default/assets/lang/lang-es.json
index 32395528774..8340e2aadfd 100644
--- a/gui/default/assets/lang/lang-es.json
+++ b/gui/default/assets/lang/lang-es.json
@@ -436,6 +436,10 @@
"full documentation": "Documentación completa",
"items": "Elementos",
"seconds": "segundos",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} puede reintroducir este dispositivo."
diff --git a/gui/default/assets/lang/lang-eu.json b/gui/default/assets/lang/lang-eu.json
index be2495d133e..6908460c1f4 100644
--- a/gui/default/assets/lang/lang-eu.json
+++ b/gui/default/assets/lang/lang-eu.json
@@ -436,6 +436,10 @@
"full documentation": "Dokumentazio osoa",
"items": "Elementuak",
"seconds": "segunduak",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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.",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} -ek gailu hau birsar lezake."
diff --git a/gui/default/assets/lang/lang-fi.json b/gui/default/assets/lang/lang-fi.json
index 5428d2f9a6b..09eb9d2e98a 100644
--- a/gui/default/assets/lang/lang-fi.json
+++ b/gui/default/assets/lang/lang-fi.json
@@ -436,6 +436,10 @@
"full documentation": "täysi dokumentaatio",
"items": "kohteet",
"seconds": "seconds",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} haluaa jakaa kansion \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-fr.json b/gui/default/assets/lang/lang-fr.json
index 4a598449681..a184734776f 100644
--- a/gui/default/assets/lang/lang-fr.json
+++ b/gui/default/assets/lang/lang-fr.json
@@ -100,7 +100,7 @@
"Discovery Failures": "Échecs de découverte",
"Discovery Status": "État de la découverte",
"Dismiss": "Écarter",
- "Do not add it to the ignore list, so this notification may recur.": "Ne pas l'ajouter à la liste permanente à ignorer, pour que cette notification puisse réapparaître.",
+ "Do not add it to the ignore list, so this notification may recur.": "Attendre la disparition de cette demande : évite l'ajout immédiat à la liste noire persistante.",
"Do not restore": "Ne pas restaurer",
"Do not restore all": "Ne pas tout restaurer",
"Do you want to enable watching for changes for all your folders?": "Voulez-vous activer la surveillance des changements sur tous vos partages ?",
@@ -436,6 +436,10 @@
"full documentation": "Documentation complète ici",
"items": "élément(s)",
"seconds": "secondes",
+ "theme-name-black": "Noir",
+ "theme-name-dark": "Sombre",
+ "theme-name-default": "Par défaut (système)",
+ "theme-name-light": "Clair",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} pourrait ré-enrôler cet appareil."
diff --git a/gui/default/assets/lang/lang-fy.json b/gui/default/assets/lang/lang-fy.json
index b8b5b757f96..f1d7f2598b8 100644
--- a/gui/default/assets/lang/lang-fy.json
+++ b/gui/default/assets/lang/lang-fy.json
@@ -436,6 +436,10 @@
"full documentation": "komplete dokumintaasje",
"items": "items",
"seconds": "sekonden",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wol map \"{{folder}}\" diele.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wol map \"{{folderlabel}}\" ({{folder}}) diele.",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} kin dit apparaat opnij yntrodusearje."
diff --git a/gui/default/assets/lang/lang-hu.json b/gui/default/assets/lang/lang-hu.json
index 0bd64c96bb2..e9fa1e04fd2 100644
--- a/gui/default/assets/lang/lang-hu.json
+++ b/gui/default/assets/lang/lang-hu.json
@@ -436,6 +436,10 @@
"full documentation": "teljes dokumentáció",
"items": "elem",
"seconds": "másodperc",
+ "theme-name-black": "Fekete",
+ "theme-name-dark": "Sötét",
+ "theme-name-default": "Alapértelmezett",
+ "theme-name-light": "Világos",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} újra bevezetheti ezt az eszközt."
diff --git a/gui/default/assets/lang/lang-id.json b/gui/default/assets/lang/lang-id.json
index 099a4d426c1..22136311351 100644
--- a/gui/default/assets/lang/lang-id.json
+++ b/gui/default/assets/lang/lang-id.json
@@ -12,14 +12,14 @@
"Add Remote Device": "Tambah Perangkat Jarak Jauh",
"Add devices from the introducer to our device list, for mutually shared folders.": "Tambahkan perangkat dari pengenal ke daftar perangkat kita, untuk folder yang saling terbagi.",
"Add new folder?": "Tambah folder baru?",
- "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.": "Selain itu, interval pindai ulang secara penuh akan ditambah (kali 60, yaitu bawaan baru selama 1 jam). Anda juga bisa mengkonfigurasi secara manual untuk setiap folder setelah memilih Tidak.",
+ "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.": "Selain itu, interval pindai ulang secara penuh akan ditambah (kali 60, yaitu bawaan baru selama 1 jam). Anda juga dapat mengkonfigurasi secara manual untuk setiap folder setelah memilih Tidak.",
"Address": "Alamat",
"Addresses": "Alamat",
"Advanced": "Tingkat Lanjut",
"Advanced Configuration": "Konfigurasi Tingkat Lanjut",
"All Data": "Semua Data",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Semua folder yang dibagi dengan perangkat ini harus dilindungi dengan sandi, sehingga semua data tidak dapat dilihat tanpa sandi.",
- "Allow Anonymous Usage Reporting?": "Aktifkan Laporan Penggunaan Anonim?",
+ "Allow Anonymous Usage Reporting?": "Izinkan Laporan Penggunaan Anonim?",
"Allowed Networks": "Jaringan Terizinkan",
"Alphabetic": "Alfabet",
"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.": "Perintah eksternal menangani pemversian. Ia harus menghapus berkas dari folder yang dibagi. Jika lokasi aplikasi terdapat spasi, itu harus dikutip.",
@@ -100,7 +100,7 @@
"Discovery Failures": "Kegagalan Penemuan",
"Discovery Status": "Status Penemuan",
"Dismiss": "Tolak",
- "Do not add it to the ignore list, so this notification may recur.": "Jangan tambahkan ke daftar pengabaian, maka notifkasi ini mungkin muncul kembali.",
+ "Do not add it to the ignore list, so this notification may recur.": "Jangan tambahkan ke daftar pengabaian, maka notifikasi ini mungkin muncul kembali.",
"Do not restore": "Jangan pulihkan",
"Do not restore all": "Jangan pulihkan semua",
"Do you want to enable watching for changes for all your folders?": "Apakah anda ingin mengaktifkan fitur melihat perubahan untuk semua folder anda?",
@@ -125,7 +125,7 @@
"Enter up to three octal digits.": "Masukkan hingga tiga digit oktal.",
"Error": "Galat",
"External File Versioning": "Pemversian Berkas Eksternal",
- "Failed Items": "Materi yang gagal",
+ "Failed Items": "Berkas yang gagal",
"Failed to load ignore patterns.": "Gagal memuat pola pengabaian.",
"Failed to setup, retrying": "Gagal menyiapkan, mengulang",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Gagal untuk menyambung ke server IPv6 itu disangka apabila tidak ada konektivitas IPv6.",
@@ -172,7 +172,7 @@
"Ignored Folders": "Folder yang Diabaikan",
"Ignored at": "Diabaikan di",
"Incoming Rate Limit (KiB/s)": "Batas Kecepatan Unduh (KiB/s)",
- "Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Konfigurasi salah bisa merusak isi folder dan membuat Syncthing tidak bisa dijalankan.",
+ "Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Konfigurasi salah dapat merusak isi folder dan membuat Syncthing tidak dapat dijalankan.",
"Introduced By": "Dikenalkan Oleh",
"Introducer": "Pengenal",
"Inversion of the given condition (i.e. do not exclude)": "Inversi dari kondisi yang diberikan (yakni jangan dikecualikan)",
@@ -204,7 +204,7 @@
"Minimum Free Disk Space": "Ruang Penyimpanan Kosong Minimum",
"Mod. Device": "Perangkat Pengubah",
"Mod. Time": "Waktu Diubah",
- "Move to top of queue": "Move to top of queue",
+ "Move to top of queue": "Pindah ke daftar tunggu teratas",
"Multi level wildcard (matches multiple directory levels)": "Wildcard multi tingkat (cocok dalam tingkat direktori apapun)",
"Never": "Never",
"New Device": "Perangkat Baru",
@@ -232,8 +232,8 @@
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Lokasi berbagai versi berkas disimpan (tinggalkan kosong untuk menggunakan direktori .stversions bawaan dalam folder).",
"Pause": "Jeda",
"Pause All": "Jeda Semua",
- "Paused": "Telah Berjeda",
- "Paused (Unused)": "Telah Berjeda (Tidak Digunakan)",
+ "Paused": "Terjeda",
+ "Paused (Unused)": "Terjeda (Tidak Digunakan)",
"Pending changes": "Perubahan yang tertunda",
"Periodic scanning at given interval and disabled watching for changes": "Pemindaian periodik pada interval tertentu dan fitur melihat perubahan dinonaktifkan",
"Periodic scanning at given interval and enabled watching for changes": "Pemindaian periodik pada interval tertentu dan fitur melihat perubahan diaktifkan",
@@ -366,7 +366,7 @@
"The number of old versions to keep, per file.": "Jumlah versi lama untuk disimpan, setiap berkas.",
"The number of versions must be a number and cannot be blank.": "Jumlah versi harus berupa angka dan tidak dapat kosong.",
"The path cannot be blank.": "Lokasi tidak dapat kosong.",
- "The rate limit must be a non-negative number (0: no limit)": "Pembatasan kecepatan harus berupa angka positif (0: tidak ada batas)",
+ "The rate limit must be a non-negative number (0: no limit)": "Pembatasan kecepatan harus berupa angka positif (0: tidak terbatas)",
"The rescan interval must be a non-negative number of seconds.": "Interval pemindaian ulang harus berupa angka positif.",
"There are no devices to share this folder with.": "Tidak ada perangkat untuk membagikan folder ini.",
"There are no folders to share with this device.": "Tidak ada folder untuk dibagi dengan perangkat ini.",
@@ -382,7 +382,7 @@
"Type": "Tipe",
"UNIX Permissions": "Izin UNIX",
"Unavailable": "Tidak Tersedia",
- "Unavailable/Disabled by administrator or maintainer": "Tidak Tersedia/Dinonaktifkan oleh administrator atau pengelola",
+ "Unavailable/Disabled by administrator or maintainer": "Tidak tersedia/Dinonaktifkan oleh administrator atau pengelola",
"Undecided (will prompt)": "Belum terpilih (akan mengingatkan)",
"Unexpected Items": "Berkas Tidak Terduga",
"Unexpected items have been found in this folder.": "Berkas tidak terduga telah ditemukan dalam folder ini.",
@@ -436,6 +436,10 @@
"full documentation": "dokumentasi penuh",
"items": "berkas",
"seconds": "detik",
+ "theme-name-black": "Hitam",
+ "theme-name-dark": "Gelap",
+ "theme-name-default": "Bawaan",
+ "theme-name-light": "Terang",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ingin berbagi folder \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ingin berbagi folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} mungkin memperkenalkan ulang perangkat ini."
diff --git a/gui/default/assets/lang/lang-it.json b/gui/default/assets/lang/lang-it.json
index 4ffc202b2b3..f5cde47b6a0 100644
--- a/gui/default/assets/lang/lang-it.json
+++ b/gui/default/assets/lang/lang-it.json
@@ -436,6 +436,10 @@
"full documentation": "documentazione completa",
"items": "elementi",
"seconds": "secondi",
+ "theme-name-black": "Nero",
+ "theme-name-dark": "Scuro",
+ "theme-name-default": "Default",
+ "theme-name-light": "Chiaro",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} potrebbe reintrodurre questo dispositivo."
diff --git a/gui/default/assets/lang/lang-ja.json b/gui/default/assets/lang/lang-ja.json
index b657550cd49..07bc2b62d99 100644
--- a/gui/default/assets/lang/lang-ja.json
+++ b/gui/default/assets/lang/lang-ja.json
@@ -26,23 +26,23 @@
"Anonymous Usage Reporting": "匿名での使用状況レポート",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名での使用状況レポートのフォーマットが変わりました。新形式でのレポートに移行しますか?",
"Are you sure you want to continue?": "続行してもよろしいですか?",
- "Are you sure you want to override all remote changes?": "Are you sure you want to override all remote changes?",
+ "Are you sure you want to override all remote changes?": "リモートでの変更をすべて上書きしてもよろしいですか?",
"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 revert all local changes?": "Are you sure you want to revert all local changes?",
+ "Are you sure you want to revert all local changes?": "ローカルでの変更をすべて取り消してもよろしいですか?",
"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.": "Automatic upgrades are always enabled for candidate releases.",
+ "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:": "Available debug logging facilities:",
"Be careful!": "注意!",
"Bugs": "バグ",
- "Cancel": "Cancel",
+ "Cancel": "キャンセル",
"Changelog": "更新履歴",
"Clean out after": "以下の期間後に完全に削除する",
"Cleaning Versions": "Cleaning Versions",
@@ -146,7 +146,7 @@
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder type \"{{receiveEncrypted}}\" can only be set when adding a new folder.",
"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.": "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.",
+ "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.": "以下のフォルダへの変更の監視を始めるにあたってエラーが発生しました。1分ごとに再試行するため、すぐに回復するかもしれません。エラーが続くようであれば、原因に対処するか、必要に応じて助けを求めましょう。",
"Full Rescan Interval (s)": "フルスキャンの間隔 (秒)",
"GUI": "GUI",
"GUI Authentication Password": "GUI認証パスワード",
@@ -195,7 +195,7 @@
"Local State (Total)": "ローカル状態 (合計)",
"Locally Changed Items": "Locally Changed Items",
"Log": "ログ",
- "Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
+ "Log tailing paused. Scroll to the bottom to continue.": "ログのリアルタイム表示を停止しています。下部までスクロールすると再開されます。",
"Logs": "ログ",
"Major Upgrade": "メジャーアップグレード",
"Mass actions": "Mass actions",
@@ -245,7 +245,7 @@
"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": "Preparing to Sync",
+ "Preparing to Sync": "同期の準備中",
"Preview": "プレビュー",
"Preview Usage Report": "使用状況レポートのプレビュー",
"Quick guide to supported patterns": "サポートされているパターンのクイックガイド",
@@ -275,7 +275,7 @@
"Resume All": "すべて再開",
"Reused": "中断後再利用",
"Revert": "Revert",
- "Revert Local Changes": "Revert Local Changes",
+ "Revert Local Changes": "ローカルでの変更を取り消す",
"Save": "保存",
"Scan Time Remaining": "スキャン残り時間",
"Scanning": "スキャン中",
@@ -299,7 +299,7 @@
"Sharing": "共有",
"Show ID": "IDを表示",
"Show QR": "QRコードを表示",
- "Show detailed discovery status": "Show detailed discovery status",
+ "Show detailed discovery status": "詳細な探索ステータスを表示する",
"Show detailed listener status": "Show detailed listener status",
"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の代わりに表示されます。他のデバイスに対してもデフォルトの名前として通知されます。",
@@ -349,7 +349,7 @@
"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 content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.",
+ "The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "他のデバイス上にあるフォルダーの中身は、このデバイスのものと同じになるように上書きされます。他のデバイスにあっても、ここに表示されていないファイルは削除されます。",
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.",
"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.": "保存間隔は次の通りです。初めの1時間は30秒ごとに古いバージョンを保存します。同様に、初めの1日間は1時間ごと、初めの30日間は1日ごと、その後最大保存日数までは1週間ごとに、古いバージョンを保存します。",
@@ -357,7 +357,7 @@
"The following items were changed locally.": "The following items were changed locally.",
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "The following methods are used to discover other devices on the network and announce this device to be found by others:",
"The following unexpected items were found.": "The following unexpected items were found.",
- "The interval must be a positive number of seconds.": "The interval must be a positive number of seconds.",
+ "The interval must be a positive number of seconds.": "間隔は0秒以下にはできません。",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.",
"The maximum age must be a number and cannot be blank.": "最大保存日数には数値を指定してください。空欄にはできません。",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "古いバージョンを保持する最大日数 (0で無期限)",
@@ -368,7 +368,7 @@
"The path cannot be blank.": "パスを入力してください。",
"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 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": "このデバイス",
@@ -381,8 +381,8 @@
"Trash Can File Versioning": "ゴミ箱によるバージョン管理",
"Type": "タイプ",
"UNIX Permissions": "UNIX パーミッション",
- "Unavailable": "Unavailable",
- "Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
+ "Unavailable": "利用不可",
+ "Unavailable/Disabled by administrator or maintainer": "利用不可、または管理者によって無効化されています",
"Undecided (will prompt)": "未決定(再確認する)",
"Unexpected Items": "Unexpected Items",
"Unexpected items have been found in this folder.": "Unexpected items have been found in this folder.",
@@ -415,7 +415,7 @@
"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.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
+ "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.": "変更の監視は、定期スキャンを行わずにほとんどの変更を検出できます。",
@@ -436,6 +436,10 @@
"full documentation": "詳細なマニュアル",
"items": "項目",
"seconds": "seconds",
+ "theme-name-black": "ブラック",
+ "theme-name-dark": "ダーク",
+ "theme-name-default": "デフォルト",
+ "theme-name-light": "ライト",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} がフォルダー「{{folderlabel}}」 ({{folder}}) を共有するよう求めています。",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-ko-KR.json b/gui/default/assets/lang/lang-ko-KR.json
index 5a6c5717dbb..c64a11ac939 100644
--- a/gui/default/assets/lang/lang-ko-KR.json
+++ b/gui/default/assets/lang/lang-ko-KR.json
@@ -38,20 +38,20 @@
"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.": "이 기기가 기본 경로에서 알리는 폴더를 자동으로 만들거나 공유합니다.",
+ "Automatically create or share folders that this device advertises at the default path.": "이 기기가 통보하는 폴더들을 기본 경로에서 자동으로 생성 또는 공유합니다.",
"Available debug logging facilities:": "사용 가능한 디버그 로깅 기능:",
"Be careful!": "주의하십시오!",
"Bugs": "버그",
"Cancel": "취소",
"Changelog": "바뀐 점",
"Clean out after": "삭제 후",
- "Cleaning Versions": "버전 정리 중",
+ "Cleaning Versions": "버전 정리",
"Cleanup Interval": "정리 간격",
"Click to see discovery failures": "탐지 실패 보기",
"Click to see full identification string and QR code.": "기기 식별자 및 QR 코드 보기",
"Close": "닫기",
"Command": "명령",
- "Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
+ "Comment, when used at the start of a line": "주석 (줄 앞에 사용할 때)",
"Compression": "압축",
"Configured": "설정됨",
"Connected (Unused)": "연결됨 (미사용)",
@@ -92,22 +92,22 @@
"Disabled periodic scanning and enabled watching for changes": "주기적 탐색을 사용 중지하고 변경 사항 감시 하기",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"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": "무시",
+ "Discard": "일시적 무시",
"Disconnected": "연결 끊김",
"Disconnected (Unused)": "연결 끊김 (미사용)",
"Discovered": "탐지됨",
"Discovery": "탐지",
"Discovery Failures": "탐지 실패",
"Discovery Status": "탐지 현황",
- "Dismiss": "무시",
+ "Dismiss": "나중에",
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
"Do not restore": "복구 하지 않기",
"Do not restore all": "모두 복구 하지 않기",
- "Do you want to enable watching for changes for all your folders?": "변경 사항 감시를 당신의 모든 폴더에서 활성화 하는걸 원하시나요?",
+ "Do you want to enable watching for changes for all your folders?": "변경 항목 감시를 모든 폴더에서 활성화하시겠습니까?",
"Documentation": "사용 설명서",
"Download Rate": "수신 속도",
"Downloaded": "수신됨",
- "Downloading": "수신 중",
+ "Downloading": "수신",
"Edit": "편집",
"Edit Device": "기기 편집",
"Edit Device Defaults": "기기 기본 설정 편집",
@@ -121,18 +121,18 @@
"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.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
- "Enter ignore patterns, one per line.": "무시할 패턴을 한 줄에 하나씩 입력하세요.",
+ "Enter ignore patterns, one per line.": "무시할 패턴을 한 줄에 하나씩 입력하십시오.",
"Enter up to three octal digits.": "최대 3자리의 8진수를 입력하세요.",
"Error": "오류",
"External File Versioning": "외부 파일 버전 관리",
"Failed Items": "실패 항목",
- "Failed to load ignore patterns.": "Failed to load ignore patterns.",
+ "Failed to load ignore patterns.": "무시 패턴 불러오기에 실패했습니다.",
"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.": "파일이 Syncthing에 의해서 교체되거나 삭제되면 .stversions 폴더에 있는 날짜가 바뀐 버전으로 이동됩니다.",
+ "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "파일이 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.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Watcher Errors": "파일 시스템 감시 오류",
@@ -143,22 +143,22 @@
"Folder Label": "폴더명",
"Folder Path": "폴더 경로",
"Folder Type": "폴더 유형",
- "Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder type \"{{receiveEncrypted}}\" can only be set when adding a new folder.",
- "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%}\" can only be set when adding a new folder.": "\"{{receiveEncrypted}}\" 폴더 유형은 새 폴더를 추가할 때만 설정할 수 있습니다.",
+ "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.": "\"{{receiveEncrypted}}\" 폴더 유형은 폴더를 추가한 후 변경할 수 없습니다. 폴더를 먼저 삭제하고, 디스크에 있는 데이터를 삭제 또는 해독한 다음 폴더를 다시 추가하십시오.",
"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.": "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",
"GUI Authentication Password": "GUI 인증 비밀번호",
"GUI Authentication User": "GUI 인증 사용자",
- "GUI Authentication: Set User and Password": "GUI 인증: 사용자와 비밀번호를 설정하십시오",
+ "GUI Authentication: Set User and Password": "GUI 인증: 사용자 이름과 비밀번호를 설정하십시오",
"GUI Listen Address": "GUI 수신 주소",
"GUI Theme": "GUI 테마",
"General": "일반",
"Generate": "생성",
"Global Discovery": "글로벌 탐지",
"Global Discovery Servers": "글로벌 탐지 서버",
- "Global State": "글로벌 서버 상태",
+ "Global State": "전체 기기 상태",
"Help": "도움말",
"Home page": "홈페이지",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
@@ -175,7 +175,7 @@
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "잘못된 설정은 폴더의 컨텐츠를 훼손하거나 Syncthing의 오작동을 일으킬 수 있습니다.",
"Introduced By": "Introduced By",
"Introducer": "유도자",
- "Inversion of the given condition (i.e. do not exclude)": "주어진 조건의 반대 (전혀 배제하지 않음)",
+ "Inversion of the given condition (i.e. do not exclude)": "특정한 조건의 반대 (즉 배제하지 않음)",
"Keep Versions": "버전 보관",
"LDAP": "LDAP",
"Largest First": "큰 파일 순",
@@ -189,11 +189,11 @@
"Listeners": "수신자",
"Loading data...": "데이터를 불러오는 중...",
"Loading...": "불러오는 중...",
- "Local Additions": "로컬 변경 항목",
+ "Local Additions": "현재 기기 추가 항목",
"Local Discovery": "로컬 탐지",
- "Local State": "로컬 상태",
- "Local State (Total)": "로컬 상태 (합계)",
- "Locally Changed Items": "로컬 변경 항목",
+ "Local State": "현재 기기 상태",
+ "Local State (Total)": "현재 기기 상태 (합계)",
+ "Locally Changed Items": "현재 기기 변경 항목",
"Log": "기록",
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
"Logs": "기록",
@@ -205,7 +205,7 @@
"Mod. Device": "수정된 기기",
"Mod. Time": "수정된 시간",
"Move to top of queue": "대기열 상단으로 이동",
- "Multi level wildcard (matches multiple directory levels)": "다중 레벨 와일드 카드 (여러 단계의 디렉토리와 일치하는 경우)",
+ "Multi level wildcard (matches multiple directory levels)": "다중 레벨 와일드카드 (여러 단계의 디렉토리에서 적용됨)",
"Never": "사용 안 함",
"New Device": "새 기기",
"New Folder": "새 폴더",
@@ -227,9 +227,9 @@
"Override": "덮어쓰기",
"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 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%}.": "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}}.",
- "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "버전을 보관할 경로 (비워둘 시 공유된 폴더 안의 기본값 .stversions 폴더로 지정됨)",
+ "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "버전을 보관할 경로입니다. 공유된 폴더 안의 기본값 .stversions 폴더로 지정하려면 비워 두십시오.",
"Pause": "일시 중지",
"Pause All": "모두 일시 중지",
"Paused": "일시 중지됨",
@@ -241,11 +241,11 @@
"Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
"Permissions": "권한",
"Please consult the release notes before performing a major upgrade.": "메이저 업데이트를 하기 전에 먼저 릴리즈 노트를 살펴보세요.",
- "Please set a GUI Authentication User and Password in the Settings dialog.": "설정에서 GUI 인증용 User와 암호를 입력해주세요.",
+ "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": "동기화 준비 중",
+ "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": "지원하는 패턴에 대한 빠른 도움말",
@@ -278,8 +278,8 @@
"Revert Local Changes": "본 기기의 변경 항목 되돌리기",
"Save": "저장",
"Scan Time Remaining": "탐색 남은 시간",
- "Scanning": "탐색 중",
- "See external versioning help for supported templated command line parameters.": "지원되는 템플릿 명령 행 매개 변수에 대해서는 외부 버전 도움말을 참조하십시오.",
+ "Scanning": "탐색",
+ "See external versioning help for supported templated command line parameters.": "지원하는 견본 명령 매개 변수에 대해서는 외부 버전 도움말을 참조하십시오.",
"Select All": "모두 선택",
"Select a version": "버전을 선택하십시오.",
"Select additional devices to share this folder with.": "이 폴더를 추가로 공유할 기기를 선택하십시오.",
@@ -287,7 +287,7 @@
"Select latest version": "가장 최신 버전 선택하십시오.",
"Select oldest version": "가장 오래된 버전 선택하십시오.",
"Select the folders to share with this device.": "이 기기와 공유할 폴더를 선택하십시오.",
- "Send & Receive": "송신 & 수신",
+ "Send & Receive": "송수신",
"Send Only": "송신 전용",
"Settings": "설정",
"Share": "공유",
@@ -295,19 +295,19 @@
"Share Folders With Device": "폴더를 공유할 기기",
"Share this folder?": "이 폴더를 공유하시겠습니까?",
"Shared Folders": "공유 폴더",
- "Shared With": "~와 공유",
+ "Shared With": "공유된 기기",
"Sharing": "공유",
"Show ID": "기기 ID 보기",
"Show QR": "QR 코드 보기",
"Show detailed discovery status": "탐지 현황 상세 보기",
"Show detailed listener status": "수신자 현황 상세 보기",
"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.": "기기에 대한 아이디로 표시됩니다. 옵션에 얻은 기본 이름으로 다른 기기에 통보합니다.",
- "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "아이디가 비어 있는 경우 기본 값으로 다른 기기에 업데이트됩니다.",
+ "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": "종료",
"Shutdown Complete": "종료 완료",
"Simple File Versioning": "간단한 파일 버전 관리",
- "Single level wildcard (matches within a directory only)": "단일 레벨 와일드카드 (하나의 디렉토리만 일치하는 경우)",
+ "Single level wildcard (matches within a directory only)": "단일 레벨 와일드카드 (하나의 디렉토리 내에서만 적용됨)",
"Size": "크기",
"Smallest First": "작은 파일 순",
"Some discovery methods could not be established for finding other devices or announcing this device:": "Some discovery methods could not be established for finding other devices or announcing this device:",
@@ -317,18 +317,18 @@
"Stable releases and release candidates": "안정 버전과 출시 후보",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "안정 버전은 약 2주 정도 지연되어 출시됩니다. 그 기간 동안 출시 후보에 대한 테스트를 거칩니다.",
"Stable releases only": "안정 버전만",
- "Staggered File Versioning": "타임스탬프 기준 파일 버전 관리",
+ "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.",
+ "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.": "암호화된 데이터만 보관하여 동기화합니다. 모든 공유된 기기의 폴더가 동일한 비밀번호를 설정하거나 같은 \"{{receiveEncrypted}}\" 폴더 유형이어야 합니다.",
"Support": "지원",
"Support Bundle": "Support Bundle",
"Sync Protocol Listen Addresses": "동기화 프로토콜 수신 주소",
- "Syncing": "동기화 중",
+ "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 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 listening on the following network addresses for connection attempts from other devices:": "Syncthing is listening on the following network addresses for connection attempts from other devices:",
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.",
"Syncthing is restarting.": "Syncthing이 재시작 중입니다.",
@@ -352,17 +352,17 @@
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.",
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.",
"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.": "다음과 같은 간격이 사용됩니다: 첫 한 시간 동안은 버전이 매 30초마다 유지되며, 첫 하루 동안은 매 시간, 첫 한 달 동안은 매 일마다 유지됩니다. 그리고 최대 날짜까지는 버전이 매 주마다 유지됩니다.",
+ "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.": "다음과 같은 간격이 사용됩니다: 첫 한 시간 동안은 30초마다, 첫 하루 동안은 1시간마다, 첫 30일 동안은 1일마다, 그리고 최대 보존 기간까지는 1주일마다 버전이 보관됩니다.",
"The following items could not be synchronized.": "이 항목들은 동기화 할 수 없습니다.",
- "The following items were changed locally.": "다음 로컬 변경 항목이 발견되었습니다.",
+ "The following items were changed locally.": "다음 항목이 현재 기기에서 변경되었습니다.",
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "The following methods are used to discover other devices on the network and announce this device to be found by others:",
"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.": "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.": "버전 폴더를 정리하는 초 단위 간격입니다. 주기적 정리를 해제하려면 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 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.": "설정된 날짜 동안 파일이 휴지통에 보관됩니다. 0은 무제한입니다.",
+ "The number of days to keep files in the trash can. Zero means forever.": "휴지통에서 파일을 보관할 일수입니다. 0은 무제한을 의미합니다.",
"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.": "경로는 비워 둘 수 없습니다.",
@@ -392,24 +392,24 @@
"Unshared Devices": "공유되지 않은 기기",
"Unshared Folders": "공유되지 않은 폴더",
"Untrusted": "신뢰하지 않음",
- "Up to Date": "최신 데이터",
+ "Up to Date": "최신 상태",
"Updated": "업데이트 완료",
"Upgrade": "업데이트",
"Upgrade To {%version%}": "{{version}}으로 업데이트",
"Upgrading": "업데이트 중",
- "Upload Rate": "업로드 속도",
+ "Upload Rate": "송신 속도",
"Uptime": "가동 시간",
"Usage reporting is always enabled for candidate releases.": "출시 후보 버전에서는 사용 보고서가 항상 활성화 됩니다.",
"Use HTTPS for GUI": "GUI에서 HTTPS 프로토콜 사용",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
- "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.",
+ "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": "동기화 대기 중",
+ "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": "경고",
"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}})의 상위 폴더입니다.",
@@ -417,8 +417,8 @@
"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.": "Watching for changes discovers most changes without periodic scanning.",
+ "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를 사용해야 합니다.",
"Yes": "예",
@@ -427,16 +427,20 @@
"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 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.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
+ "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}})를 공유하길 원합니다.",
+ "theme-name-black": "검은색",
+ "theme-name-dark": "어두운 색",
+ "theme-name-default": "기본 색",
+ "theme-name-light": "밝은 색",
+ "{%device%} wants to share folder \"{%folder%}\".": "{{device}}에서 \"{{folder}}\" 폴더를 공유하길 원합니다.",
+ "{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}}에서 \"{{folderlabel}}\"({{folder}}) 폴더를 공유하길 원합니다.",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
}
\ No newline at end of file
diff --git a/gui/default/assets/lang/lang-lt.json b/gui/default/assets/lang/lang-lt.json
index 1c23f876596..2f1e7da50ad 100644
--- a/gui/default/assets/lang/lang-lt.json
+++ b/gui/default/assets/lang/lang-lt.json
@@ -224,7 +224,7 @@
"Out of Sync": "Išsisinchronizavę",
"Out of Sync Items": "Nesutikrinta",
"Outgoing Rate Limit (KiB/s)": "Išsiunčiamo srauto maksimalus greitis (KiB/s)",
- "Override": "Override",
+ "Override": "Nustelbti",
"Override Changes": "Perrašyti pakeitimus",
"Path": "Kelias",
"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": "Kelias iki aplanko šiame kompiuteryje. Bus sukurtas, jei neegzistuoja. Tildės simbolis (~) gali būti naudojamas kaip trumpinys",
@@ -436,6 +436,10 @@
"full documentation": "pilna dokumentacija",
"items": "įrašai",
"seconds": "sek.",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\"",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} nori dalintis aplanku \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-nb.json b/gui/default/assets/lang/lang-nb.json
index 0ec21f37dd6..c337329f98e 100644
--- a/gui/default/assets/lang/lang-nb.json
+++ b/gui/default/assets/lang/lang-nb.json
@@ -436,6 +436,10 @@
"full documentation": "all dokumentasjon",
"items": "elementer",
"seconds": "sekunder",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappa \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker å dele mappa \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-nl.json b/gui/default/assets/lang/lang-nl.json
index 96bba3c2e08..3814bf4d62a 100644
--- a/gui/default/assets/lang/lang-nl.json
+++ b/gui/default/assets/lang/lang-nl.json
@@ -436,6 +436,10 @@
"full documentation": "volledige documentatie",
"items": "items",
"seconds": "seconden",
+ "theme-name-black": "Zwart",
+ "theme-name-dark": "Donker",
+ "theme-name-default": "Standaard",
+ "theme-name-light": "Licht",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil map \"{{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."
diff --git a/gui/default/assets/lang/lang-pl.json b/gui/default/assets/lang/lang-pl.json
index c34f7ab0d15..76c5bf38046 100644
--- a/gui/default/assets/lang/lang-pl.json
+++ b/gui/default/assets/lang/lang-pl.json
@@ -4,8 +4,8 @@
"A new major version may not be compatible with previous versions.": "Nowa duża wersja może nie być kompatybilna z poprzednimi wersjami.",
"API Key": "Klucz API",
"About": "O Syncthing",
- "Action": "Akcja",
- "Actions": "Akcje",
+ "Action": "Działanie",
+ "Actions": "Działania",
"Add": "Dodaj",
"Add Device": "Dodaj urządzenie",
"Add Folder": "Dodaj folder",
@@ -38,7 +38,7 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatycznie aktualizacje pozwalają teraz na wybór pomiędzy wydaniami stabilnymi oraz wydaniami kandydującymi.",
"Automatic upgrades": "Automatyczne aktualizacje",
"Automatic upgrades are always enabled for candidate releases.": "Automatyczne aktualizacje są zawsze włączone dla wydań kandydujących.",
- "Automatically create or share folders that this device advertises at the default path.": "Automatycznie utwórz lub współdziel foldery wysyłane przez to urządzenie w domyślnej ścieżce.",
+ "Automatically create or share folders that this device advertises at the default path.": "Automatycznie współdziel lub utwórz w domyślnej ścieżce foldery wysyłane przez to urządzenie.",
"Available debug logging facilities:": "Dostępne narzędzia logujące do debugowania:",
"Be careful!": "Ostrożnie!",
"Bugs": "Błędy",
@@ -58,7 +58,7 @@
"Connection Error": "Błąd połączenia",
"Connection Type": "Rodzaj połączenia",
"Connections": "Połączenia",
- "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.": "Ciągłe obserwowanie zmian jest już dostępne w Syncthing. Będzie ono wykrywać zmiany na dysku i uruchamiać skanowanie tylko w zmodyfikowanych ścieżkach. Zalety tego są takie, że zmiany rozsyłane są znacznie szybciej oraz że wymagana jest mniejsza liczba pełnych skanowań.",
+ "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.": "Ciągłe obserwowanie zmian jest już dostępne w Syncthingu. Będzie ono wykrywać zmiany na dysku i uruchamiać skanowanie tylko w zmodyfikowanych ścieżkach. Zalety tego są takie, że zmiany rozsyłane są znacznie szybciej oraz że wymagana jest mniejsza liczba pełnych skanowań.",
"Copied from elsewhere": "Skopiowane z innego miejsca ",
"Copied from original": "Skopiowane z oryginału",
"Copyright © 2014-2019 the following Contributors:": "Wszelkie prawa zastrzeżone © 2014-2019 dla współtwórców:",
@@ -164,7 +164,7 @@
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Niemniej jednak, obecne ustawienia wskazują, że możesz nie chcieć włączać tej funkcji. Automatyczne zgłaszanie awarii zostało wyłączone na tym urządzeniu.",
"Identification": "Identyfikator",
"If untrusted, enter encryption password": "Jeżeli folder jest niezaufany, wprowadź szyfrujące hasło",
- "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Jeżeli chcesz zakazać innym użytkownikom tego komputera dostępu do Syncthing, a przez niego do swoich plików, zastanów się nad włączeniem uwierzytelniania.",
+ "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Jeżeli chcesz zakazać innym użytkownikom tego komputera dostępu do Syncthinga, a przez niego do swoich plików, zastanów się nad włączeniem uwierzytelniania.",
"Ignore": "Ignoruj",
"Ignore Patterns": "Wzorce ignorowania",
"Ignore Permissions": "Ignorowanie uprawnień",
@@ -256,7 +256,7 @@
"Recent Changes": "Ostatnie zmiany",
"Reduced by ignore patterns": "Ograniczono przez wzorce ignorowania",
"Release Notes": "Informacje o wersji",
- "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Wydania kandydujące zawierają najnowsze funkcje oraz poprawki błędów. Są one podobne do tradycyjnych codwutygodniowych wydań Syncthing.",
+ "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Wydania kandydujące zawierają najnowsze funkcje oraz poprawki błędów. Są one podobne do tradycyjnych codwutygodniowych wydań Syncthinga.",
"Remote Devices": "Urządzenia zdalne",
"Remote GUI": "Zdalne GUI",
"Remove": "Usuń",
@@ -339,12 +339,12 @@
"Take me back": "Powrót",
"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 dokonane tutaj nie będą obowiązywać, dopóki nadpisywanie jest w użyciu.",
"The Syncthing Authors": "The Syncthing Authors",
- "The Syncthing admin interface is configured to allow remote access without a password.": "Ustawienia interfejsu administracyjnego Syncthing zezwalają na zdalny dostęp bez hasła.",
+ "The Syncthing admin interface is configured to allow remote access without a password.": "Ustawienia interfejsu administracyjnego Syncthinga zezwalają 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 adresem.",
"The cleanup interval cannot be blank.": "Przedział czasowy czyszczenia nie może być pusty.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Ustawienia zostały zapisane, ale nie są jeszcze aktywne. Syncthing musi zostać uruchomiony ponownie, aby aktywować nowe ustawienia.",
"The device ID cannot be blank.": "ID urządzenia nie może być puste.",
- "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 urządzenia do wpisania tutaj można znaleźć w oknie \"Akcje > Pokaż ID\" na innym urządzeniu. Spacje i myślniki są opcjonalne (ignorowane).",
+ "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 urządzenia do wpisania tutaj można znaleźć w oknie \"Działania > Pokaż ID\" na innym urządzeniu. Spacje i myślniki są opcjonalne (ignorowane).",
"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.": "Zaszyfrowane statystyki użycia są wysyłane codziennie. Używane są one do śledzenia popularności systemów, rozmiarów folderów oraz wersji programu. Jeżeli wysyłane statystyki ulegną zmianie, zostaniesz poproszony o ponowne udzielenie zgody w tym oknie.",
"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.": "Wprowadzone ID urządzenia wygląda na niepoprawne. Musi ono zawierać 52 lub 56 znaków składających się z liter i cyfr. Spacje i myślniki są opcjonalne.",
"The folder ID cannot be blank.": "ID folderu nie może być puste.",
@@ -386,7 +386,7 @@
"Undecided (will prompt)": "Jeszcze nie zdecydowałem (przypomnij później)",
"Unexpected Items": "Elementy nieoczekiwane",
"Unexpected items have been found in this folder.": "Znaleziono elementy nieoczekiwane w tym folderze.",
- "Unignore": "Wyłącz ignorowanie",
+ "Unignore": "Przestań ignorować",
"Unknown": "Nieznany",
"Unshared": "Niewspółdzielony",
"Unshared Devices": "Niewspółdzielone urządzenia",
@@ -411,16 +411,16 @@
"Waiting to Scan": "Oczekiwanie na skanowanie",
"Waiting to Sync": "Oczekiwanie na synchronizację",
"Warning": "Uwaga",
- "Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka to nadkatalog istniejącego folderu \"{{otherFolder}}\".",
- "Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ta ścieżka to nadkatalog 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}}\".",
- "Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ten folder to podkatalog istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
- "Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeżeli korzystasz z zewnętrznego obserwatora, takiego jak {{syncthingInotify}}, upewnij się, że ta opcja jest wyłączona.",
+ "Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Uwaga: ta ścieżka to nadkatalog istniejącego folderu \"{{otherFolder}}\".",
+ "Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga: ta ścieżka to nadkatalog 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}}\".",
+ "Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga: ten folder to podkatalog istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
+ "Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: jeżeli korzystasz z zewnętrznego obserwatora, takiego jak {{syncthingInotify}}, upewnij się, że ta opcja jest wyłączona.",
"Watch for Changes": "Obserwuj zmiany",
"Watching for Changes": "Obserwowanie zmian",
"Watching for changes discovers most changes without periodic scanning.": "Obserwowanie wykrywa większość zmian bez potrzeby okresowego skanowania.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Gdy dodajesz nowe urządzenie pamiętaj, że to urządzenie musi zostać dodane także po drugiej stronie.",
- "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.": "Gdy dodajesz nowy folder pamiętaj, że ID folderu używane jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ma znaczenie i musi ono być identyczne na wszystkich urządzeniach.",
+ "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.": "Dodając nowy folder pamiętaj, że ID folderu używane jest do parowania folderów pomiędzy urządzeniami. Wielkość liter ma znaczenie i musi ono być identyczne na wszystkich urządzeniach.",
"Yes": "Tak",
"You can also select one of these nearby devices:": "Możesz również wybrać jedno z pobliskich urządzeń:",
"You can change your choice at any time in the Settings dialog.": "Możesz zmienić swój wybór w dowolnej chwili w oknie Ustawień.",
@@ -436,6 +436,10 @@
"full documentation": "pełna dokumentacja",
"items": "elementy",
"seconds": "sekundy",
+ "theme-name-black": "Czarny",
+ "theme-name-dark": "Ciemny",
+ "theme-name-default": "Domyślny",
+ "theme-name-light": "Jasny",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce współdzielić folder \"{{folder}}\"",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce współdzielić folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} może ponownie wprowadzić to urządzenie."
diff --git a/gui/default/assets/lang/lang-pt-BR.json b/gui/default/assets/lang/lang-pt-BR.json
index 5aea16bad27..24a8ea82114 100644
--- a/gui/default/assets/lang/lang-pt-BR.json
+++ b/gui/default/assets/lang/lang-pt-BR.json
@@ -436,6 +436,10 @@
"full documentation": "documentação completa",
"items": "itens",
"seconds": "segundos",
+ "theme-name-black": "Preto",
+ "theme-name-dark": "Escuro",
+ "theme-name-default": "Padrão",
+ "theme-name-light": "Claro",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} pode reintroduzir este dispositivo."
diff --git a/gui/default/assets/lang/lang-pt-PT.json b/gui/default/assets/lang/lang-pt-PT.json
index 0b785a4a5aa..580e924533f 100644
--- a/gui/default/assets/lang/lang-pt-PT.json
+++ b/gui/default/assets/lang/lang-pt-PT.json
@@ -436,6 +436,10 @@
"full documentation": "documentação completa",
"items": "itens",
"seconds": "segundos",
+ "theme-name-black": "Preto",
+ "theme-name-dark": "Escuro",
+ "theme-name-default": "Predefinido",
+ "theme-name-light": "Claro",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} poderá reintroduzir este dispositivo."
diff --git a/gui/default/assets/lang/lang-ro-RO.json b/gui/default/assets/lang/lang-ro-RO.json
index 8ce9354766a..22f48028323 100644
--- a/gui/default/assets/lang/lang-ro-RO.json
+++ b/gui/default/assets/lang/lang-ro-RO.json
@@ -436,6 +436,10 @@
"full documentation": "toată documentaţia",
"items": "obiecte",
"seconds": "seconds",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{Dispozitivul}} vrea să transmită mapa {{Mapa}}",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-ru.json b/gui/default/assets/lang/lang-ru.json
index b9d25083f77..ff0b4711b56 100644
--- a/gui/default/assets/lang/lang-ru.json
+++ b/gui/default/assets/lang/lang-ru.json
@@ -26,12 +26,12 @@
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимных отчётов изменился. Хотите переключиться на новый формат?",
"Are you sure you want to continue?": "Уверены, что хотите продолжить?",
- "Are you sure you want to override all remote changes?": "Are you sure you want to override all remote changes?",
+ "Are you sure you want to override all remote changes?": "Уверены, что хотите перезаписать все удалённые изменения?",
"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 revert all local changes?": "Are you sure you want to revert all local changes?",
+ "Are you sure you want to revert all local changes?": "Уверены, что хотите отменить все локальные изменения?",
"Are you sure you want to upgrade?": "Уверены, что хотите обновить?",
"Auto Accept": "Автопринятие",
"Automatic Crash Reporting": "Автоматическая отправка отчётов о сбоях",
@@ -48,7 +48,7 @@
"Cleaning Versions": "Очистка Версий",
"Cleanup Interval": "Интервал очистки",
"Click to see discovery failures": "Щёлкните, чтобы посмотреть ошибки",
- "Click to see full identification string and QR code.": "Click to see full identification string and QR code.",
+ "Click to see full identification string and QR code.": "Нажмите, чтобы увидеть полную строку идентификации и QR код.",
"Close": "Закрыть",
"Command": "Команда",
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
@@ -98,9 +98,9 @@
"Discovered": "Обнаружено",
"Discovery": "Обнаружение",
"Discovery Failures": "Ошибки обнаружения",
- "Discovery Status": "Discovery Status",
+ "Discovery Status": "Статус обнаружения",
"Dismiss": "Отклонить",
- "Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
+ "Do not add it to the ignore list, so this notification may recur.": "Не добавлять в список игнорирования, это уведомление может повториться.",
"Do not restore": "Не восстанавливать",
"Do not restore all": "Не восстанавливать все",
"Do you want to enable watching for changes for all your folders?": "Хотите включить слежение за изменениями для всех своих папок?",
@@ -126,7 +126,7 @@
"Error": "Ошибка",
"External File Versioning": "Внешний контроль версий файлов",
"Failed Items": "Сбои",
- "Failed to load ignore patterns.": "Failed to load ignore patterns.",
+ "Failed to load ignore patterns.": "Не удалось загрузить шаблоны игнорирования.",
"Failed to setup, retrying": "Не удалось настроить, пробуем ещё",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Если нет IPv6-соединений, при подключении к IPv6-серверам произойдёт ошибка.",
"File Pull Order": "Порядок получения файлов",
@@ -162,7 +162,7 @@
"Help": "Помощь",
"Home page": "Сайт",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Ваши настройки указывают что вы не хотите, чтобы эта функция была включена. Мы отключили отправку отчетов о сбоях.",
- "Identification": "Identification",
+ "Identification": "Идентификация",
"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": "Игнорировать",
@@ -184,8 +184,8 @@
"Latest Change": "Последнее изменение",
"Learn more": "Узнать больше",
"Limit": "Ограничение",
- "Listener Failures": "Listener Failures",
- "Listener Status": "Listener Status",
+ "Listener Failures": "Ошибки прослушивателя",
+ "Listener Status": "Статус прослушивателя",
"Listeners": "Прослушиватель",
"Loading data...": "Загрузка данных...",
"Loading...": "Загрузка...",
@@ -224,7 +224,7 @@
"Out of Sync": "Нет синхронизации",
"Out of Sync Items": "Несинхронизированные элементы",
"Outgoing Rate Limit (KiB/s)": "Ограничение исходящей скорости (КиБ/с)",
- "Override": "Override",
+ "Override": "Перезаписать",
"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": "Путь к папке на локальном компьютере. Если её не существует, то она будет создана. Тильда (~) может использоваться как сокращение для",
@@ -238,7 +238,7 @@
"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:": "Периодическое сканирование с заданным интервалом, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
- "Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
+ "Permanently add it to the ignore list, suppressing further notifications.": "Добавление в список игнорирования навсегда с подавлением будущих уведомлений.",
"Permissions": "Разрешения",
"Please consult the release notes before performing a major upgrade.": "Перед проведением обновления основной версии ознакомьтесь, пожалуйста, с замечаниями к версии",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Установите имя пользователя и пароль для интерфейса в настройках",
@@ -258,7 +258,7 @@
"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": "Удаленный GUI",
+ "Remote GUI": "Удалённый GUI",
"Remove": "Удалить",
"Remove Device": "Удалить устройство",
"Remove Folder": "Удалить папку",
@@ -274,7 +274,7 @@
"Resume": "Возобновить",
"Resume All": "Возобновить все",
"Reused": "Повторно использовано",
- "Revert": "Revert",
+ "Revert": "Обратить",
"Revert Local Changes": "Отменить изменения на этом компьютере",
"Save": "Сохранить",
"Scan Time Remaining": "Оставшееся время сканирования",
@@ -299,8 +299,8 @@
"Sharing": "Предоставление доступа",
"Show ID": "Показать ID",
"Show QR": "Показать QR-код",
- "Show detailed discovery status": "Show detailed discovery status",
- "Show detailed listener status": "Show detailed listener status",
+ "Show detailed discovery status": "Показать подробный статус обнаружения",
+ "Show detailed listener status": "Показать подробный статус прослушивателя",
"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 устройства в статусе группы. Если поле не заполнено, то будет установлено имя, передаваемое этим устройством.",
@@ -310,9 +310,9 @@
"Single level wildcard (matches within a directory only)": "Одноуровневая маска (поиск совпадений только внутри папки)",
"Size": "Размер",
"Smallest First": "Сначала маленькие",
- "Some discovery methods could not be established for finding other devices or announcing this device:": "Some discovery methods could not be established for finding other devices or announcing this device:",
+ "Some discovery methods could not be established for finding other devices or announcing this device:": "Некоторые методы обнаружения не могут быть использованы для поиска других устройств или анонсирования этого устройства:",
"Some items could not be restored:": "Не удалось восстановить некоторые объекты:",
- "Some listening addresses could not be enabled to accept connections:": "Some listening addresses could not be enabled to accept connections:",
+ "Some listening addresses could not be enabled to accept connections:": "Некоторые адреса для прослушивания не могут быть активированы для приёма соединений:",
"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.": "Стабильные выпуски выходят с задержкой около двух недель. За это время они проходят тестирование в качестве кандидатов в релизы.",
@@ -329,8 +329,8 @@
"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 listening on the following network addresses for connection attempts from other devices:": "Syncthing is listening on the following network addresses for connection attempts from other devices:",
- "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.",
+ "Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing ожидает подключения от других устройств на следующих сетевых адресах:",
+ "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing не ожидает попыток подключения ни на каких адресах. Только исходящие подключения могут работать на этом устройстве.",
"Syncthing is restarting.": "Перезапуск Syncthing.",
"Syncthing is upgrading.": "Обновление Syncthing.",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing теперь поддерживает автоматическую отправку отчетов о сбоях разработчикам. Эта функция включена по умолчанию.",
@@ -355,7 +355,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.": "Используются следующие интервалы: в первый час версия меняется каждые 30 секунд, в первый день - каждый час, первые 30 дней - каждый день, после, до максимального срока - каждую неделю.",
"The following items could not be synchronized.": "Невозможно синхронизировать следующие объекты",
"The following items were changed locally.": "Следующие объекты были изменены локально",
- "The following methods are used to discover other devices on the network and announce this device to be found by others:": "The following methods are used to discover other devices on the network and announce this device to be found by others:",
+ "The following methods are used to discover other devices on the network and announce this device to be found by others:": "Для обнаружения других устройств в сети и анонсирования этого устройства используются следующие методы:",
"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.": "Интервал в секундах для запуска очистки в каталоге версий. Ноль, чтобы отключить периодическую очистку.",
@@ -373,7 +373,7 @@
"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 device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.",
+ "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Это устройство не может автоматически обнаруживать другие устройства или анонсировать свой адрес для обнаружения извне. Только устройства со статически заданными адресами могут подключиться.",
"This is a major version upgrade.": "Это обновление основной версии продукта.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Эта настройка управляет свободным местом, необходимым на домашнем диске (например, для базы индексов).",
"Time": "Время",
@@ -436,6 +436,10 @@
"full documentation": "полная документация",
"items": "элементы",
"seconds": "сек.",
+ "theme-name-black": "Чёрная",
+ "theme-name-dark": "Тёманя",
+ "theme-name-default": "По умолчанию",
+ "theme-name-light": "Светлая",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой «{{folder}}».",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хочет поделиться папкой «{{folderlabel}}» ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} может повторно рекомендовать это устройство."
diff --git a/gui/default/assets/lang/lang-sk.json b/gui/default/assets/lang/lang-sk.json
index da3f17de8b4..43103f71354 100644
--- a/gui/default/assets/lang/lang-sk.json
+++ b/gui/default/assets/lang/lang-sk.json
@@ -436,6 +436,10 @@
"full documentation": "úplná dokumntácia",
"items": "položiek",
"seconds": "sekúnd",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%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}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-sl.json b/gui/default/assets/lang/lang-sl.json
new file mode 100644
index 00000000000..0774c31a937
--- /dev/null
+++ b/gui/default/assets/lang/lang-sl.json
@@ -0,0 +1,446 @@
+{
+ "A device with that ID is already added.": "Naprava z istim ID-jem že obstaja.",
+ "A negative number of days doesn't make sense.": "Negativno število dni nima smisla.",
+ "A new major version may not be compatible with previous versions.": "Nova glavna različica morda ni združljiva s prejšnjimi različicami. ",
+ "API Key": "Ključ API",
+ "About": "O sistemu ...",
+ "Action": "Dejanje",
+ "Actions": "Dejanja",
+ "Add": "Dodaj",
+ "Add Device": "Dodaj napravo",
+ "Add Folder": "Dodaj mapo",
+ "Add Remote Device": "Dodaj oddaljeno napravo",
+ "Add devices from the introducer to our device list, for mutually shared folders.": "Dodajte naprave od uvajalca na naš seznam naprav, za vzajemno deljenje map.",
+ "Add new folder?": "Dodaj novo mapo",
+ "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.": "Poleg tega bo celoten interval ponovnega skeniranja se povečal (60 krat, torej nova privzeta vrednost 1 ure). Ti lahko tudi nastaviš si to ročno za vsako mapo pozneje, če ste prej izbrali Ne.",
+ "Address": "Naslov",
+ "Addresses": "Naslovi",
+ "Advanced": "Napredno",
+ "Advanced Configuration": "Napredna konfiguracija",
+ "All Data": "Vsi podatki",
+ "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Vse deljene mape z to napravo morajo biti zaščitena z geslom, tako, da so vsi poslani podatki neberljivi, brez podanega gesla. ",
+ "Allow Anonymous Usage Reporting?": "Ali naj se dovoli brezimno poročanje o uporabi?",
+ "Allowed Networks": "Dovoljena omrežja",
+ "Alphabetic": "Po abecedi",
+ "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.": "Zunanji ukaz upravlja z različicami. To mora odstraniti datoteko od deljene mape. Če pot do aplikacije vsebuje presledke, jo postavite v dvojne narekovaje.",
+ "Anonymous Usage Reporting": "Brezimno poročanje o uporabi",
+ "Anonymous usage report format has changed. Would you like to move to the new format?": "Format anonimnega poročanja uporabe se je spremenil. Ali želite se premakniti na novi format?",
+ "Are you sure you want to continue?": "Ste prepričani, da želite nadaljevati?",
+ "Are you sure you want to override all remote changes?": "Ali ste prepričani, da želite preglasiti vse oddaljene spremembe?",
+ "Are you sure you want to permanently delete all these files?": "Ste prepričani, da želite trajno izbrisati datoteke?",
+ "Are you sure you want to remove device {%name%}?": "Ste prepričani, da želite odstraniti napravo {{name}}?",
+ "Are you sure you want to remove folder {%label%}?": "Ste prepričani, da želite odstraniti mapo {{label}}?",
+ "Are you sure you want to restore {%count%} files?": "Ste prepričani, da želite obnoviti {{count}} datotek?",
+ "Are you sure you want to revert all local changes?": "Ste prepričani, da želite povrniti vse lokalne spremembe?",
+ "Are you sure you want to upgrade?": "Ali ste prepričani, da želite nadgraditi?",
+ "Auto Accept": "Samodejno sprejmi",
+ "Automatic Crash Reporting": "Avtomatsko poročanje o sesutju",
+ "Automatic upgrade now offers the choice between stable releases and release candidates.": "Samodejna nadgradnja zdaj ponuja izbiro med stabilnimi izdajami in kandidati za izdajo.",
+ "Automatic upgrades": "Avtomatično posodabljanje",
+ "Automatic upgrades are always enabled for candidate releases.": "Samodejne nadgradnje so vedno omogočene za kandidatne izdaje.",
+ "Automatically create or share folders that this device advertises at the default path.": "Samodejno ustvarite ali delite mape, ki jih ta naprava oglašuje na privzeti poti.",
+ "Available debug logging facilities:": "Razpoložljive naprave za beleženje napak:",
+ "Be careful!": "Previdno!",
+ "Bugs": "Hrošči",
+ "Cancel": "Prekliči",
+ "Changelog": "Spremembe",
+ "Clean out after": "Počisti kasneje",
+ "Cleaning Versions": "Različice za očistiti",
+ "Cleanup Interval": "Interval čiščenja",
+ "Click to see discovery failures": "Pritisnite za ogled napak pri odkrivanju",
+ "Click to see full identification string and QR code.": "Pritisnite, če si želite ogledati celoten identifikacijski niz in QR kodo.",
+ "Close": "Zapri",
+ "Command": "Ukaz",
+ "Comment, when used at the start of a line": "Komentar uporabljen na začetku vrstice",
+ "Compression": "Stisnjeno",
+ "Configured": "Nastavljeno",
+ "Connected (Unused)": "Povezano (neuporabljeno)",
+ "Connection Error": "Napaka povezave",
+ "Connection Type": "Tip povezave",
+ "Connections": "Povezave",
+ "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.": "Nenehno spremljanje sprememb je zdaj na voljo v Syncthing-u. To bo zaznalo spremembe na disku in izdalo skeniranje samo na spremenjenih poteh. Prednosti so, da se spremembe hitreje širijo in da je potrebnih manj popolnih pregledov.",
+ "Copied from elsewhere": "Prekopirano od drugod.",
+ "Copied from original": "Kopiranje z originala",
+ "Copyright © 2014-2019 the following Contributors:": "Avtorske pravice © 2014-2019 pripadajo naslednjim sodelavcem:",
+ "Creating ignore patterns, overwriting an existing file at {%path%}.": "Ustvarjanje prezrtih vzorcev, prepisovanje obstoječe datoteke na {{path}}.",
+ "Currently Shared With Devices": "Trenutno deljeno z napravami",
+ "Danger!": "Nevarno!",
+ "Debugging Facilities": "Možnosti za odpravljanje napak",
+ "Default Configuration": "Privzeta konfiguracija",
+ "Default Device": "Privzeta naprava",
+ "Default Folder": "Privzeta mapa",
+ "Default Folder Path": "Privzeta pot do mape",
+ "Defaults": "Privzeti",
+ "Delete": "Izbriši",
+ "Delete Unexpected Items": "Izbrišite nepričakovane predmete",
+ "Deleted": "Izbrisana",
+ "Deselect All": "Prekliči vse",
+ "Deselect devices to stop sharing this folder with.": "Prekliči izbiro naprav, z katerimi ne želiš več deliti mape.",
+ "Deselect folders to stop sharing with this device.": "Prekliči mape, da se ne delijo več z to napravo.",
+ "Device": "Naprava",
+ "Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Naprava \"{{name}}\" {{device}} na ({{address}}) želi vzpostaviti povezavo. Dodaj novo napravo?",
+ "Device ID": "ID Naprave",
+ "Device Identification": "Identifikacija naprave",
+ "Device Name": "Ime naprave",
+ "Device is untrusted, enter encryption password": "Naprava ni zaupljiva, vnesite geslo za šifriranje",
+ "Device rate limits": "Omejitve hitrosti naprave",
+ "Device that last modified the item": "Naprava, ki je nazadnje spremenila predmet",
+ "Devices": "Naprave",
+ "Disable Crash Reporting": "Onemogoči poročanje o zrušitvah",
+ "Disabled": "Onemogočeno",
+ "Disabled periodic scanning and disabled watching for changes": "Onemogočeno občasno skeniranje in onemogočeno spremljanje sprememb",
+ "Disabled periodic scanning and enabled watching for changes": "Onemogočeno občasno skeniranje in omogočeno spremljanje sprememb",
+ "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Onemogočeno periodično skeniranje in neuspešna nastavitev opazovanja sprememb, ponovni poskus vsake 1 minute:",
+ "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Onemogoči primerjavo in sinhronizacijo dovoljenj za datoteke. Uporabno v sistemih z neobstoječimi dovoljenji ali dovoljenji po meri (npr. FAT, exFAT, Synology, Android).",
+ "Discard": "Zavrzi",
+ "Disconnected": "Brez povezave",
+ "Disconnected (Unused)": "Ni povezave (neuporabljeno)",
+ "Discovered": "Odkrito",
+ "Discovery": "Odkritje",
+ "Discovery Failures": "Napake odkritja",
+ "Discovery Status": "Stanje odkritja",
+ "Dismiss": "Opusti",
+ "Do not add it to the ignore list, so this notification may recur.": "Ne dodajte to na seznam prezrtih, tako, da se obvestilo lahko ponovi.",
+ "Do not restore": "Ne obnovi",
+ "Do not restore all": "Ne obnovi vse",
+ "Do you want to enable watching for changes for all your folders?": "Ali želite omogočiti spremljanje sprememb za vse svoje mape?",
+ "Documentation": "Dokumentacija",
+ "Download Rate": "Hitrost prejemanja",
+ "Downloaded": "Prenešeno",
+ "Downloading": "Prenašanje",
+ "Edit": "Uredi",
+ "Edit Device": "Uredi napravo",
+ "Edit Device Defaults": "Uredi privzete nastavitve naprave",
+ "Edit Folder": "Uredi mapo",
+ "Edit Folder Defaults": "Uredi privzete nastavitve mape",
+ "Editing {%path%}.": "Ureja se {{path}}.",
+ "Enable Crash Reporting": "Omogoči poročanje o zrušitvah",
+ "Enable NAT traversal": "Omogoči NAT prehod",
+ "Enable Relaying": "Omogoči posredovanje",
+ "Enabled": "Omogočeno",
+ "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Vnesite število, katera ni negativno (npr. \"2,35\") in izberite enoto. Odstotki so del celotne velikosti diska.",
+ "Enter a non-privileged port number (1024 - 65535).": "Vnesite neprivilegirano številko omrežnih vrat (1024 - 65535).",
+ "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Vnesi z vejico ločene naslove (\"tcp://ip:port\", \"tcp://host:port\") ali \"dynamic\" za samodejno odkrivanje naslova.",
+ "Enter ignore patterns, one per line.": "Vnesite spregledni vzorec, enega v vrsto",
+ "Enter up to three octal digits.": "Vnesite do tri osmiške števke.",
+ "Error": "Napaka",
+ "External File Versioning": "Zunanje beleženje različic datotek",
+ "Failed Items": "Neuspeli predmeti",
+ "Failed to load ignore patterns.": "Prezrih vzorcev ni bilo mogoče naložiti.",
+ "Failed to setup, retrying": "Nastavitev ni uspela, ponovni poskus",
+ "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Neuspeh povezav z IPv6 strežniki je pričakovan, če ni IPv6 povezljivost.",
+ "File Pull Order": "Vrstni red prenosa datotek",
+ "File Versioning": "Beleženje različic datotek",
+ "Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Datoteke so premaknjene v .stversions mapo ob brisanju ali zamenjavi s strani programa Syncthing.",
+ "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Datoteke se premaknejo v datumsko označene različice v mapi .stversions, ko jih zamenja ali izbriše Syncthing.",
+ "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Datoteke so zaščitene pred spremembami z drugih naprav, ampak spremembe narejene na tej napravi bodo poslane na ostale naprave.",
+ "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Datoteke se sinhronizirajo iz gruče, vendar vse spremembe, narejene lokalno, ne bodo poslane drugim napravam.",
+ "Filesystem Watcher Errors": "Napake opazovalca datotečnega sistema",
+ "Filter by date": "Filtriraj po datumu",
+ "Filter by name": "Filtriraj po imenu",
+ "Folder": "Mapa",
+ "Folder ID": "ID Mape",
+ "Folder Label": "Oznaka mape",
+ "Folder Path": "Pot do mape",
+ "Folder Type": "Vrsta mape",
+ "Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Vrsta mape »{{receiveEncrypted}}« je mogoče nastaviti samo ob dodajanju nove mape.",
+ "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.": "Vrste mape »{{receiveEncrypted}}« po dodajanju mape ni mogoče spremeniti. Odstraniti morate mapo, izbrisati ali dešifrirati podatke na disku in ponovno dodati mapo.",
+ "Folders": "Mape",
+ "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.": "Za naslednje mape je prišlo do napake, ko so se začele spremljati spremembe. Se bo ponovno poskusilo vsako minuto, tako da lahko napake kmalu izginejo. Če vztrajajo, poskusite odpraviti osnovno težavo in prosite za pomoč, če ne morete.",
+ "Full Rescan Interval (s)": "Polni interval osveževanja (v sekundah)",
+ "GUI": "Vmesnik",
+ "GUI Authentication Password": "Geslo overjanja vmesnika",
+ "GUI Authentication User": "Uporabniško ime overjanja vmesnika",
+ "GUI Authentication: Set User and Password": "Overjanje za grafični vmesnik: Nastavi uporabnika in geslo",
+ "GUI Listen Address": "Naslov grafičnega vmesnika",
+ "GUI Theme": "Slog grafičnega vmesnika",
+ "General": "Splošno",
+ "Generate": "Ustvari",
+ "Global Discovery": "Splošno odkrivanje",
+ "Global Discovery Servers": "Strežniki splošnega odkrivanja",
+ "Global State": "Stanje odkrivanja",
+ "Help": "Pomoč",
+ "Home page": "Domača stran",
+ "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Vendar pa vaše trenutne nastavitve kažejo, da morda ne želite to omogočiti. Za vas smo onemogočili samodejno poročanje o zrušitvah.",
+ "Identification": "Identifikacija",
+ "If untrusted, enter encryption password": "Če ni zaupanja vreden, vnesite geslo za šifriranje",
+ "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Če želite drugim uporabnikom v tem računalniku preprečiti dostop do Syncthinga in prek njega do vaših datotek, razmislite o nastavitvi preverjanja pristnosti.",
+ "Ignore": "Prezri",
+ "Ignore Patterns": "Vzorec preziranja",
+ "Ignore Permissions": "Prezri dovoljenja",
+ "Ignored Devices": "Prezrte naprave",
+ "Ignored Folders": "Prezrte mape",
+ "Ignored at": "Prezrt pri",
+ "Incoming Rate Limit (KiB/s)": "Omejitev nalaganja (KiB/s)",
+ "Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Nepravilna konfiguracija lahko poškodujejo vsebino vaše mape in povzroči, da Syncthing postane neoperativen.",
+ "Introduced By": "Predstavil",
+ "Introducer": "Uvajalec",
+ "Inversion of the given condition (i.e. do not exclude)": "Inverzija podanega pogoja (primer. ne vključi)",
+ "Keep Versions": "Ohrani različice",
+ "LDAP": "LDAP",
+ "Largest First": "najprej največja",
+ "Last Scan": "Zadnje skeniranje",
+ "Last seen": "Zadnjič videno",
+ "Latest Change": "Najnovejša sprememba",
+ "Learn more": "Nauči se več",
+ "Limit": "Omejitev",
+ "Listener Failures": "Napake vmesnika",
+ "Listener Status": "Stanje vmesnika",
+ "Listeners": "Poslušalci",
+ "Loading data...": "Nalaganje podatkov...",
+ "Loading...": "Nalaganje...",
+ "Local Additions": "Lokalni dodatki",
+ "Local Discovery": "Krajevno odkrivanje",
+ "Local State": "Krajevno stanje",
+ "Local State (Total)": "Krajevno stanje (vsota)",
+ "Locally Changed Items": "Lokalno spremenjeni predmeti",
+ "Log": "Dnevnik",
+ "Log tailing paused. Scroll to the bottom to continue.": "Odvajanje dnevnika je zaustavljeno. Za nadaljevanje se pomaknite do dna.",
+ "Logs": "Dnevniki",
+ "Major Upgrade": "Večja nadgradnja",
+ "Mass actions": "Množične akcije",
+ "Maximum Age": "Največja starost",
+ "Metadata Only": "Samo metapodatki ",
+ "Minimum Free Disk Space": "Minimalen nezaseden prostor na disku",
+ "Mod. Device": "Spremenjeno od naprave",
+ "Mod. Time": "Čas spremembe",
+ "Move to top of queue": "Premakni na vrh čakalne vrste",
+ "Multi level wildcard (matches multiple directory levels)": "Več ravni map (sklada se z več ravni map in podmap)",
+ "Never": "Nikoli",
+ "New Device": "Nova Naprava",
+ "New Folder": "Nova Mapa",
+ "Newest First": "najprej najnovejši",
+ "No": "Ne",
+ "No File Versioning": "Brez beleženja različic datotek",
+ "No files will be deleted as a result of this operation.": "Nobene datoteke se ne bodo izbrisale kot rezultat od te operacije.",
+ "No upgrades": "Nobene posodobitve",
+ "Not shared": "Ni deljeno",
+ "Notice": "Obvestilo",
+ "OK": "V redu",
+ "Off": "Brez",
+ "Oldest First": "Najprej najstarejši",
+ "Optional descriptive label for the folder. Can be different on each device.": "Izbirna opisna oznaka za mapo. Na vsaki napravi je lahko drugačna.",
+ "Options": "Možnosti",
+ "Out of Sync": "Neusklajeno",
+ "Out of Sync Items": "Predmeti izven sinhronizacije",
+ "Outgoing Rate Limit (KiB/s)": "Omejitev prenašanja (KiB/s)",
+ "Override": "Preglasi",
+ "Override Changes": "Prepiši spremembe",
+ "Path": "Pot",
+ "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": "Pot do mape v lokalnem računalniku. Ustvarjeno bo, če ne obstaja. Znak tilde (~) lahko uporabite kot bližnjico za",
+ "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%}.": "Pot, kjer bodo ustvarjene nove samodejno sprejete mape, kot tudi privzeta predlagana pot pri dodajanju novih map prek uporabniškega vmesnika. Znak tilde (~) se razširi na {{tilde}}.",
+ "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pot, kjer naj bodo shranjene različice (pustite prazno za privzeto mapo .stversions v mapi skupne rabe).",
+ "Pause": "Premor",
+ "Pause All": "Začasno ustavi vse",
+ "Paused": "V premoru",
+ "Paused (Unused)": "Zaustavljeno (neuporabljeno)",
+ "Pending changes": "Čakajoče spremembe",
+ "Periodic scanning at given interval and disabled watching for changes": "Občasno skeniranje v določenem intervalu in onemogočeno spremljanje sprememb",
+ "Periodic scanning at given interval and enabled watching for changes": "Periodično skeniranje v določenem intervalu in omogočeno spremljanje sprememb",
+ "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodično skeniranje v določenem intervalu in neuspešna nastavitev spremljanje sprememb, ponovni poskus vsake 1 minute:",
+ "Permanently add it to the ignore list, suppressing further notifications.": "Trajno ga dodajte na seznam prezrtih, tako da preprečite nadaljnja obvestila.",
+ "Permissions": "Dovoljenja",
+ "Please consult the release notes before performing a major upgrade.": "Prosimo, da preverite opombe ob izdaji, preden izvedete nadgradnjo na glavno različico.",
+ "Please set a GUI Authentication User and Password in the Settings dialog.": "Prosimo, nastavite uporabnika in geslo za preverjanje pristnosti grafičnega vmesnika v pozivnem oknu Nastavitve.",
+ "Please wait": "Počakajte ...",
+ "Prefix indicating that the file can be deleted if preventing directory removal": "Predpona, ki označuje, da je datoteko mogoče izbrisati, če preprečuje odstranitev imenika",
+ "Prefix indicating that the pattern should be matched without case sensitivity": "Predpona, ki označuje, da je treba vzorec ujemati brez občutljivosti velikih in malih črk",
+ "Preparing to Sync": "Priprava na sinhronizacijo",
+ "Preview": "Predogled",
+ "Preview Usage Report": "Predogled poročila uporabe",
+ "Quick guide to supported patterns": "Hitri vodnik za podprte vzorce",
+ "Random": "Naključno",
+ "Receive Encrypted": "Prejmi šifrirano",
+ "Receive Only": "Samo prejemanje",
+ "Received data is already encrypted": "Prejeti podatki so že šifrirani",
+ "Recent Changes": "Nedavne spremembe",
+ "Reduced by ignore patterns": "Zmanjšano z ignoriranjem vzorcev",
+ "Release Notes": "Opombe ob izdaji",
+ "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidati za izdajo vsebujejo najnovejše funkcije in popravke. Podobne so tradicionalnim dvotedenskim izdajam Syncthing.",
+ "Remote Devices": "Oddaljene naprave",
+ "Remote GUI": "Oddaljeni grafični vmesnik",
+ "Remove": "Odstrani",
+ "Remove Device": "Odstranite napravo",
+ "Remove Folder": "Odstranite mapo",
+ "Required identifier for the folder. Must be the same on all cluster devices.": "Zahtevan identifikator za mapo. Biti mora enak na vseh napravah v gruči.",
+ "Rescan": "Ponovno osveži",
+ "Rescan All": "Osveži vse",
+ "Rescans": "Ponovno skeniranje",
+ "Restart": "Ponovno zaženi",
+ "Restart Needed": "Zahtevan je ponovni zagon",
+ "Restarting": "Poteka ponovni zagon",
+ "Restore": "Obnovi",
+ "Restore Versions": "Obnovi različice",
+ "Resume": "Nadaljuj",
+ "Resume All": "Nadaljuj vse",
+ "Reused": "Ponovno uporabi",
+ "Revert": "Povrni",
+ "Revert Local Changes": "Razveljavi lokalne spremembe",
+ "Save": "Shrani",
+ "Scan Time Remaining": "Preostali čas skeniranja",
+ "Scanning": "Osveževanje",
+ "See external versioning help for supported templated command line parameters.": "Oglejte si zunanjo pomoč za urejanje različic za podprte predloge parametrov ukazne vrstice.",
+ "Select All": "Izberi vse",
+ "Select a version": "Izberite različico",
+ "Select additional devices to share this folder with.": "Izberite dodatne naprave za skupno rabo te mape.",
+ "Select additional folders to share with this device.": "Izberite dodatne mape za skupno rabo s to napravo.",
+ "Select latest version": "Izberite najnovejšo različico",
+ "Select oldest version": "Izberite najstarejšo različico",
+ "Select the folders to share with this device.": "Izberi mapo za deljenje z to napravo.",
+ "Send & Receive": "Pošlji in Prejmi",
+ "Send Only": "Samo pošiljanje",
+ "Settings": "Nastavitve",
+ "Share": "Deli",
+ "Share Folder": "Deli mapo",
+ "Share Folders With Device": "Deli mapo z napravo",
+ "Share this folder?": "Deli to mapo?",
+ "Shared Folders": "Skupne mape",
+ "Shared With": "Usklajeno z",
+ "Sharing": "Skupna raba",
+ "Show ID": "Pokaži ID",
+ "Show QR": "Pokaži QR",
+ "Show detailed discovery status": "Pokaži podroben status odkritja",
+ "Show detailed listener status": "Pokaži podroben status vmesnika",
+ "Show diff with previous version": "Pokaži razliko s prejšnjo različico",
+ "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Prikazano namesto ID-ja naprave v stanju gruče. Oglašuje se drugim napravam kot neobvezno privzeto ime.",
+ "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Prikazano namesto ID-ja naprave v stanju gruče. Če ostane prazno, bo posodobljeno na ime, ki ga oglašuje naprava.",
+ "Shutdown": "Izklopi",
+ "Shutdown Complete": "Izklop končan",
+ "Simple File Versioning": "Enostvno beleženje različic datotek",
+ "Single level wildcard (matches within a directory only)": "Enostopenjski nadomestni znak (ujema se samo v imeniku)",
+ "Size": "Velikost",
+ "Smallest First": "najprej najmanjša",
+ "Some discovery methods could not be established for finding other devices or announcing this device:": "Nekaterih metod odkrivanja ni bilo mogoče vzpostaviti za iskanje drugih naprav ali objavo te naprave:",
+ "Some items could not be restored:": "Nekaterih predmetov ni bilo mogoče obnoviti:",
+ "Some listening addresses could not be enabled to accept connections:": "Nekaterim naslovom za poslušanje ni bilo mogoče omogočiti sprejemanja povezav:",
+ "Source Code": "Izvorna koda",
+ "Stable releases and release candidates": "Stabilne izdaje in kandidati za sprostitev",
+ "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabilne izdaje zamujajo za približno dva tedna. V tem času gredo skozi testiranje kot kandidati za izdajo.",
+ "Stable releases only": "Samo stabilne izdaje",
+ "Staggered File Versioning": "Razporeditveno beleženje različic datotek",
+ "Start Browser": "Zaženi brskalnik",
+ "Statistics": "Statistika",
+ "Stopped": "Zaustavljeno",
+ "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.": "Shranjuje in sinhronizira samo šifrirane podatke. Mape na vseh povezanih napravah morajo biti nastavljene z istim geslom ali pa morajo biti tudi vrste \"{{receiveEncrypted}}\".",
+ "Support": "Pomoč",
+ "Support Bundle": "Podporni paket",
+ "Sync Protocol Listen Addresses": "Naslovi poslušanja protokola sinhronizacije",
+ "Syncing": "Usklajevanje",
+ "Syncthing has been shut down.": "Program Syncthing je onemogočen.",
+ "Syncthing includes the following software or portions thereof:": "Syncthing vključuje naslednjo programsko opremo ali njene dele:",
+ "Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing je brezplačna odprtokodna programska oprema, licencirana kot MPL v2.0.",
+ "Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing posluša na naslednjih omrežnih naslovih poskuse povezovanja iz drugih naprav:",
+ "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing ne posluša poskusov povezovanja drugih naprav na katerem koli naslovu. Delujejo lahko samo odhodne povezave iz te naprave.",
+ "Syncthing is restarting.": "Program Syncthing se ponovno zaganja.",
+ "Syncthing is upgrading.": "Program Syncthing se posodablja",
+ "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing zdaj podpira samodejno poročanje o zrušitvah razvijalcem. Ta funkcija je privzeto omogočena.",
+ "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Zdi se, da je Syncthing ni delujoč ali pa je prišlo do težave z vašo internetno povezavo. Ponovni poskus …",
+ "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Zdi se, da ima Syncthing težave pri obdelavi vaše zahteve. Osvežite stran ali znova zaženite Syncthing, če se težava ponovi.",
+ "Take me back": "Daj me nazaj",
+ "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Naslov grafičnega vmesnika preglasijo možnosti zagona. Spremembe tukaj ne bodo veljale, dokler je preglasitev v veljavi.",
+ "The Syncthing Authors": "Syncthing avtorji",
+ "The Syncthing admin interface is configured to allow remote access without a password.": "Skrbniški vmesnik Syncthing je konfiguriran tako, da omogoča oddaljeni dostop brez gesla.",
+ "The aggregated statistics are publicly available at the URL below.": "Zbrani statistični podatki so javno dostopni na spodnjem URL-ju.",
+ "The cleanup interval cannot be blank.": "Interval čiščenja ne sme biti prazen.",
+ "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfiguracija je bila shranjena, vendar ni aktivirana. Sinhronizacija se mora znova zagnati, da aktivirate novo konfiguracijo.",
+ "The device ID cannot be blank.": "ID naprave ne more biti prazno.",
+ "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 naprave, ki ga vnesete tukaj, najdete v pozivnem oknu »Dejanja > Pokaži ID« na drugi napravi. Presledki in pomišljaji so neobvezni (prezrti).",
+ "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.": "Poročilo o šifrirani uporabi se pošilja vsak dan. Uporablja se za sledenje običajnih platform, velikosti map in različic aplikacij. Če se sporočeni nabor podatkov spremeni, boste znova pozvani k temu pozivnem oknu.",
+ "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.": "Vneseni ID naprave ni videti veljaven. To mora biti niz z 52 ali 56 znaki, sestavljen iz črk in številk, pri čemer so presledki in pomišljaji neobvezni.",
+ "The folder ID cannot be blank.": "ID mape ne more biti prazno.",
+ "The folder ID must be unique.": "ID mape more biti edinstveno.",
+ "The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "Vsebina mape na drugih napravah bo prepisana, da bo postala identična tej napravi. Datoteke, ki niso tukaj, bodo izbrisane na drugih napravah.",
+ "The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "Vsebina mape na drugih napravah bo prepisana, da bo postala identična tej napravi. Tu na novo dodane datoteke bodo izbrisane.",
+ "The folder path cannot be blank.": "Pot mape ne more biti prazno.",
+ "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.": "Uporabljajo se sledeči intervali: prvo uro se različica hrani vsakih 30 sekund, prvi dan se različica hrani vsako uro, prvih 30 dni se različica hrani vsak dan, do najvišje starosti se različica hrani vsaki teden.",
+ "The following items could not be synchronized.": "Naslednjih predmetov ni bilo mogoče sinhronizirati.",
+ "The following items were changed locally.": "Naslednji predmeti so bili lokalno spremenjeni.",
+ "The following methods are used to discover other devices on the network and announce this device to be found by others:": "Naslednje metode se uporabljajo za odkrivanje drugih naprav v omrežju in oznanitev, da jo najdejo drugi:",
+ "The following unexpected items were found.": "Najdeni so bili naslednji nepričakovani predmeti.",
+ "The interval must be a positive number of seconds.": "Interval mora biti pozitivno število od sekund.",
+ "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Interval, v sekundah, za zagon čiščenja v mapi različic. 0 za onemogočanje občasnega čiščenja.",
+ "The maximum age must be a number and cannot be blank.": "Najvišja starost mora biti številka in ne sme biti prazno.",
+ "The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Najdaljši čas za shranjevanje različice (v dnevih, nastavite na 0, da se različice ohranijo za vedno).",
+ "The number of days must be a number and cannot be blank.": "Število dni mora biti število in ne more biti prazno.",
+ "The number of days to keep files in the trash can. Zero means forever.": "Število dni kolikor se hranijo datoteke v Smetnjaku. Nič pomeni za zmeraj. ",
+ "The number of old versions to keep, per file.": "Število starejših različic za hrambo na datoteko.",
+ "The number of versions must be a number and cannot be blank.": "Število različic mora biti število in ne more biti prazno.",
+ "The path cannot be blank.": "Pot ne more biti prazna.",
+ "The rate limit must be a non-negative number (0: no limit)": "Omejitev stopnje odzivnosti mora biti nenegativno število (0: brez omejitve)",
+ "The rescan interval must be a non-negative number of seconds.": "Interval skeniranja mora biti pozitivna številka.",
+ "There are no devices to share this folder with.": "Ni naprav za skupno rabo te mape.",
+ "There are no folders to share with this device.": "Ni map za skupno rabo s to napravo.",
+ "They are retried automatically and will be synced when the error is resolved.": "Samodejno se poskuša znova in bo sinhronizirano, ko je napaka odpravljena.",
+ "This Device": "Ta naprava",
+ "This can easily give hackers access to read and change any files on your computer.": "To lahko hekerjem preprosto omogoči dostop do branja in spreminjanja vseh datotek v vašem računalniku.",
+ "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Ta naprava ne more samodejno odkriti drugih naprav ali objaviti svojega naslova, da ga najdejo drugi. Povezujejo se lahko samo naprave s statično konfiguriranimi naslovi.",
+ "This is a major version upgrade.": "To je nadgradnja glavne različice",
+ "This setting controls the free space required on the home (i.e., index database) disk.": "Ta nastavitev nadzoruje prosti prostor potreben na domačem (naprimer, indeksirana podatkovna baza) pogonu.",
+ "Time": "Čas",
+ "Time the item was last modified": "Čas, ko je bil element nazadnje spremenjen",
+ "Trash Can File Versioning": "Beleženje različic datotek s Smetnjakom",
+ "Type": "Vrsta",
+ "UNIX Permissions": "UNIX dovoljenja",
+ "Unavailable": "Ni na voljo",
+ "Unavailable/Disabled by administrator or maintainer": "Ni na voljo/Onemogočeno od administratorja ali vzdrževalca",
+ "Undecided (will prompt)": "Neodločen (bo pozval)",
+ "Unexpected Items": "Nepričakovani predmeti",
+ "Unexpected items have been found in this folder.": "V tej mapi so bili najdeni nepričakovani predmeti.",
+ "Unignore": "Odignoriraj",
+ "Unknown": "Neznano",
+ "Unshared": "Ne deli",
+ "Unshared Devices": "Naprave, ki niso v skupni rabi",
+ "Unshared Folders": "Mape, ki niso v skupni rabi",
+ "Untrusted": "Nezaupno",
+ "Up to Date": "Posodobljeno",
+ "Updated": "Posodobljeno",
+ "Upgrade": "Posodobi",
+ "Upgrade To {%version%}": "Posodobi na različico {{version}}",
+ "Upgrading": "Posodabljanje",
+ "Upload Rate": "Hitrost prejemanja",
+ "Uptime": "Čas delovanja",
+ "Usage reporting is always enabled for candidate releases.": "Poročanje o uporabi je vedno omogočeno za kandidatne izdaje.",
+ "Use HTTPS for GUI": "Uporabi protokol HTTPS za vmesnik",
+ "Use notifications from the filesystem to detect changed items.": "Za odkrivanje spremenjenih elementov uporabite obvestila iz datotečnega sistema.",
+ "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Uporabniško ime/geslo ni bilo nastavljeno za preverjanje pristnosti na grafičnem vmesniku. Razmislite o nastavitvi.",
+ "Version": "Različica",
+ "Versions": "Različice",
+ "Versions Path": "Pot do različic",
+ "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Različice se samodejno izbrišejo, če so starejše od najvišje starosti ali presegajo dovoljeno število datotek v intervalu.",
+ "Waiting to Clean": "Čakanje na čiščenje",
+ "Waiting to Scan": "Čakanje na skeniranje",
+ "Waiting to Sync": "Čakanje na sinhronizacijo",
+ "Warning": "Opozorilo",
+ "Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Opozorilo, ta pot je nadrejena mapa obstoječe mape \"{{otherFolder}}\".",
+ "Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Opozorilo, ta pot je nadrejena mapa obstoječe mape \"{{otherFolderLabel}}\" ({{otherFolder}}).",
+ "Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Opozorilo, ta pot je že podmapa obstoječe mape \"{{otherFolder}}\".",
+ "Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Opozorilo, ta pot je že podmapa obstoječe mape \"{{otherFolderLabel}}\" ({{otherFolder}}).",
+ "Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Opozorilo: Če uporabljate zunanji opazovalec, kot je {{syncthingInotify}}, se prepričajte, da je deaktiviran.",
+ "Watch for Changes": "Gleda se za spremembe",
+ "Watching for Changes": "Gledanje za spremembe",
+ "Watching for changes discovers most changes without periodic scanning.": "Opazovanje sprememb odkrije večino sprememb brez občasnega skeniranja.",
+ "When adding a new device, keep in mind that this device must be added on the other side too.": "Ob dodajanju nove naprave imejte v mislih, da ta naprava mora biti dodana tudi na drugi strani.",
+ "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.": "Ko dodajate novo mapo, ne pozabite, da se ID mape uporablja za povezovanje map med napravami. Razlikujejo se na velike in male črke in se morajo natančno ujemati med vsemi napravami.",
+ "Yes": "Da",
+ "You can also select one of these nearby devices:": "Izberete lahko tudi eno od teh naprav v bližini:",
+ "You can change your choice at any time in the Settings dialog.": "Svojo izbiro lahko kadar koli spremenite v pozivnem oknu Nastavitve.",
+ "You can read more about the two release channels at the link below.": "Več o obeh kanalih za izdajo si lahko preberete na spodnji povezavi.",
+ "You have no ignored devices.": "Nimate prezrtih naprav.",
+ "You have no ignored folders.": "Nimate prezrtih map.",
+ "You have unsaved changes. Do you really want to discard them?": "Imate neshranjene spremembe. Ali jih res želite zavreči?",
+ "You must keep at least one version.": "Potrebno je obdržati vsaj eno verzijo.",
+ "You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Nikoli ne smete ničesar dodati ali spremeniti lokalno v mapi \"{{receiveEncrypted}}\".",
+ "days": "dnevi",
+ "directories": "mape",
+ "files": "datoteke",
+ "full documentation": "celotna dokumentacija",
+ "items": "predmeti",
+ "seconds": "sekunde",
+ "theme-name-black": "Črna",
+ "theme-name-dark": "Temno",
+ "theme-name-default": "Privzeto",
+ "theme-name-light": "Svetlo",
+ "{%device%} wants to share folder \"{%folder%}\".": "{{device}} želi deliti mapo \"{{folder}}\".",
+ "{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} želi deliti mapo \"{{folderlabel}}\" ({{folder}}).",
+ "{%reintroducer%} might reintroduce this device.": "{{reintroducer}} bo morda znova predstavil to napravo."
+}
\ No newline at end of file
diff --git a/gui/default/assets/lang/lang-sv.json b/gui/default/assets/lang/lang-sv.json
index 81b7684c8a0..5d2e20c55db 100644
--- a/gui/default/assets/lang/lang-sv.json
+++ b/gui/default/assets/lang/lang-sv.json
@@ -92,7 +92,7 @@
"Disabled periodic scanning and enabled watching for changes": "Inaktiverad periodisk skanning och aktiverad bevakning av ändringar",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Inaktiverad periodisk skanning och misslyckades med att ställa in bevakning av ändringar, försöker igen var 1:e minut:",
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Inaktiverar att jämföra och synkronisera filbehörigheter. Användbart för system med obefintliga eller anpassade behörigheter (t.ex. FAT, exFAT, Synology, Android).",
- "Discard": "Kasta",
+ "Discard": "Kassera",
"Disconnected": "Frånkopplad",
"Disconnected (Unused)": "Frånkopplad (oanvänd)",
"Discovered": "Upptäckt",
@@ -171,7 +171,7 @@
"Ignored Devices": "Ignorerade enheter",
"Ignored Folders": "Ignorerade mappar",
"Ignored at": "Ignorerad vid",
- "Incoming Rate Limit (KiB/s)": "Inkommande hastighetsgräns (KiB/s)",
+ "Incoming Rate Limit (KiB/s)": "Ingående hastighetsgräns (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Inkorrekt konfiguration kan skada innehållet i mappen and få Syncthing att sluta fungera.",
"Introduced By": "Introducerad av",
"Introducer": "Introduktör",
@@ -278,7 +278,7 @@
"Revert Local Changes": "Återställ lokala ändringar",
"Save": "Spara",
"Scan Time Remaining": "Återstående skanningstid",
- "Scanning": "Skannar",
+ "Scanning": "Skanning",
"See external versioning help for supported templated command line parameters.": "Se hjälp för extern version för stödda mallade kommandoradsparametrar.",
"Select All": "Markera alla",
"Select a version": "Välj en version",
@@ -427,7 +427,7 @@
"You can read more about the two release channels at the link below.": "Du kan läsa mer om de två publiceringsskanalerna på länken nedan.",
"You have no ignored devices.": "Du har inga ignorerade enheter.",
"You have no ignored folders.": "Du har inga ignorerade mappar.",
- "You have unsaved changes. Do you really want to discard them?": "Du har osparade ändringar. Vill du verkligen kasta dem?",
+ "You have unsaved changes. Do you really want to discard them?": "Du har osparade ändringar. Vill du verkligen kassera dem?",
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Du ska aldrig lägga till eller ändra något lokalt i en \"{{receiveEncrypted}}\"-mapp.",
"days": "dagar",
@@ -436,6 +436,10 @@
"full documentation": "fullständig dokumentation",
"items": "objekt",
"seconds": "sekunder",
+ "theme-name-black": "Svart",
+ "theme-name-dark": "Mörkt",
+ "theme-name-default": "Standard",
+ "theme-name-light": "Ljust",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela mapp \"{{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."
diff --git a/gui/default/assets/lang/lang-tr.json b/gui/default/assets/lang/lang-tr.json
index 1eaee2fa969..be14fa36b37 100644
--- a/gui/default/assets/lang/lang-tr.json
+++ b/gui/default/assets/lang/lang-tr.json
@@ -436,6 +436,10 @@
"full documentation": "tam belgelendirme",
"items": "öğe",
"seconds": "saniye",
+ "theme-name-black": "Siyah",
+ "theme-name-dark": "Koyu",
+ "theme-name-default": "Varsayılan",
+ "theme-name-light": "Açık",
"{%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.",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} bu cihazı yeniden tanıtabilir."
diff --git a/gui/default/assets/lang/lang-uk.json b/gui/default/assets/lang/lang-uk.json
index c2e0b6f4da7..1eb9e42ce4d 100644
--- a/gui/default/assets/lang/lang-uk.json
+++ b/gui/default/assets/lang/lang-uk.json
@@ -436,6 +436,10 @@
"full documentation": "повна документація",
"items": "елементи",
"seconds": "секунд",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хоче поділитися директорією \"{{folderLabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
diff --git a/gui/default/assets/lang/lang-zh-CN.json b/gui/default/assets/lang/lang-zh-CN.json
index 55839bb7dc1..2e26dc0e14d 100644
--- a/gui/default/assets/lang/lang-zh-CN.json
+++ b/gui/default/assets/lang/lang-zh-CN.json
@@ -114,9 +114,9 @@
"Edit Folder": "编辑文件夹",
"Edit Folder Defaults": "编辑文件夹默认值",
"Editing {%path%}.": "正在编辑 {{path}}。",
- "Enable Crash Reporting": "启用自动发送崩溃报告",
- "Enable NAT traversal": "启用 NAT 遍历",
- "Enable Relaying": "开启中继",
+ "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)。",
@@ -436,6 +436,10 @@
"full documentation": "完整文档",
"items": "条目",
"seconds": "秒",
+ "theme-name-black": "黑色",
+ "theme-name-dark": "深色",
+ "theme-name-default": "默认",
+ "theme-name-light": "浅色",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将 “{{folder}}” 文件夹共享给您。",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要共享 \"{{folderlabel}}\" ({{folder}}) 文件夹给您。",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}}可能会重新引入此设备。"
diff --git a/gui/default/assets/lang/lang-zh-HK.json b/gui/default/assets/lang/lang-zh-HK.json
index 266c9070985..ecf39bf58ac 100644
--- a/gui/default/assets/lang/lang-zh-HK.json
+++ b/gui/default/assets/lang/lang-zh-HK.json
@@ -436,6 +436,10 @@
"full documentation": "完整文檔",
"items": "條目",
"seconds": "秒",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想將 「{{folder}}」 文件夾共享給您。",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要共享 \"{{folderlabel}}\" ({{folder}}) 文件夾給您。",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} 可能會重新引入此設備。"
diff --git a/gui/default/assets/lang/lang-zh-TW.json b/gui/default/assets/lang/lang-zh-TW.json
index 4b8f8240ed6..7c29312e851 100644
--- a/gui/default/assets/lang/lang-zh-TW.json
+++ b/gui/default/assets/lang/lang-zh-TW.json
@@ -436,6 +436,10 @@
"full documentation": "完整說明文件",
"items": "個項目",
"seconds": "秒",
+ "theme-name-black": "Black",
+ "theme-name-dark": "Dark",
+ "theme-name-default": "Default",
+ "theme-name-light": "Light",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要共享資料夾 \"{{folder}}\"。",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 想要共享資料夾 \"{{folderlabel}}\" ({{folder}})。",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} 可能會重新引入此裝置。"
diff --git a/gui/default/assets/lang/prettyprint.js b/gui/default/assets/lang/prettyprint.js
index 22260d947dd..049caf67c69 100644
--- a/gui/default/assets/lang/prettyprint.js
+++ b/gui/default/assets/lang/prettyprint.js
@@ -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","id":"Indonesian","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)","ro-RO":"Romanian (Romania)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-HK":"Chinese (Hong Kong)","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","id":"Indonesian","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)","ro-RO":"Romanian (Romania)","ru":"Russian","sk":"Slovak","sl":"Slovenian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-HK":"Chinese (Hong Kong)","zh-TW":"Chinese (Taiwan)"}
diff --git a/gui/default/assets/lang/valid-langs.js b/gui/default/assets/lang/valid-langs.js
index b3b0ae91bd8..990cd3391c8 100644
--- a/gui/default/assets/lang/valid-langs.js
+++ b/gui/default/assets/lang/valid-langs.js
@@ -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","id","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ro-RO","ru","sk","sv","tr","uk","zh-CN","zh-HK","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","id","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ro-RO","ru","sk","sl","sv","tr","uk","zh-CN","zh-HK","zh-TW"]
diff --git a/gui/default/syncthing/core/aboutModalView.html b/gui/default/syncthing/core/aboutModalView.html
index 9b3c3646aed..b2623a0ceb6 100644
--- a/gui/default/syncthing/core/aboutModalView.html
+++ b/gui/default/syncthing/core/aboutModalView.html
@@ -19,7 +19,7 @@
The Syncthing Authors
-Jakob Borg, Audrius Butkevicius, Jesse Lucas, 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, Tomasz Wilczyński, 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, Anur, 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, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, 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, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, 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, Jonta, 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, Lars Lehtonen, 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, Max Schulze, MaximAL, Maxime Thirouin, MichaIng, 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, Steven Eckhoff, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, 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, bt90, chenrui, chucic, deepsource-autofix[bot], dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, jtagcat, klemens, marco-m, mclang, mv1005, otbutz, overkill, perewa, rubenbe, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙
+Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Alexander Graf, Alexandre Viau, Aman Gupta, Anderson Mesquita, Andrew Dunham, Andrew Rabert, Andrey D, André Colomb, Anjan Momi, Antoine Lamielle, Antony Male, Anur, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Audrius Butkevicius, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Schulz, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Caleb Callaway, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Harte, 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, Evgeny Kuznetsov, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, Jakob Borg, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jesse Lucas, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, 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, Lars K.W. Gohlke, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lode Hoste, 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, Max Schulze, MaximAL, Maxime Thirouin, MichaIng, Michael Jephcote, Michael Ploujnikov, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nate Morrison, 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, Philippe Schommers, 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, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, Sergey Mishin, Shaarad Dalvi, Simon Frei, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Stefan Tatschner, Steven Eckhoff, 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, Wulf Weich, Xavier O., Yannic A., andresvia, andyleap, boomsquared, bt90, chenrui, chucic, deepsource-autofix[bot], dependabot-preview[bot], dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, greatroar, janost, jaseg, jelle van der Waa, jtagcat, klemens, marco-m, mclang, mv1005, otbutz, overkill, perewa, rubenbe, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙
diff --git a/man/stdiscosrv.1 b/man/stdiscosrv.1
index 14cb8586911..a3738b138a9 100644
--- a/man/stdiscosrv.1
+++ b/man/stdiscosrv.1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "STDISCOSRV" "1" "Oct 17, 2021" "v1" "Syncthing"
+.TH "STDISCOSRV" "1" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.SH SYNOPSIS
diff --git a/man/strelaysrv.1 b/man/strelaysrv.1
index 8ef50d10829..23c3c47c3aa 100644
--- a/man/strelaysrv.1
+++ b/man/strelaysrv.1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "STRELAYSRV" "1" "Oct 17, 2021" "v1" "Syncthing"
+.TH "STRELAYSRV" "1" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.SH SYNOPSIS
diff --git a/man/syncthing-bep.7 b/man/syncthing-bep.7
index c1aae7273ba..1ec8f74f725 100644
--- a/man/syncthing-bep.7
+++ b/man/syncthing-bep.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-BEP" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-BEP" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.SH INTRODUCTION AND DEFINITIONS
diff --git a/man/syncthing-config.5 b/man/syncthing-config.5
index af808c16660..6394e97e8b1 100644
--- a/man/syncthing-config.5
+++ b/man/syncthing-config.5
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-CONFIG" "5" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-CONFIG" "5" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.SH SYNOPSIS
@@ -115,12 +115,18 @@ may no longer correspond to the defaults.
.sp
.nf
.ft C
-
+
basic
-
+
+
+
1
-
+
+ 3600
+
+ basic
+
0
0
0
@@ -140,14 +146,18 @@ may no longer correspond to the defaults.
false
standard
standard
+ false
+ false
-
+
dynamic
false
false
0
0
+
0
+ false
0
@@ -190,8 +200,8 @@ may no longer correspond to the defaults.
https://upgrades.syncthing.net/meta.json
false
10
+ authenticationUserAndPassword
0
- ~
true
0
https://crash.syncthing.net/newcrash
@@ -201,7 +211,58 @@ may no longer correspond to the defaults.
default
auto
0
+ true
+ false
+ 0
+ 0
+ false
+
+
+
+ basic
+
+
+
+ 1
+
+ 3600
+
+ basic
+
+ 0
+ 0
+ 0
+ random
+ false
+ 0
+ 0
+ 10
+ false
+ false
+ false
+ 25
+ .stfolder
+ false
+ 0
+ 2
+ false
+ standard
+ standard
+ false
+ false
+
+
+ dynamic
+ false
+ false
+ 0
+ 0
+ 0
+ false
+ 0
+
+
.ft P
.fi
@@ -213,14 +274,14 @@ may no longer correspond to the defaults.
.sp
.nf
.ft C
-
+
- 5SYI2FS\-LW6YAXI\-JJDYETS\-NDBBPIO\-256MWBO\-XDPXWVG\-24QPUM4\-PDW4UQU
- bd7q3\-zskm5
+
+
.ft P
.fi
@@ -235,19 +296,14 @@ The config version. Increments whenever a change is made that requires
migration from previous formats.
.UNINDENT
.sp
-It contains the elements described in the following sections and these two
-additional child elements:
+It contains the elements described in the following sections and any number of
+this additional child element:
.INDENT 0.0
.TP
-.B ignoredDevice
+.B remoteIgnoredDevice
Contains the ID of the device that should be ignored. Connection attempts
from this device are logged to the console but never displayed in the web
GUI.
-.TP
-.B ignoredFolder
-Contains the ID of the folder that should be ignored. This folder will
-always be skipped when advertised from a remote device, i.e. this will be
-logged, but there will be no dialog shown in the web GUI.
.UNINDENT
.SH FOLDER ELEMENT
.INDENT 0.0
@@ -257,9 +313,15 @@ logged, but there will be no dialog shown in the web GUI.
.ft C
basic
-
+
+
+
1
-
+
+ 3600
+
+ basic
+
0
0
0
@@ -279,6 +341,8 @@ logged, but there will be no dialog shown in the web GUI.
false
standard
standard
+ false
+ false
.ft P
.fi
@@ -519,16 +583,18 @@ See folder\-copyRangeMethod for details.
.sp
.nf
.ft C
-
+
dynamic
false
false
0
0
+
0
+ false
0
-
+
tcp://192.0.2.1:22001
true
192.168.0.0/16
@@ -536,6 +602,7 @@ See folder\-copyRangeMethod for details.
100
100
65536
+ false
8384
.ft P
@@ -669,6 +736,11 @@ the config name looking like kilobits/second.
Maximum receive rate to use for this device. Unit is kibibytes/second,
despite the config name looking like kilobits/second.
.TP
+.B ignoredFolder
+Contains the ID of the folder that should be ignored. This folder will
+always be skipped when advertised from the containing remote device,
+i.e. this will be logged, but there will be no dialog shown in the web GUI.
+.TP
.B maxRequestKiB
Maximum amount of data to have outstanding in requests towards this device.
Unit is kibibytes.
@@ -688,7 +760,7 @@ work for link\-local IPv6 addresses because of modern browser limitations.
.ft C
127.0.0.1:8384
- l7jSbCqPD95JYZ0g8vi4ZLAMg3ulnN1b
+ k1dnz1Dd0rzTBjjFFh7CXPnrF12C49B1
default
.ft P
@@ -717,7 +789,7 @@ The following child elements may be present:
.INDENT 0.0
.TP
.B address
-Set the listen address. One address element must be present. Allowed address formats are:
+Set the listen address. Exactly one address element must be present. Allowed address formats are:
.INDENT 7.0
.TP
.B IPv4 address and port (\fB127.0.0.1:8384\fP)
@@ -856,8 +928,8 @@ Skip verification (\fBtrue\fP or \fBfalse\fP).
https://upgrades.syncthing.net/meta.json
false
10
+ authenticationUserAndPassword
0
- ~
true
0
https://crash.syncthing.net/newcrash
@@ -867,6 +939,11 @@ Skip verification (\fBtrue\fP or \fBfalse\fP).
default
auto
0
+ true
+ false
+ 0
+ 0
+ false
.ft P
.fi
@@ -1030,10 +1107,6 @@ expanded to
Interval in seconds between contacting a STUN server to
maintain NAT mapping. Default is \fB24\fP and you can set it to \fB0\fP to
disable contacting STUN servers.
-.TP
-.B defaultFolderPath
-The UI will propose to create new folders at this path. This can be disabled by
-setting this to an empty string.
.UNINDENT
.INDENT 0.0
.TP
@@ -1046,6 +1119,85 @@ to nine; on Windows, set the process priority class to below normal. To
disable this behavior, for example to control process priority yourself
as part of launching Syncthing, set this option to \fBfalse\fP\&.
.UNINDENT
+.SH DEFAULTS ELEMENT
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+
+
+ basic
+
+
+
+ 1
+
+ 3600
+
+ basic
+
+ 0
+ 0
+ 0
+ random
+ false
+ 0
+ 0
+ 10
+ false
+ false
+ false
+ 25
+ .stfolder
+ false
+ 0
+ 2
+ false
+ standard
+ standard
+ false
+ false
+
+
+ dynamic
+ false
+ false
+ 0
+ 0
+ 0
+ false
+ 0
+
+
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+The \fBdefaults\fP element describes a template for newly added device and folder
+options. These will be used when adding a new remote device or folder, either
+through the GUI or the command line interface. The following child elements can
+be present in the \fBdefaults\fP element:
+.INDENT 0.0
+.TP
+.B device
+Template for a \fBdevice\fP element, with the same internal structure. Any
+fields here will be used for a newly added remote device. The \fBid\fP
+attribute is meaningless in this context.
+.TP
+.B folder
+Template for a \fBfolder\fP element, with the same internal structure. Any
+fields here will be used for a newly added shared folder. The \fBid\fP
+attribute is meaningless in this context.
+.sp
+The UI will propose to create new folders at the path given in the \fBpath\fP
+attribute (used to be \fBdefaultFolderPath\fP under \fBoptions\fP). It also
+applies to folders automatically accepted from a remote device.
+.sp
+Even sharing with other remote devices can be done in the template by
+including the appropriate \fBdevice\fP element underneath.
+.UNINDENT
.SS Listen Addresses
.sp
The following address types are accepted in sync protocol listen addresses.
diff --git a/man/syncthing-device-ids.7 b/man/syncthing-device-ids.7
index 376c993b36e..02df255866d 100644
--- a/man/syncthing-device-ids.7
+++ b/man/syncthing-device-ids.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-DEVICE-IDS" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-DEVICE-IDS" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.sp
diff --git a/man/syncthing-event-api.7 b/man/syncthing-event-api.7
index f383133f2b9..d0e658459f1 100644
--- a/man/syncthing-event-api.7
+++ b/man/syncthing-event-api.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-EVENT-API" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-EVENT-API" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.SH DESCRIPTION
diff --git a/man/syncthing-faq.7 b/man/syncthing-faq.7
index c4bbd629df2..6bef9ba459e 100644
--- a/man/syncthing-faq.7
+++ b/man/syncthing-faq.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-FAQ" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-FAQ" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.INDENT 0.0
@@ -45,7 +45,7 @@ syncthing-faq \- Frequently Asked Questions
.IP \(bu 2
\fI\%How does Syncthing differ from BitTorrent/Resilio Sync?\fP
.IP \(bu 2
-\fI\%Why is there no iOS client?\fP
+\fI\%Is there an iOS client?\fP
.IP \(bu 2
\fI\%Should I keep my device IDs secret?\fP
.UNINDENT
@@ -93,6 +93,8 @@ syncthing-faq \- Frequently Asked Questions
.IP \(bu 2
\fI\%When I do have two distinct Syncthing\-managed folders on two hosts, how does Syncthing handle moving files between them?\fP
.IP \(bu 2
+\fI\%Can I help initial sync by copying files manually?\fP
+.IP \(bu 2
\fI\%Is Syncthing my ideal backup application?\fP
.IP \(bu 2
\fI\%How can I exclude files with brackets ([]) in the name?\fP
@@ -172,9 +174,9 @@ the faster an additional device will receive the data
because small blocks will be fetched from all devices in parallel.
.sp
Syncthing handles renaming files and updating their metadata in an efficient
-manner. This means that renaming a large file will not cause a retransmission of
-that file. Additionally, appending data to existing large files should be
-handled efficiently as well.
+manner. This means that renaming a file will not cause a retransmission of
+that file. Additionally, appending data to existing files should be handled
+efficiently as well.
.sp
Temporary files are used to store partial data
downloaded from other devices. They are automatically removed whenever a file
@@ -195,12 +197,16 @@ mechanisms in use are well defined and visible in the source code. Resilio
Sync uses an undocumented, closed protocol with unknown security properties.
.IP [1] 5
\fI\%https://en.wikipedia.org/wiki/Resilio_Sync\fP
-.SS Why is there no iOS client?
+.SS Is there an iOS client?
+.sp
+There are no plans by the current Syncthing team to officially support iOS in the foreseeable future.
+.sp
+iOS has significant restrictions on background processing that make it very hard to
+run Syncthing reliably and integrate it into the system.
.sp
-There is an alternative implementation of Syncthing (using the same network
-protocol) called \fBfsync()\fP\&. There are no plans by the current Syncthing
-team to support iOS in the foreseeable future, as the code required to do so
-would be quite different from what Syncthing is today.
+However, there is a commercial packaging of Syncthing for iOS that attempts to work within these limitations. [2]
+.IP [2] 5
+\fI\%https://www.mobiussync.com\fP
.SS Should I keep my device IDs secret?
.sp
No. The IDs are not sensitive. Given a device ID it’s possible to find the IP
@@ -225,7 +231,7 @@ device\-ids
.sp
Syncthing logs to stdout by default. On Windows Syncthing by default also
creates \fBsyncthing.log\fP in Syncthing’s home directory (run \fBsyncthing
-\-paths\fP to see where that is). The command line option \fB\-logfile\fP can be
+\-\-paths\fP to see where that is). The command line option \fB\-\-logfile\fP can be
used to specify a user\-defined logfile.
.sp
If you’re running a process manager like systemd, check there. If you’re
@@ -332,7 +338,7 @@ crashes and other bugs.
.SS How can I view the history of changes?
.sp
The web GUI contains a \fBRecent Changes\fP button under the device list which
-displays changes since the last (re)start of Syncthing. With the \fB\-audit\fP
+displays changes since the last (re)start of Syncthing. With the \fB\-\-audit\fP
option you can enable a persistent, detailed log of changes and most
activities, which contains a \fBJSON\fP formatted sequence of events in the
\fB~/.config/syncthing/audit\-_date_\-_time_.log\fP file.
@@ -397,7 +403,7 @@ The easy way to rename or move a synced folder on the local system is to
remove the folder in the Syncthing UI, move it on disk, then re\-add it using
the new path.
.sp
-It’s best to do this when the folder is already in sync between your
+It’s important to do this when the folder is already in sync between your
devices, as it is otherwise unpredictable which changes will “win” after the
move. Changes made on other devices may be overwritten, or changes made
locally may be overwritten by those on other devices.
@@ -432,6 +438,19 @@ block) from A, and then as A gets rescanned, it will remove the files from A.
.sp
A workaround would be to copy first from A to B, rescan B, wait for B to
copy the files on the remote side, and then delete from A.
+.SS Can I help initial sync by copying files manually?
+.sp
+If you have a large folder that you want to keep in sync over a not\-so\-fast network, and you have the possibility to move all files to the remote device in a faster manner, here is a procedure to follow:
+.INDENT 0.0
+.IP \(bu 2
+Create the folder on the local device, but don’t share it with the remote device yet.
+.IP \(bu 2
+Copy the files from the local device to the remote device using regular file copy. If this takes a long time (perhaps requiring travelling there physically), it may be a good idea to make sure that the files on the local device are not updated while you are doing this.
+.IP \(bu 2
+Create the folder on the remote device, and copy the Folder ID from the folder on the local device, as we want the folders to be considered the same. Then wait until scanning the folder is done.
+.IP \(bu 2
+Now share the folder with the other device, on both sides. Syncthing will exchange file information, updating the database, but existing files will not be transferred. This may still take a while initially, be patient and wait until it settled.
+.UNINDENT
.SS Is Syncthing my ideal backup application?
.sp
No. Syncthing is not a great backup application because all changes to your
@@ -531,7 +550,7 @@ own.
By default, Syncthing will look for a directory \fBgui\fP inside the Syncthing
home folder. To change the directory to look for themes, you need to set the
STGUIASSETS environment variable. To get the concrete directory, run
-syncthing with the \fB\-paths\fP parameter. It will print all the relevant paths,
+syncthing with the \fB\-\-paths\fP parameter. It will print all the relevant paths,
including the “GUI override directory”.
.sp
To add e.g. a red theme, you can create the file \fBred/assets/css/theme.css\fP
@@ -553,7 +572,7 @@ upgrade itself automatically within 24 hours of a new release.
The upgrade button appears in the web GUI when a new version has been
released. Pressing it will perform an upgrade.
.IP \(bu 2
-To force an upgrade from the command line, run \fBsyncthing \-upgrade\fP\&.
+To force an upgrade from the command line, run \fBsyncthing \-\-upgrade\fP\&.
.UNINDENT
.sp
Note that your system should have CA certificates installed which allows a
diff --git a/man/syncthing-globaldisco.7 b/man/syncthing-globaldisco.7
index bf732a5287e..f7c77d9637f 100644
--- a/man/syncthing-globaldisco.7
+++ b/man/syncthing-globaldisco.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-GLOBALDISCO" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-GLOBALDISCO" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.SH ANNOUNCEMENTS
diff --git a/man/syncthing-localdisco.7 b/man/syncthing-localdisco.7
index 430da90b8ee..e6ba5daa960 100644
--- a/man/syncthing-localdisco.7
+++ b/man/syncthing-localdisco.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-LOCALDISCO" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-LOCALDISCO" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.SH MODE OF OPERATION
diff --git a/man/syncthing-networking.7 b/man/syncthing-networking.7
index 1f2caac4ad0..83ffc79b97d 100644
--- a/man/syncthing-networking.7
+++ b/man/syncthing-networking.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-NETWORKING" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-NETWORKING" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.SH ROUTER SETUP
diff --git a/man/syncthing-relay.7 b/man/syncthing-relay.7
index f0d6ea090cb..60baadd8920 100644
--- a/man/syncthing-relay.7
+++ b/man/syncthing-relay.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-RELAY" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-RELAY" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.SH WHAT IS A RELAY?
diff --git a/man/syncthing-rest-api.7 b/man/syncthing-rest-api.7
index 5b48b2708ee..a5ff0127390 100644
--- a/man/syncthing-rest-api.7
+++ b/man/syncthing-rest-api.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-REST-API" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-REST-API" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.sp
@@ -100,22 +100,23 @@ Returns the current configuration.
.nf
.ft C
{
- "version": 30,
+ "version": 35,
"folders": [
{
- "id": "GXWxf\-3zgnU",
- "label": "MyFolder",
+ "id": "default",
+ "label": "Default Folder",
"filesystemType": "basic",
"path": "...",
"type": "sendreceive",
"devices": [
{
"deviceID": "...",
- "introducedBy": ""
+ "introducedBy": "",
+ "encryptionPassword": ""
}
],
- "rescanIntervalS": 60,
- "fsWatcherEnabled": false,
+ "rescanIntervalS": 3600,
+ "fsWatcherEnabled": true,
"fsWatcherDelayS": 10,
"ignorePerms": false,
"autoNormalize": true,
@@ -124,10 +125,11 @@ Returns the current configuration.
"unit": "%"
},
"versioning": {
- "type": "simple",
- "params": {
- "keep": "5"
- }
+ "type": "",
+ "params": {},
+ "cleanupIntervalS": 3600,
+ "fsPath": "",
+ "fsType": "basic"
},
"copiers": 0,
"pullerMaxPendingKiB": 0,
@@ -136,14 +138,20 @@ Returns the current configuration.
"ignoreDelete": false,
"scanProgressIntervalS": 0,
"pullerPauseS": 0,
- "maxConflicts": 10,
+ "maxConflicts": \-1,
"disableSparseFiles": false,
"disableTempIndexes": false,
"paused": false,
"weakHashThresholdPct": 25,
"markerName": ".stfolder",
"copyOwnershipFromParent": false,
- "modTimeWindowS": 0
+ "modTimeWindowS": 0,
+ "maxConcurrentWrites": 2,
+ "disableFsync": false,
+ "blockPullOrder": "standard",
+ "copyRangeMethod": "standard",
+ "caseSensitiveFS": false,
+ "junctionsAsDirs": true
}
],
"devices": [
@@ -164,18 +172,27 @@ Returns the current configuration.
"autoAcceptFolders": false,
"maxSendKbps": 0,
"maxRecvKbps": 0,
- "ignoredFolders": [],
- "maxRequestKiB": 0
+ "ignoredFolders": [
+ {
+ "time": "2022\-01\-09T19:09:52Z",
+ "id": "br63e\-wyhb7",
+ "label": "Foo"
+ }
+ ],
+ "maxRequestKiB": 0,
+ "untrusted": false,
+ "remoteGUIPort": 0
}
],
"gui": {
"enabled": true,
"address": "127.0.0.1:8384",
+ "unixSocketPermissions": "",
"user": "Username",
"password": "$2a$10$ZFws69T4FlvWwsqeIwL.TOo5zOYqsa/.TxlUnsGYS.j3JvjFTmxo6",
"authMode": "static",
"useTLS": false,
- "apiKey": "pGahcht56664QU5eoFQW6szbEG6Ec2Cr",
+ "apiKey": "k1dnz1Dd0rzTBjjFFh7CXPnrF12C49B1",
"insecureAdminAccess": false,
"theme": "default",
"debugging": false,
@@ -186,7 +203,9 @@ Returns the current configuration.
"address": "",
"bindDN": "",
"transport": "plain",
- "insecureSkipVerify": false
+ "insecureSkipVerify": false,
+ "searchBaseDN": "",
+ "searchFilter": ""
},
"options": {
"listenAddresses": [
@@ -204,14 +223,14 @@ Returns the current configuration.
"reconnectionIntervalS": 60,
"relaysEnabled": true,
"relayReconnectIntervalM": 10,
- "startBrowser": false,
+ "startBrowser": true,
"natEnabled": true,
"natLeaseMinutes": 60,
"natRenewalMinutes": 30,
"natTimeoutSeconds": 10,
- "urAccepted": \-1,
- "urSeen": 2,
- "urUniqueId": "",
+ "urAccepted": 0,
+ "urSeen": 0,
+ "urUniqueId": "...",
"urURL": "https://data.syncthing.net/newdata",
"urPostInsecurely": false,
"urInitialDelayS": 1800,
@@ -230,9 +249,10 @@ Returns the current configuration.
"alwaysLocalNets": [],
"overwriteRemoteDeviceNamesOnConnect": false,
"tempIndexMinBlocks": 10,
- "unackedNotificationIDs": [],
+ "unackedNotificationIDs": [
+ "authenticationUserAndPassword"
+ ],
"trafficClass": 0,
- "defaultFolderPath": "~",
"setLowPriority": true,
"maxFolderConcurrency": 0,
"crURL": "https://crash.syncthing.net/newcrash",
@@ -243,9 +263,96 @@ Returns the current configuration.
"default"
],
"databaseTuning": "auto",
- "maxConcurrentIncomingRequestKiB": 0
+ "maxConcurrentIncomingRequestKiB": 0,
+ "announceLANAddresses": true,
+ "sendFullIndexOnUpgrade": false,
+ "featureFlags": [],
+ "connectionLimitEnough": 0,
+ "connectionLimitMax": 0,
+ "insecureAllowOldTLSVersions": false
},
- "remoteIgnoredDevices": []
+ "remoteIgnoredDevices": [
+ {
+ "time": "2022\-01\-09T20:02:01Z",
+ "deviceID": "...",
+ "name": "...",
+ "address": "192.168.0.20:22000"
+ }
+ ],
+ "defaults": {
+ "folder": {
+ "id": "",
+ "label": "",
+ "filesystemType": "basic",
+ "path": "~",
+ "type": "sendreceive",
+ "devices": [
+ {
+ "deviceID": "...",
+ "introducedBy": "",
+ "encryptionPassword": ""
+ }
+ ],
+ "rescanIntervalS": 3600,
+ "fsWatcherEnabled": true,
+ "fsWatcherDelayS": 10,
+ "ignorePerms": false,
+ "autoNormalize": true,
+ "minDiskFree": {
+ "value": 1,
+ "unit": "%"
+ },
+ "versioning": {
+ "type": "",
+ "params": {},
+ "cleanupIntervalS": 3600,
+ "fsPath": "",
+ "fsType": "basic"
+ },
+ "copiers": 0,
+ "pullerMaxPendingKiB": 0,
+ "hashers": 0,
+ "order": "random",
+ "ignoreDelete": false,
+ "scanProgressIntervalS": 0,
+ "pullerPauseS": 0,
+ "maxConflicts": 10,
+ "disableSparseFiles": false,
+ "disableTempIndexes": false,
+ "paused": false,
+ "weakHashThresholdPct": 25,
+ "markerName": ".stfolder",
+ "copyOwnershipFromParent": false,
+ "modTimeWindowS": 0,
+ "maxConcurrentWrites": 2,
+ "disableFsync": false,
+ "blockPullOrder": "standard",
+ "copyRangeMethod": "standard",
+ "caseSensitiveFS": false,
+ "junctionsAsDirs": false
+ },
+ "device": {
+ "deviceID": "",
+ "name": "",
+ "addresses": [
+ "dynamic"
+ ],
+ "compression": "metadata",
+ "certName": "",
+ "introducer": false,
+ "skipIntroductionRemovals": false,
+ "introducedBy": "",
+ "paused": false,
+ "allowedNetworks": [],
+ "autoAcceptFolders": false,
+ "maxSendKbps": 0,
+ "maxRecvKbps": 0,
+ "ignoredFolders": [],
+ "maxRequestKiB": 0,
+ "untrusted": false,
+ "remoteGUIPort": 0
+ }
+ }
}
.ft P
.fi
@@ -290,7 +397,7 @@ config, modify the needed parts and post it again.
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
-Return format changed in 0.13.0.
+Return format changed in versions 0.13.0 and 1.19.0.
.UNINDENT
.UNINDENT
.sp
@@ -305,14 +412,9 @@ The connection types are \fBTCP (Client)\fP, \fBTCP (Server)\fP, \fBRelay (Clien
.ft C
{
"total" : {
- "paused" : false,
- "clientVersion" : "",
"at" : "2015\-11\-07T17:29:47.691637262+01:00",
- "connected" : false,
"inBytesTotal" : 1479,
- "type" : "",
"outBytesTotal" : 1318,
- "address" : ""
},
"connections" : {
"YZJBJFX\-RDBL7WY\-6ZGKJ2D\-4MJB4E7\-ZATSDUY\-LD6Y3L3\-MLFUYWE\-AEMXJAC" : {
@@ -320,6 +422,7 @@ The connection types are \fBTCP (Client)\fP, \fBTCP (Server)\fP, \fBRelay (Clien
"inBytesTotal" : 556,
"paused" : false,
"at" : "2015\-11\-07T17:29:47.691548971+01:00",
+ "startedAt" : "2015\-11\-07T00:09:47Z",
"clientVersion" : "v0.12.1",
"address" : "127.0.0.1:22002",
"type" : "TCP (Client)",
@@ -330,6 +433,7 @@ The connection types are \fBTCP (Client)\fP, \fBTCP (Server)\fP, \fBRelay (Clien
"type" : "",
"address" : "",
"at" : "0001\-01\-01T00:00:00Z",
+ "startedAt" : "0001\-01\-01T00:00:00Z",
"clientVersion" : "",
"paused" : false,
"inBytesTotal" : 0,
@@ -343,6 +447,7 @@ The connection types are \fBTCP (Client)\fP, \fBTCP (Server)\fP, \fBRelay (Clien
"inBytesTotal" : 0,
"paused" : false,
"at" : "0001\-01\-01T00:00:00Z",
+ "startedAt" : "0001\-01\-01T00:00:00Z",
"clientVersion" : ""
}
}
@@ -549,7 +654,7 @@ $ curl \-X POST \-H "X\-API\-Key: abc123" http://localhost:8384/rest/system/rese
.UNINDENT
.UNINDENT
.sp
-\fBCaution\fP: See \fB\-reset\-database\fP for \fB\&.stfolder\fP creation side\-effect and caution regarding mountpoints.
+\fBCaution\fP: See \fB\-\-reset\-database\fP for \fB\&.stfolder\fP creation side\-effect and caution regarding mountpoints.
.SS POST /rest/system/restart
.sp
Post with empty body to immediately restart Syncthing.
diff --git a/man/syncthing-security.7 b/man/syncthing-security.7
index 4dae63f4802..0d67c974cd8 100644
--- a/man/syncthing-security.7
+++ b/man/syncthing-security.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-SECURITY" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-SECURITY" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.sp
diff --git a/man/syncthing-stignore.5 b/man/syncthing-stignore.5
index f4a3e6168e0..2825446cfe0 100644
--- a/man/syncthing-stignore.5
+++ b/man/syncthing-stignore.5
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-STIGNORE" "5" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-STIGNORE" "5" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.SH SYNOPSIS
diff --git a/man/syncthing-versioning.7 b/man/syncthing-versioning.7
index c3eacef0b95..95b3e0d0746 100644
--- a/man/syncthing-versioning.7
+++ b/man/syncthing-versioning.7
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING-VERSIONING" "7" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING-VERSIONING" "7" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.sp
diff --git a/man/syncthing.1 b/man/syncthing.1
index 90354d52ace..56b8957361f 100644
--- a/man/syncthing.1
+++ b/man/syncthing.1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "SYNCTHING" "1" "Oct 17, 2021" "v1" "Syncthing"
+.TH "SYNCTHING" "1" "Jan 10, 2022" "v1" "Syncthing"
.SH NAME
syncthing \- Syncthing
.SH SYNOPSIS
@@ -36,13 +36,34 @@ syncthing \- Syncthing
.sp
.nf
.ft C
-syncthing [\-audit] [\-auditfile=] [\-browser\-only] [device\-id]
- [\-generate=] [\-gui\-address=] [\-gui\-apikey=]
- [\-home= | \-config= \-data=]
- [\-logfile=] [\-logflags=]
- [\-no\-browser] [\-no\-console] [\-no\-restart] [\-paths] [\-paused]
- [\-reset\-database] [\-reset\-deltas] [\-unpaused] [\-upgrade]
- [\-upgrade\-check] [\-upgrade\-to=] [\-verbose] [\-version]
+syncthing [serve]
+ [\-\-audit] [\-\-auditfile=] [\-\-browser\-only] [\-\-device\-id]
+ [\-\-generate=] [\-\-gui\-address=] [\-\-gui\-apikey=]
+ [\-\-home= | \-\-config= \-\-data=]
+ [\-\-logfile=] [\-\-logflags=]
+ [\-\-log\-max\-old\-files=] [\-\-log\-max\-size=]
+ [\-\-no\-browser] [\-\-no\-console] [\-\-no\-restart] [\-\-paths] [\-\-paused]
+ [\-\-no\-default\-folder] [\-\-skip\-port\-probing]
+ [\-\-reset\-database] [\-\-reset\-deltas] [\-\-unpaused] [\-\-allow\-newer\-config]
+ [\-\-upgrade] [\-\-no\-upgrade] [\-\-upgrade\-check] [\-\-upgrade\-to=]
+ [\-\-verbose] [\-\-version] [\-\-help] [\-\-debug\-*]
+
+syncthing generate
+ [\-\-home= | \-\-config=]
+ [\-\-gui\-user=] [\-\-gui\-password=]
+ [\-\-no\-default\-folder] [\-\-skip\-port\-probing] [\-\-no\-console]
+ [\-\-help]
+
+syncthing decrypt (\-\-to= | \-\-verify\-only)
+ [\-\-password=] [\-\-folder\-id=] [\-\-token\-path=]
+ [\-\-continue] [\-\-verbose] [\-\-version] [\-\-help]
+
+
+syncthing cli
+ [\-\-home= | \-\-config= \-\-data=]
+ [\-\-gui\-address=] [\-\-gui\-apikey=]
+ [\-\-help]
+ [command options...] [arguments...]
.ft P
.fi
.UNINDENT
@@ -66,39 +87,69 @@ few log messages.
.SH OPTIONS
.INDENT 0.0
.TP
-.B \-audit
+.B \-\-allow\-newer\-config
+Try loading a config file written by a newer program version, instead of
+failing immediately.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-audit
Write events to timestamped file \fBaudit\-YYYYMMDD\-HHMMSS.log\fP\&.
.UNINDENT
.INDENT 0.0
.TP
-.B \-auditfile=
+.B \-\-auditfile=
Use specified file or stream (\fB"\-"\fP for stdout, \fB"\-\-"\fP for stderr) for
audit events, rather than the timestamped default file name.
.UNINDENT
.INDENT 0.0
.TP
-.B \-browser\-only
+.B \-\-browser\-only
Open the web UI in a browser for an already running Syncthing instance.
.UNINDENT
.INDENT 0.0
.TP
-.B \-device\-id
+.B \-\-device\-id
Print device ID to command line.
.UNINDENT
.INDENT 0.0
.TP
-.B \-generate=
+.B \-\-generate=
Generate key and config in specified dir, then exit.
.UNINDENT
.INDENT 0.0
.TP
-.B \-gui\-address=
+.B \-\-gui\-address=
Override GUI listen address. Set this to an address (\fB0.0.0.0:8384\fP)
or file path (\fB/var/run/st.sock\fP, for UNIX sockets).
.UNINDENT
.INDENT 0.0
.TP
-.B \-home=
+.B \-\-gui\-apikey=
+Override the API key needed to access the GUI / REST API.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-gui\-password=
+Specify new GUI authentication password, to update the config file. Read
+from the standard input stream if only a single dash (\fB\-\fP) is given. The
+password is hashed before writing to the config file. As a special case,
+giving the existing password hash as password will leave it untouched.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-gui\-user=
+Specify new GUI authentication user name, to update the config file.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-help, \-h
+Show help text about command line usage. Context\-sensitive depending on the
+given subcommand.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-home=
Set common configuration and data directory. The default configuration
directory is \fB$HOME/.config/syncthing\fP (Unix\-like),
\fB$HOME/Library/Application Support/Syncthing\fP (Mac) and
@@ -106,26 +157,26 @@ directory is \fB$HOME/.config/syncthing\fP (Unix\-like),
.UNINDENT
.INDENT 0.0
.TP
-.B \-config=
-Set configuration directory. Alternative to \fB\-home\fP and must be used
-together with \fB\-data\fP\&.
+.B \-\-config=
+Set configuration directory. Alternative to \fB\-\-home\fP and must be used
+together with \fB\-\-data\fP\&.
.UNINDENT
.INDENT 0.0
.TP
-.B \-data=
-Set data (e.g. database) directory. Alternative to \fB\-home\fP and must be used
-together with \fB\-config\fP\&.
+.B \-\-data=
+Set data (e.g. database) directory. Alternative to \fB\-\-home\fP and must be used
+together with \fB\-\-config\fP\&.
.UNINDENT
.INDENT 0.0
.TP
-.B \-logfile=
+.B \-\-logfile=
Set destination filename for logging (use \fB"\-"\fP for stdout, which is the
default option).
.UNINDENT
.INDENT 0.0
.TP
-.B \-logflags=
-Select information in log line prefix. The \fB\-logflags\fP value is a sum of
+.B \-\-logflags=
+Select information in log line prefix. The \fB\-\-logflags\fP value is a sum of
the following:
.INDENT 7.0
.IP \(bu 2
@@ -140,40 +191,63 @@ the following:
16: Short filename
.UNINDENT
.sp
-To prefix each log line with date and time, set \fB\-logflags=3\fP (1 + 2 from
+To prefix each log line with date and time, set \fB\-\-logflags=3\fP (1 + 2 from
above). The value 0 is used to disable all of the above. The default is to
show time only (2).
.UNINDENT
.INDENT 0.0
.TP
-.B \-no\-browser
+.B \-\-log\-max\-old\-files=
+Number of old files to keep (zero to keep only current). Applies only when
+log rotation is enabled through \fB\-\-log\-max\-size\fP\&.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-log\-max\-size=
+Maximum size of any log file (zero to disable log rotation).
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-browser
Do not start a browser.
.UNINDENT
.INDENT 0.0
.TP
-.B \-no\-console
+.B \-\-no\-console
Hide the console window. (On Windows only)
.UNINDENT
.INDENT 0.0
.TP
-.B \-no\-restart
+.B \-\-no\-default\-folder
+Don’t create a default folder when generating an initial configuration /
+starting for the first time.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-restart
Do not restart Syncthing when it exits. The monitor process will still run
to handle crashes and writing to logfiles (if configured to).
.UNINDENT
.INDENT 0.0
.TP
-.B \-paths
+.B \-\-no\-upgrade
+Disable automatic upgrades. Equivalent to the \fBSTNOUPGRADE\fP environment
+variable, see below.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-paths
Print the paths used for configuration, keys, database, GUI overrides,
default sync folder and the log file.
.UNINDENT
.INDENT 0.0
.TP
-.B \-paused
+.B \-\-paused
Start with all devices and folders paused.
.UNINDENT
.INDENT 0.0
.TP
-.B \-reset\-database
+.B \-\-reset\-database
Reset the database, forcing a full rescan and resync. Create \fI\&.stfolder\fP
folders in each sync folder if they do not already exist. \fBCaution\fP:
Ensure that all sync folders which are mountpoints are already mounted.
@@ -182,39 +256,77 @@ contains older versions.
.UNINDENT
.INDENT 0.0
.TP
-.B \-reset\-deltas
+.B \-\-reset\-deltas
Reset delta index IDs, forcing a full index exchange.
.UNINDENT
.INDENT 0.0
.TP
-.B \-unpaused
+.B \-\-skip\-port\-probing
+Don’t try to find unused random ports for the GUI and listen address when
+generating an initial configuration / starting for the first time.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-unpaused
Start with all devices and folders unpaused.
.UNINDENT
.INDENT 0.0
.TP
-.B \-upgrade
+.B \-\-upgrade
Perform upgrade.
.UNINDENT
.INDENT 0.0
.TP
-.B \-upgrade\-check
+.B \-\-upgrade\-check
Check for available upgrade.
.UNINDENT
.INDENT 0.0
.TP
-.B \-upgrade\-to=
+.B \-\-upgrade\-to=
Force upgrade directly from specified URL.
.UNINDENT
.INDENT 0.0
.TP
-.B \-verbose
+.B \-\-verbose
Print verbose log output.
.UNINDENT
.INDENT 0.0
.TP
-.B \-version
+.B \-\-version
Show version.
.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-to=
+Destination directory where files should be stored after decryption.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-verify\-only
+Don’t write decrypted files to disk (but verify plaintext hashes).
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-password=
+Folder password for decryption / verification. Can be passed through the
+\fBFOLDER_PASSWORD\fP environment variable instead to avoid recording in a
+shell’s history buffer or sniffing from the running processes list.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-folder\-id=
+Folder ID of the encrypted folder, if it cannot be determined automatically.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-token\-path=
+Path to the token file within the folder (used to determine folder ID).
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-continue
+Continue processing next file in case of error, instead of aborting.
+.UNINDENT
.SH EXIT CODES
.INDENT 0.0
.TP
@@ -237,6 +349,62 @@ Upgrading
Exit codes over 125 are usually returned by the shell/binary loader/default
signal handler. Exit codes over 128+N on Unix usually represent the signal which
caused the process to exit. For example, \fB128 + 9 (SIGKILL) = 137\fP\&.
+.SH SUBCOMMANDS
+.sp
+The command line syntax actually supports different modes of operation through
+several subcommands, specified as the first argument. If omitted, the default
+\fBserve\fP is assumed.
+.sp
+The initial setup of a device ID and default configuration can be called
+explicitly with the \fBgenerate\fP subcommand. It can also update the configured
+GUI authentication credentials, without going through the REST API. An existing
+device certificate is left untouched. If the configuration file already exists,
+it is validated and updated to the latest configuration schema, including adding
+default values for any new options.
+.sp
+The \fBdecrypt\fP subcommand is used in conjunction with untrusted (encrypted)
+devices, see the relevant section on decryption for
+details. It does not depend on Syncthing to be running, but works on offline
+data.
+.sp
+To work with the REST API for debugging or automating things in Syncthing, the
+\fBcli\fP subcommand provides easy access to individual features. It basically
+saves the hassle of handling HTTP connections and API authentication.
+.sp
+The available subcommands are grouped into several nested hierarchies and some
+parts dynamically generated from the running Syncthing instance. On every
+level, the \fB\-\-help\fP option lists the available properties, actions and
+commands for the user to discover interactively. The top\-level groups are:
+.INDENT 0.0
+.TP
+.B config
+Access the live configuration in a running instance over the REST API to
+retrieve (get) or update (set) values in a fine\-grained way. The hierarchy
+is based on the same structure as used in the JSON / XML representations.
+.TP
+.B show
+Show system properties and status of a running instance. The output is
+passed on directly from the REST API response and therefore requires parsing
+JSON format.
+.TP
+.B operations
+Control the overall program operation such as restarting or handling
+upgrades, as well as triggering some actions on a per\-folder basis.
+.TP
+.B errors
+Examine pending error conditions that need attention from the user, or
+acknowledge (clear) them.
+.TP
+.B debug
+Various tools to aid in diagnosing problems or collection information for
+bug reports. Some of these commands access the database directly and can
+therefore only work when Syncthing is not running.
+.TP
+.B \fB\-\fP (a single dash)
+Reads subsequent commands from the standard input stream, without needing to
+call the \fBsyncthing cli\fP command over and over. Exits on any invalid
+command or when EOF (end\-of\-file) is received.
+.UNINDENT
.SH PROXIES
.sp
Syncthing can use a SOCKS, HTTP, or HTTPS proxy to talk to the outside
@@ -267,7 +435,7 @@ path expansion may not be supported.
Used to increase the debugging verbosity in specific or all facilities,
generally mapping to a Go package. Enabling any of these also enables
microsecond timestamps, file names plus line numbers. Enter a
-comma\-separated string of facilities to trace. \fBsyncthing \-help\fP always
+comma\-separated string of facilities to trace. \fBsyncthing \-\-help\fP always
outputs an up\-to\-date list. The valid facility strings are:
.INDENT 7.0
.TP
@@ -388,13 +556,14 @@ increases.
.TP
.B STNODEFAULTFOLDER
Don’t create a default folder when starting for the first time. This
-variable will be ignored anytime after the first run.
+variable will be ignored anytime after the first run. Equivalent to the
+\fB\-\-no\-default\-folder\fP flag.
.TP
.B STNORESTART
-Equivalent to the \fB\-no\-restart\fP flag
+Equivalent to the \fB\-\-no\-restart\fP flag.
.TP
.B STNOUPGRADE
-Disable automatic upgrades.
+Disable automatic upgrades. Equivalent to the \fB\-\-no\-upgrade\fP flag.
.TP
.B STPROFILER
Set to a listen address such as “127.0.0.1:9090” to start the profiler with
From 1242ac74ab77d7476b7d5d641f8bfbd5b5c60948 Mon Sep 17 00:00:00 2001
From: Jakob Borg
Date: Wed, 12 Jan 2022 21:56:21 +0100
Subject: [PATCH 042/220] github: Make docs commits as release bot
---
.github/workflows/update-docs-translations.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/update-docs-translations.yaml b/.github/workflows/update-docs-translations.yaml
index ff9a0135e64..2b758ba7bb0 100644
--- a/.github/workflows/update-docs-translations.yaml
+++ b/.github/workflows/update-docs-translations.yaml
@@ -18,8 +18,8 @@ jobs:
go-version: ^1.17.6
- run: |
set -euo pipefail
- git config --global user.name 'Syncthing Automation'
- git config --global user.email 'automation@syncthing.net'
+ git config --global user.name 'Syncthing Release Automation'
+ git config --global user.email 'release@syncthing.net'
bash build.sh translate
bash build.sh prerelease
git push
From 40bb52fdd86ac96c1a5cfbf4c962b40981154aa7 Mon Sep 17 00:00:00 2001
From: Ryan Qian
Date: Thu, 13 Jan 2022 16:57:23 +0800
Subject: [PATCH 043/220] build: Add an option to specify output dir for
crosscompiling all (#8109)
When GOBIN is set, 'go install' cannot install cross-compilied binaries.
To satisfy cross-compilation, it's necessary to add the '-o' to build
target, otherwise 'go build' will discarding the resulting objects when
compiling multiple packages.
Signed-off-by: bekcpear
---
build.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/build.go b/build.go
index c2e10593982..7888c834d27 100644
--- a/build.go
+++ b/build.go
@@ -47,6 +47,7 @@ var (
cc string
run string
benchRun string
+ buildOut string
debugBinary bool
coverage bool
long bool
@@ -374,6 +375,7 @@ func parseFlags() {
flag.StringVar(&run, "run", "", "Specify which tests to run")
flag.StringVar(&benchRun, "bench", "", "Specify which benchmarks to run")
flag.BoolVar(&withNextGenGUI, "with-next-gen-gui", withNextGenGUI, "Also build 'newgui'")
+ flag.StringVar(&buildOut, "build-out", "", "Set the '-o' value for 'go build'")
flag.Parse()
}
@@ -506,6 +508,9 @@ func build(target target, tags []string) {
}
args := []string{"build", "-v"}
+ if buildOut != "" {
+ args = append(args, "-o", buildOut)
+ }
args = appendParameters(args, tags, target.buildPkgs...)
runPrint(goCmd, args...)
}
From 21d04b895a7b3ee0803e36e4f0b3f5e8e6cf1379 Mon Sep 17 00:00:00 2001
From: Simon Frei
Date: Thu, 13 Jan 2022 23:38:21 +0100
Subject: [PATCH 044/220] lib, gui: Default ignores for new folders (fixes
#7428) (#7530)
---
cmd/syncthing/cli/client.go | 29 +-
cmd/syncthing/cli/operations.go | 36 ++
.../syncthing/core/syncthingController.js | 295 +++++++++++------
.../syncthing/core/uniqueFolderDirective.js | 2 +-
.../syncthing/core/validDeviceidDirective.js | 2 +-
.../syncthing/device/editDeviceModalView.html | 12 +-
.../syncthing/folder/editFolderModalView.html | 97 +++---
lib/api/api.go | 1 +
lib/api/confighandler.go | 22 ++
lib/config/config.go | 6 +
lib/config/config.pb.go | 308 +++++++++++++++---
lib/config/config_test.go | 3 +
lib/config/mocks/mocked_wrapper.go | 65 ++++
lib/config/wrapper.go | 8 +
lib/model/model.go | 15 +-
lib/model/model_test.go | 6 +-
proto/lib/config/config.proto | 9 +-
17 files changed, 708 insertions(+), 208 deletions(-)
diff --git a/cmd/syncthing/cli/client.go b/cmd/syncthing/cli/client.go
index 539192b3e56..75923b42158 100644
--- a/cmd/syncthing/cli/client.go
+++ b/cmd/syncthing/cli/client.go
@@ -10,8 +10,10 @@ import (
"bytes"
"context"
"crypto/tls"
+ "encoding/json"
"errors"
"fmt"
+ "io"
"net"
"net/http"
"strings"
@@ -25,6 +27,7 @@ import (
type APIClient interface {
Get(url string) (*http.Response, error)
Post(url, body string) (*http.Response, error)
+ PutJSON(url string, o interface{}) (*http.Response, error)
}
type apiClient struct {
@@ -118,20 +121,36 @@ func (c *apiClient) Do(req *http.Request) (*http.Response, error) {
return resp, checkResponse(resp)
}
-func (c *apiClient) Get(url string) (*http.Response, error) {
- request, err := http.NewRequest("GET", c.Endpoint()+"rest/"+url, nil)
+func (c *apiClient) Request(url, method string, r io.Reader) (*http.Response, error) {
+ request, err := http.NewRequest(method, c.Endpoint()+"rest/"+url, r)
if err != nil {
return nil, err
}
return c.Do(request)
}
-func (c *apiClient) Post(url, body string) (*http.Response, error) {
- request, err := http.NewRequest("POST", c.Endpoint()+"rest/"+url, bytes.NewBufferString(body))
+func (c *apiClient) RequestString(url, method, data string) (*http.Response, error) {
+ return c.Request(url, method, bytes.NewBufferString(data))
+}
+
+func (c *apiClient) RequestJSON(url, method string, o interface{}) (*http.Response, error) {
+ data, err := json.Marshal(o)
if err != nil {
return nil, err
}
- return c.Do(request)
+ return c.Request(url, method, bytes.NewBuffer(data))
+}
+
+func (c *apiClient) Get(url string) (*http.Response, error) {
+ return c.RequestString(url, "GET", "")
+}
+
+func (c *apiClient) Post(url, body string) (*http.Response, error) {
+ return c.RequestString(url, "POST", body)
+}
+
+func (c *apiClient) PutJSON(url string, o interface{}) (*http.Response, error) {
+ return c.RequestJSON(url, "PUT", o)
}
var errNotFound = errors.New("invalid endpoint or API call")
diff --git a/cmd/syncthing/cli/operations.go b/cmd/syncthing/cli/operations.go
index 347a99005ac..bef49cc92b7 100644
--- a/cmd/syncthing/cli/operations.go
+++ b/cmd/syncthing/cli/operations.go
@@ -7,8 +7,12 @@
package cli
import (
+ "bufio"
"fmt"
+ "path/filepath"
+ "github.com/syncthing/syncthing/lib/config"
+ "github.com/syncthing/syncthing/lib/fs"
"github.com/urfave/cli"
)
@@ -38,6 +42,12 @@ var operationCommand = cli.Command{
ArgsUsage: "[folder id]",
Action: expects(1, foldersOverride),
},
+ {
+ Name: "default-ignores",
+ Usage: "Set the default ignores (config) from a file",
+ ArgsUsage: "path",
+ Action: expects(1, setDefaultIgnores),
+ },
},
}
@@ -74,3 +84,29 @@ func foldersOverride(c *cli.Context) error {
}
return fmt.Errorf("Folder " + rid + " not found")
}
+
+func setDefaultIgnores(c *cli.Context) error {
+ client, err := getClientFactory(c).getClient()
+ if err != nil {
+ return err
+ }
+ dir, file := filepath.Split(c.Args()[0])
+ filesystem := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
+
+ fd, err := filesystem.Open(file)
+ if err != nil {
+ return err
+ }
+ scanner := bufio.NewScanner(fd)
+ var lines []string
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+ fd.Close()
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+
+ _, err = client.PutJSON("config/defaults/ignores", config.Ignores{Lines: lines})
+ return err
+}
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index 69200735181..1e6e20ef05d 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -58,6 +58,9 @@ angular.module('syncthing.core')
text: '',
error: null,
disabled: false,
+ originalLines: [],
+ defaultLines: [],
+ saved: false,
};
resetRemoteNeed();
@@ -409,8 +412,14 @@ angular.module('syncthing.core')
console.log("FolderScanProgress", data);
});
+ // May be called through .error with the presented arguments, or through
+ // .catch with the http response object containing the same arguments.
$scope.emitHTTPError = function (data, status, headers, config) {
- $scope.$emit('HTTPError', { data: data, status: status, headers: headers, config: config });
+ var out = data;
+ if (data && !data.data) {
+ out = { data: data, status: status, headers: headers, config: config };
+ }
+ $scope.$emit('HTTPError', out);
};
var debouncedFuncs = {};
@@ -741,7 +750,7 @@ angular.module('syncthing.core')
}
function shouldSetDefaultFolderPath() {
- return $scope.config.defaults.folder.path && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine && !$scope.editingDefaults;
+ return $scope.config.defaults.folder.path && $scope.folderEditor.folderPath.$pristine && $scope.currentFolder._editing == "add";
}
function resetRemoteNeed() {
@@ -750,7 +759,6 @@ angular.module('syncthing.core')
$scope.remoteNeedDevice = undefined;
}
-
function setDefaultTheme() {
if (!document.getElementById("fallback-theme-css")) {
@@ -767,13 +775,9 @@ angular.module('syncthing.core')
}
}
- function saveIgnores(ignores, cb) {
- $http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
+ function saveIgnores(ignores) {
+ return $http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
ignore: ignores
- }).success(function () {
- if (cb) {
- cb();
- }
});
};
@@ -1268,8 +1272,9 @@ angular.module('syncthing.core')
if (cfg) {
cfg.paused = pause;
$scope.config.folders = folderList($scope.folders);
- $scope.saveConfig();
+ return $scope.saveConfig();
}
+ return $q.when();
};
$scope.showListenerStatus = function () {
@@ -1421,18 +1426,14 @@ angular.module('syncthing.core')
});
};
- $scope.saveConfig = function (callback) {
+ $scope.saveConfig = function () {
var cfg = JSON.stringify($scope.config);
var opts = {
headers: {
'Content-Type': 'application/json'
}
};
- $http.put(urlbase + '/config', cfg, opts).finally(refreshConfig).then(function() {
- if (callback) {
- callback();
- }
- }, $scope.emitHTTPError);
+ return $http.put(urlbase + '/config', cfg, opts).finally(refreshConfig).catch($scope.emitHTTPError);
};
$scope.urVersions = function () {
@@ -1512,7 +1513,7 @@ angular.module('syncthing.core')
// here as well...
$scope.devices = deviceMap($scope.config.devices);
- $scope.saveConfig(function () {
+ $scope.saveConfig.then(function () {
if (themeChanged) {
document.location.reload(true);
}
@@ -1578,11 +1579,11 @@ angular.module('syncthing.core')
}
$scope.editDeviceModalTitle = function() {
- if ($scope.editingDefaults) {
+ if ($scope.editingDeviceDefaults()) {
return $translate.instant("Edit Device Defaults");
}
var title = '';
- if ($scope.editingExisting) {
+ if ($scope.editingDeviceExisting()) {
title += $translate.instant("Edit Device");
} else {
title += $translate.instant("Add Device");
@@ -1595,16 +1596,23 @@ angular.module('syncthing.core')
};
$scope.editDeviceModalIcon = function() {
- if ($scope.editingDefaults || $scope.editingExisting) {
+ if ($scope.has(["existing", "defaults"], $scope.currentDevice._editing)) {
return 'fas fa-pencil-alt';
}
return 'fas fa-desktop';
};
+ $scope.editingDeviceDefaults = function() {
+ return $scope.currentDevice._editing == 'defaults';
+ }
+
+ $scope.editingDeviceExisting = function() {
+ return $scope.currentDevice._editing == 'existing';
+ }
+
$scope.editDeviceExisting = function (deviceCfg) {
$scope.currentDevice = $.extend({}, deviceCfg);
- $scope.editingExisting = true;
- $scope.editingDefaults = false;
+ $scope.currentDevice._editing = "existing";
$scope.willBeReintroducedBy = undefined;
if (deviceCfg.introducedBy) {
var introducerDevice = $scope.devices[deviceCfg.introducedBy];
@@ -1633,7 +1641,7 @@ angular.module('syncthing.core')
$scope.editDeviceDefaults = function () {
$http.get(urlbase + '/config/defaults/device').then(function (p) {
$scope.currentDevice = p.data;
- $scope.editingDefaults = true;
+ $scope.currentDevice._editing = "defaults";
editDeviceModal();
}, $scope.emitHTTPError);
};
@@ -1671,8 +1679,7 @@ angular.module('syncthing.core')
$scope.currentDevice = p.data;
$scope.currentDevice.name = name;
$scope.currentDevice.deviceID = deviceID;
- $scope.editingExisting = false;
- $scope.editingDefaults = false;
+ $scope.currentDevice._editing = "add";
initShareEditing('device');
$scope.currentSharing.unrelated = $scope.folderList();
editDeviceModal();
@@ -1682,7 +1689,7 @@ angular.module('syncthing.core')
$scope.deleteDevice = function () {
$('#editDevice').modal('hide');
- if (!$scope.editingExisting) {
+ if ($scope.currentDevice._editing != "existing") {
return;
}
@@ -1705,13 +1712,13 @@ angular.module('syncthing.core')
return x.trim();
});
delete $scope.currentDevice._addressesStr;
- if ($scope.editingDefaults) {
+ if ($scope.currentDevice._editing == "defaults") {
$scope.config.defaults.device = $scope.currentDevice;
} else {
setDeviceConfig();
}
delete $scope.currentSharing;
- delete $scope.currentDevice;
+ $scope.currentDevice = {};
$scope.saveConfig();
};
@@ -1955,20 +1962,34 @@ angular.module('syncthing.core')
$('#folder-ignores textarea').focus();
}
}).one('hidden.bs.modal', function () {
- window.location.hash = "";
- $scope.currentFolder = {};
+ var p = $q.when();
+ // If the modal was closed default patterns should still apply
+ if ($scope.currentFolder._editing == "add-ignores" && !$scope.ignores.saved && $scope.ignores.defaultLines) {
+ p = saveFolderAddIgnores($scope.currentFolder.id, true);
+ }
+ p.then(function () {
+ window.location.hash = "";
+ $scope.currentFolder = {};
+ $scope.ignores = {};
+ });
});
};
$scope.editFolderModalTitle = function() {
- if ($scope.editingDefaults) {
+ if ($scope.editingFolderDefaults()) {
return $translate.instant("Edit Folder Defaults");
}
var title = '';
- if ($scope.editingExisting) {
- title += $translate.instant("Edit Folder");
- } else {
- title += $translate.instant("Add Folder");
+ switch ($scope.currentFolder._editing) {
+ case "existing":
+ title = $translate.instant("Edit Folder");
+ break;
+ case "add":
+ title = $translate.instant("Add Folder");
+ break;
+ case "add-ignores":
+ title = $translate.instant("Set Ignores on Added Folder");
+ break;
}
if ($scope.currentFolder.id !== '') {
title += ' (' + $scope.folderLabel($scope.currentFolder.id) + ')';
@@ -1977,12 +1998,20 @@ angular.module('syncthing.core')
};
$scope.editFolderModalIcon = function() {
- if ($scope.editingDefaults || $scope.editingExisting) {
+ if ($scope.has(["existing", "defaults"], $scope.currentFolder._editing)) {
return 'fas fa-pencil-alt';
}
return 'fas fa-folder';
};
+ $scope.editingFolderDefaults = function() {
+ return $scope.currentFolder._editing == 'defaults';
+ }
+
+ $scope.editingFolderExisting = function() {
+ return $scope.currentFolder._editing == 'existing';
+ }
+
function editFolder(initialTab) {
if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
@@ -2033,39 +2062,60 @@ angular.module('syncthing.core')
};
$scope.editFolderExisting = function(folderCfg, initialTab) {
- $scope.editingExisting = true;
- $scope.editingDefaults = false;
$scope.currentFolder = angular.copy(folderCfg);
+ $scope.currentFolder._editing = "existing";
+ editFolderLoadIgnores();
+ editFolder(initialTab);
+ };
+ function editFolderLoadingIgnores() {
$scope.ignores.text = 'Loading...';
$scope.ignores.error = null;
$scope.ignores.disabled = true;
- $http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
- .success(function (data) {
- $scope.currentFolder.ignores = data.ignore || [];
- $scope.ignores.text = $scope.currentFolder.ignores.join('\n');
- $scope.ignores.error = data.error;
- $scope.ignores.disabled = false;
- })
- .error(function (err) {
- $scope.ignores.text = $translate.instant("Failed to load ignore patterns.");
- $scope.emitHTTPError(err);
- });
+ }
- editFolder(initialTab);
+ function editFolderGetIgnores() {
+ return $http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
+ .then(function(r) {
+ return r.data;
+ }, function (response) {
+ $scope.ignores.text = $translate.instant("Failed to load ignore patterns.");
+ return $q.reject(response);
+ });
};
+ function editFolderLoadIgnores() {
+ editFolderLoadingIgnores();
+ return editFolderGetIgnores().then(editFolderInitIgnores, $scope.emitHTTPError);
+ }
+
$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);
+ $q.all([
+ $http.get(urlbase + '/config/defaults/folder').then(function (response) {
+ $scope.currentFolder = response.data;
+ $scope.currentFolder._editing = "defaults";
+ }),
+ getDefaultIgnores().then(editFolderInitIgnores),
+ ]).then(editFolder, $scope.emitHTTPError);
};
+ function getDefaultIgnores() {
+ return $http.get(urlbase + '/config/defaults/ignores').then(function (r) {
+ return r.data.lines;
+ });
+ }
+
+ function editFolderInitIgnores(data) {
+ $scope.ignores.originalLines = data.ignore || [];
+ setIgnoresText(data.ignore);
+ $scope.ignores.error = data.error;
+ $scope.ignores.disabled = false;
+ }
+
+ function setIgnoresText(lines) {
+ $scope.ignores.text = lines ? lines.join('\n') : "";
+ }
+
$scope.selectAllSharedDevices = function (state) {
var devices = $scope.currentSharing.shared;
for (var i = 0; i < devices.length; i++) {
@@ -2093,9 +2143,6 @@ angular.module('syncthing.core')
$scope.addFolderAndShare = function (folderID, pendingFolder, device) {
addFolderInit(folderID).then(function() {
- $scope.currentFolder.viewFlags = {
- importFromOtherDevice: true
- };
$scope.currentSharing.selected[device] = true;
$scope.currentFolder.label = pendingFolder.offeredBy[device].label;
for (var k in pendingFolder.offeredBy) {
@@ -2110,19 +2157,16 @@ angular.module('syncthing.core')
};
function addFolderInit(folderID) {
- $scope.editingExisting = false;
- $scope.editingDefaults = false;
- return $http.get(urlbase + '/config/defaults/folder').then(function(p) {
- $scope.currentFolder = p.data;
+ return $http.get(urlbase + '/config/defaults/folder').then(function (response) {
+ $scope.currentFolder = response.data;
+ $scope.currentFolder._editing = "add";
$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;
+ // Ignores don't need to be initialized here, as that happens in
+ // a second step if the user indicates in the creation modal
+ // that they want to set ignores
}, $scope.emitHTTPError);
}
@@ -2142,7 +2186,14 @@ angular.module('syncthing.core')
};
$scope.saveFolder = function () {
- $('#editFolder').modal('hide');
+ if ($scope.currentFolder._editing == "add-ignores") {
+ // On modal being hidden without clicking save, the defaults will be saved.
+ $scope.ignores.saved = true;
+ saveFolderAddIgnores($scope.currentFolder.id);
+ hideFolderModal();
+ return;
+ }
+
var folderCfg = angular.copy($scope.currentFolder);
$scope.currentSharing.selected[$scope.myID] = true;
var newDevices = [];
@@ -2191,45 +2242,89 @@ angular.module('syncthing.core')
}
delete folderCfg._guiVersioning;
- if ($scope.editingDefaults) {
+ if ($scope.currentFolder._editing == "defaults") {
+ hideFolderModal();
+ $scope.config.defaults.ignores.lines = ignoresArray();
$scope.config.defaults.folder = folderCfg;
$scope.saveConfig();
- } else {
- saveFolderExisting(folderCfg);
+ return;
}
- };
- 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
- if (ignores.length === 1 && ignores[0] === "") {
- ignores = [];
- }
- if (!$scope.editingExisting && ignores.length) {
+ // This is a new folder where ignores should apply before it first starts.
+ if ($scope.currentFolder._addIgnores) {
folderCfg.paused = true;
- };
-
+ }
$scope.folders[folderCfg.id] = folderCfg;
$scope.config.folders = folderList($scope.folders);
- function arrayEquals(a, b) {
- return a.length === b.length && a.every(function(v, i) { return v === b[i] });
+ if ($scope.currentFolder._editing == "existing") {
+ hideFolderModal();
+ saveFolderIgnoresExisting();
+ $scope.saveConfig();
+ return;
}
- if (ignoresLoaded && $scope.editingExisting && !arrayEquals(ignores, folderCfg.ignores)) {
- saveIgnores(ignores);
- };
+ // No ignores to be set on the new folder, save directly.
+ if (!$scope.currentFolder._addIgnores) {
+ hideFolderModal();
+ $scope.saveConfig();
+ return;
+ }
- $scope.saveConfig(function () {
- if (!$scope.editingExisting && ignores.length) {
- saveIgnores(ignores, function () {
- $scope.setFolderPause(folderCfg.id, false);
+ // Add folder (paused), load existing ignores and if there are none,
+ // load default ignores, then let the user edit them.
+ $scope.saveConfig().then(function() {
+ editFolderLoadingIgnores();
+ $scope.currentFolder._editing = "add-ignores";
+ $('.nav-tabs a[href="#folder-ignores"]').tab('show');
+ return editFolderGetIgnores();
+ }).then(function(data) {
+ // Error getting ignores -> leave error message.
+ if (!data) {
+ return;
+ }
+ if ((data.ignore && data.ignore.length > 0) || data.error) {
+ editFolderInitIgnores(data);
+ } else {
+ getDefaultIgnores().then(function(lines) {
+ setIgnoresText(lines);
+ $scope.ignores.defaultLines = lines;
+ $scope.ignores.disabled = false;
});
}
+ }, $scope.emitHTTPError);
+ };
+
+ function saveFolderIgnoresExisting() {
+ if ($scope.ignores.disabled) {
+ return;
+ }
+ var ignores = ignoresArray();
+
+ function arrayDiffers(a, b) {
+ return !a !== !b || a.length !== b.length || a.some(function(v, i) { return v !== b[i]; });
+ }
+ if (arrayDiffers(ignores, $scope.ignores.originalLines)) {
+ return saveIgnores(ignores);
+ };
+ }
+
+ function saveFolderAddIgnores(folderID, useDefault) {
+ var ignores = useDefault ? $scope.ignores.defaultLines : ignoresArray();
+ return saveIgnores(ignores).then(function () {
+ return $scope.setFolderPause(folderID, $scope.currentFolder.paused);
});
};
+ function ignoresArray() {
+ var ignores = $scope.ignores.text.split('\n');
+ // Split always returns a minimum 1-length array even for no patterns
+ if (ignores.length === 1 && ignores[0] === "") {
+ ignores = [];
+ }
+ return ignores;
+ }
+
$scope.ignoreFolder = function (device, folderID, offeringDevice) {
var ignoredFolder = {
id: folderID,
@@ -2282,8 +2377,8 @@ angular.module('syncthing.core')
};
$scope.deleteFolder = function (id) {
- $('#editFolder').modal('hide');
- if (!$scope.editingExisting) {
+ hideFolderModal();
+ if ($scope.currentFolder._editing != "existing") {
return;
}
@@ -2295,6 +2390,10 @@ angular.module('syncthing.core')
$scope.saveConfig();
};
+ function hideFolderModal() {
+ $('#editFolder').modal('hide');
+ }
+
function resetRestoreVersions() {
$scope.restoreVersions = {
folder: null,
@@ -2839,6 +2938,10 @@ angular.module('syncthing.core')
return Object.keys(dict).length;
};
+ $scope.has = function (array, element) {
+ return array.indexOf(element) >= 0;
+ };
+
$scope.dismissNotification = function (id) {
var idx = $scope.config.options.unackedNotificationIDs.indexOf(id);
if (idx > -1) {
diff --git a/gui/default/syncthing/core/uniqueFolderDirective.js b/gui/default/syncthing/core/uniqueFolderDirective.js
index 18ae561401f..240bd3707b8 100644
--- a/gui/default/syncthing/core/uniqueFolderDirective.js
+++ b/gui/default/syncthing/core/uniqueFolderDirective.js
@@ -4,7 +4,7 @@ angular.module('syncthing.core')
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
- if (scope.editingExisting) {
+ if (scope.currentFolder._editing != "add") {
// we shouldn't validate
ctrl.$setValidity('uniqueFolder', true);
} else if (scope.folders.hasOwnProperty(viewValue)) {
diff --git a/gui/default/syncthing/core/validDeviceidDirective.js b/gui/default/syncthing/core/validDeviceidDirective.js
index ca44ccc4542..8c04564888c 100644
--- a/gui/default/syncthing/core/validDeviceidDirective.js
+++ b/gui/default/syncthing/core/validDeviceidDirective.js
@@ -4,7 +4,7 @@ angular.module('syncthing.core')
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
- if (scope.editingExisting) {
+ if (scope.currentDevice._editing != "add") {
// we shouldn't validate
ctrl.$setValidity('validDeviceid', true);
} else {
diff --git a/gui/default/syncthing/device/editDeviceModalView.html b/gui/default/syncthing/device/editDeviceModalView.html
index 0b772c4373f..6ae45382195 100644
--- a/gui/default/syncthing/device/editDeviceModalView.html
+++ b/gui/default/syncthing/device/editDeviceModalView.html
@@ -3,14 +3,14 @@