Compare commits

...
10 Commits
Author SHA1 Message Date
Jakob Borg 058bcd7334 fix: open GUI when relaunched instead of printing error (fixes #10727) (#10852)
This is primarily to improve the experience in environments that start
Syncthing from a graphical environment, e.g., Windows.

When already running, instead of printing an error we open the GUI. If
the GUI is not available (but the lockfile indicates we are running),
print an error and exit.

If --no-browser is given or STNOBROWSER is set, act like before.

Closes #10736. Marking as `fix` because this does not deserve to trigger
a minor release on its own.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-11 11:17:09 +00:00
Syncthing Release Automation 42ea7231c5 chore(gui, man, authors): update docs, translations, and contributors 2026-08-10 04:17:57 +00:00
Jakob Borg 328d910aee fix(api): correctly return metrics, support bundle (fixes #10847) (#10849)
Our faked request wasn't good enough; improve it, adding a test.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-05 18:35:17 +00:00
Jakob Borg d7df27a367 fix(model): correctly handle receive-only changed directories (fixes #8004) (#10843)
When adding a folder in receive-only mode where the contents were
already identical to a remote device, directories would remain as
locally changed when everything else had consolidated. The reason this
happened is that we only did the matching between locally changed files
and their global equivalent for changed items, but directories are
typically not "changed" much as we don't track their mtime, so they
wouldn't pass through this stage when scanning.

Now, instead, do the check when we're anyway walking all the files in
phase two of scanning. This catches all cases of identical items, files
or directories, regardless of how they came to be in the index.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-05 20:23:53 +02:00
Jakob Borg 8ea09c0094 chore: style fixes from go fix (#10846)
Just `go fix ./...`

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-05 20:23:22 +02:00
Jakob Borg 946e2b83a1 fix(db, model): better handle db connections, puller concurrency, avoid deadlock (fixes #10841) (#10842)
There is a danger of deadlock wherever we have database operations
inside a database iterator. The iterator itself pins a connection, so
the nested operations need another connection; if we've reached
maxOpenConns then it blocks until a connection is released. If all
connections are consumed by such iterators then none can make progress.

Luckily, we have limited concurrency for most such iterator loops. They
are part of scanning, pulling, reverting, etc where there is only ever
one such routine per folder. The exception is block reuse in the copier
routine, which is limited by the `Copiers` setting per folder. This
could in practice deadlock since you could set copiers to eight and end
up with six `AllLocalBlocksWithHash` iterators when `maxDBConns=6`, all
of which need to make additional database calls inside the loop.

This PR fixes the problem twice;

- The problematic loop does not need to be reentrant. The set of blocks
that may be returned by the iterator is finite so we can easily just
collect them to a slice before we start processing them. This avoids the
problem entirely.
- We do not need to limit database connections as strictly as we
currently do. Increase the maximum allowed, while reducing the number of
held-open idle connections slightly. This is not an exact science, but
ideally we want "most" operations to be able to use the pinned
connections to avoid cache churn. Most operations are short lived
queries, or single-goroutine iterators with short lived queries inside,
so four connections seems like it should usually be enough. 🤷
- Additionally, set a cap on Copiers. Currently you could set it to an
arbitrarily large number, which is not advantageous. Limit it to
2*NumCPU which scales somewhat with system performance.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-03 23:36:05 +02:00
Syncthing Release Automation 13f38e9abf chore(gui, man, authors): update docs, translations, and contributors 2026-08-03 04:54:49 +00:00
Jakob Borg bcef5c5bc6 chore(syncthing): include local db entries in perfstats (#10839)
As used by the latest few PRs with graphs in them

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-07-27 10:23:18 +00:00
Jakob Borg 952da4224b chore(model): optimise fsync calls (#10831)
This improves performance when running with fsync enabled. The key
observation is that the OS may coalesce multiple concurrent fsyncs, so
we gain some performance by issuing them in parallell. Since they run
from the finisher routine, a simple fix is to run multiple finisher
routines. That's the `after` line in the graph below. The other step is
to do the same for the directory fsyncs, issuing them concurrently with
a limiter. In both cases I used the Copiers value as the concurrency
factor. Additionally, add some buffering to the channels between
routines to minimise stalls where a routine needs to wait for another.
This is the `after2` line.

All in all, this speeds up syncing 25k tiny files from 190s to 130s, a
30% improvement.

<img width="821" height="540" alt="Screenshot 2026-07-25 at 22 58 34"
src="https://github.com/user-attachments/assets/18272c89-a99e-4ee4-9b5e-b278967ea7c6"
/>

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-07-27 12:13:05 +02:00
Jakob Borg b2092b188f chore(fs): optimise casefs caching performance (#10830)
This tweaks the casefs cache to do in-place updates for modifications we
perform, instead of dropping the entire cache. That is, we add, remove
and rename things in the cache after having done the corresponding
operation.

We still drop the cache entirely on larger operations like MkdirAll, and
before Walk, and the 1s expiry is unchanged.

The result is performance much closer to the original. I set up a device
with 150 000 tiny files and measured the time it took for a blank device
to come online and sync all the files, with default settings apart from
`fsync` being disabled, as it otherwise dominated the benchmark...

There are three runs, all are identical in the first 40 seconds which is
the index transmission & reception, then we can see the `sensitive` run
(casefs disabled) finishes at 137 seconds (137-40 = 97s spent syncing);
the `before` run finishes at 238s (198s spent syncing); the `after` run
finishes at 142 seconds (102s spent syncing). So, effectively the sync
time is cut in half, and is now very close to having case sensitive
enabled.

<img width="812" height="476" alt="Screenshot 2026-07-25 at 23 22 22"
src="https://github.com/user-attachments/assets/0aa39712-9fd7-46cf-969e-b6a20f150a3c"
/>

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-07-27 12:05:20 +02:00
183 changed files with 663 additions and 561 deletions
+4 -4
View File
@@ -17,10 +17,10 @@ import (
)
type event struct {
ID int `json:"id"`
Type string `json:"type"`
Time time.Time `json:"time"`
Data map[string]interface{} `json:"data"`
ID int `json:"id"`
Type string `json:"type"`
Time time.Time `json:"time"`
Data map[string]any `json:"data"`
}
func main() {
-1
View File
@@ -60,7 +60,6 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
t0 := time.Now()
resc := make(chan checkResult)
for _, srv := range servers {
srv := srv
go func() {
res := checkServer(deviceID, srv)
res.server = srv
+2 -5
View File
@@ -34,7 +34,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
return err
}
for i := 0; i < files; i++ {
for range files {
n := randomName()
if rand.Float64() < 0.05 {
@@ -51,10 +51,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
p1 := filepath.Join(p0, n)
s := int64(1 << uint(rand.Intn(maxexp)))
a := int64(128 * 1024)
if a > s {
a = s
}
a := min(int64(128*1024), s)
s += rand.Int63n(a)
if err := generateOneFile(fd, p1, s); err != nil {
+2 -2
View File
@@ -138,7 +138,7 @@ func printProgress(prefix string, count *atomic.Int64) {
}
}
func saveCert(priv interface{}, derBytes []byte) {
func saveCert(priv any, derBytes []byte) {
certOut, err := os.Create("cert.pem")
if err != nil {
fmt.Println(err)
@@ -179,7 +179,7 @@ func saveCert(priv interface{}, derBytes []byte) {
}
}
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
func pemBlockForKey(priv any) (*pem.Block, error) {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
+1 -1
View File
@@ -203,7 +203,7 @@ func loadIgnorePatterns(path string) (*ignorePatterns, error) {
}
var patterns []*regexp.Regexp
for _, line := range strings.Split(string(bs), "\n") {
for line := range strings.SplitSeq(string(bs), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build noassets
// +build noassets
package auto
+1 -1
View File
@@ -587,7 +587,7 @@ func loadRelays(file string, geoip *geoip.Provider) []*relay {
}
var relays []*relay
for _, line := range strings.Split(string(content), "\n") {
for line := range strings.SplitSeq(string(content), "\n") {
if line == "" {
continue
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
)
func init() {
for i := 0; i < 10; i++ {
for i := range 10 {
u := fmt.Sprintf("permanent%d", i)
permanentRelays = append(permanentRelays, &relay{URL: u})
}
+1 -1
View File
@@ -188,7 +188,7 @@ func (p *proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(resp.StatusCode)
if strings.HasPrefix(ct, "application/json") {
// Special JSON handling; clean it up a bit.
var v interface{}
var v any
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
+1 -4
View File
@@ -402,10 +402,7 @@ func (s *apiSrv) certificateBytes(req *http.Request) ([]byte, error) {
b.WriteByte('\n')
for i := 0; i < len(cert); i += 64 {
end := i + 64
if end > len(cert) {
end = len(cert)
}
end := min(i+64, len(cert))
b.WriteString(cert[i:end])
b.WriteByte('\n')
}
+3 -8
View File
@@ -7,7 +7,6 @@
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
@@ -120,7 +119,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
numBuckets := (notFoundRetryUnknownMaxSeconds + bucketSize - 1) / bucketSize
buckets := make([]int, numBuckets)
for i := 0; i < n; i++ {
for range n {
v := tracker.retryAfterS()
if v < notFoundRetryUnknownMinSeconds || v > notFoundRetryUnknownMaxSeconds {
t.Fatalf("retryAfterS() = %d, out of range [%d, %d]", v, notFoundRetryUnknownMinSeconds, notFoundRetryUnknownMaxSeconds)
@@ -142,10 +141,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
barWidth := 60
for i, c := range buckets {
lo := i*bucketSize + 1
hi := (i + 1) * bucketSize
if hi > notFoundRetryUnknownMaxSeconds {
hi = notFoundRetryUnknownMaxSeconds
}
hi := min((i+1)*bucketSize, notFoundRetryUnknownMaxSeconds)
bar := ""
if maxCount > 0 {
bar = strings.Repeat("#", c*barWidth/maxCount)
@@ -156,8 +152,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
func BenchmarkAPIRequests(b *testing.B) {
db := newInMemoryStore(b.TempDir(), 0, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := b.Context()
go db.Serve(ctx)
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000, 1000)
srv := httptest.NewServer(http.HandlerFunc(api.handler))
+4 -4
View File
@@ -18,7 +18,7 @@ import (
var (
outboxesMut = sync.RWMutex{}
outboxes = make(map[syncthingprotocol.DeviceID]chan interface{})
outboxes = make(map[syncthingprotocol.DeviceID]chan any)
numConnections atomic.Int64
)
@@ -97,9 +97,9 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config, token strin
id := syncthingprotocol.NewDeviceID(certs[0].Raw)
messages := make(chan interface{})
messages := make(chan any)
errors := make(chan error, 1)
outbox := make(chan interface{})
outbox := make(chan any)
// Read messages from the connection and send them on the messages
// channel. When there is an error, send it on the error channel and
@@ -364,7 +364,7 @@ func sessionConnectionHandler(conn net.Conn) {
}
}
func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- error) {
func messageReader(conn net.Conn, messages chan<- any, errors chan<- error) {
numConnections.Add(1)
defer numConnections.Add(-1)
+1 -4
View File
@@ -330,10 +330,7 @@ func take(tokens int, ls ...*rate.Limiter) {
for tokens > 0 {
// chunk is how many tokens we can consume at a time
chunk := tokens
if chunk > minBurst {
chunk = minBurst
}
chunk := min(tokens, minBurst)
// maxDelay is the longest delay mandated by any of the limiters for
// the chosen chunk size.
+2 -2
View File
@@ -38,7 +38,7 @@ func statusService(addr string) {
func getStatus(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
status := make(map[string]interface{})
status := make(map[string]any)
sessionMut.Lock()
// This can potentially be double the number of pending sessions, as each session has two keys, one for each side.
@@ -67,7 +67,7 @@ func getStatus(w http.ResponseWriter, _ *http.Request) {
rc.rate(30*60/10) * 8 / 1000,
rc.rate(60*60/10) * 8 / 1000,
}
status["options"] = map[string]interface{}{
status["options"] = map[string]any{
"network-timeout": networkTimeout / time.Second,
"ping-interval": pingInterval / time.Second,
"message-timeout": messageTimeout / time.Second,
+3 -3
View File
@@ -27,7 +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)
PutJSON(url string, o any) (*http.Response, error)
}
type apiClient struct {
@@ -134,7 +134,7 @@ func (c *apiClient) RequestString(url, method, data string) (*http.Response, err
return c.Request(url, method, bytes.NewBufferString(data))
}
func (c *apiClient) RequestJSON(url, method string, o interface{}) (*http.Response, error) {
func (c *apiClient) RequestJSON(url, method string, o any) (*http.Response, error) {
data, err := json.Marshal(o)
if err != nil {
return nil, err
@@ -150,7 +150,7 @@ 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) {
func (c *apiClient) PutJSON(url string, o any) (*http.Response, error) {
return c.RequestJSON(url, "PUT", o)
}
+1 -1
View File
@@ -81,7 +81,7 @@ func (c *configCommand) Run(ctx Context, outerCtx *kong.Context) error {
app.Name = "syncthing cli config"
app.HelpName = "syncthing cli config"
app.Description = outerCtx.Selected().Help
app.Metadata = map[string]interface{}{
app.Metadata = map[string]any{
"clientFactory": ctx.clientFactory,
}
app.CustomAppHelpTemplate = customAppHelpTemplate
+2 -2
View File
@@ -112,7 +112,7 @@ func getConfig(c APIClient) (config.Configuration, error) {
return cfg, nil
}
func prettyPrintJSON(data interface{}) error {
func prettyPrintJSON(data any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
@@ -123,7 +123,7 @@ func prettyPrintResponse(response *http.Response) error {
if err != nil {
return err
}
var data interface{}
var data any
if err := json.Unmarshal(bytes, &data); err != nil {
return err
}
+1 -1
View File
@@ -125,7 +125,7 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error {
func filterLogLines(data []byte) []byte {
filtered := data[:0]
matched := false
for _, line := range bytes.Split(data, []byte("\n")) {
for line := range bytes.SplitSeq(data, []byte("\n")) {
switch {
case !matched && bytes.HasPrefix(line, []byte("Panic ")):
// This begins the panic trace, set the matched flag and append.
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package main
+59 -38
View File
@@ -313,21 +313,6 @@ func (c *serveCmd) Run() error {
return nil
}
func openGUI() error {
cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
if guiCfg := cfg.GUI(); guiCfg.Enabled {
if err := openURL(guiCfg.URL()); err != nil {
return err
}
} else {
slog.Error("Browser: GUI is currently disabled")
}
return nil
}
func logPackages() string {
packages := slogutil.PackageDescrs()
@@ -418,15 +403,34 @@ func upgradeViaRest() error {
}
func (c *serveCmd) syncthingMain() {
// Ensure we are the only running instance
lf := flock.New(locations.Get(locations.LockFile))
locked, err := lf.TryLock()
switch {
case err != nil:
slog.Error("Failed to acquire lock", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt())
case !locked && c.NoBrowser:
slog.Error("Failed to acquire lock: is another Syncthing instance already running?")
os.Exit(svcutil.ExitError.AsInt())
case !locked:
slog.Info("Seems to already be running, launching GUI instead (use --no-browser to prevent)")
cmd := browserCmd{Verify: true}
if err := cmd.Run(); err != nil {
slog.Error("Failed to open browser", slogutil.Error(err))
os.Exit(svcutil.ExitNoRestart.AsInt())
}
return
}
if c.DebugProfileBlock {
startBlockProfiler()
}
if c.DebugProfileHeap {
startHeapProfiler()
}
if c.DebugPerfStats {
startPerfStats()
}
// Print our version information up front, so any crash that happens
// early etc. will have it available.
@@ -439,18 +443,7 @@ func (c *serveCmd) syncthingMain() {
)
if err != nil {
slog.Error("Failed to load/generate certificate", slogutil.Error(err))
os.Exit(1)
}
// Ensure we are the only running instance
lf := flock.New(locations.Get(locations.LockFile))
locked, err := lf.TryLock()
if err != nil {
slog.Error("Failed to acquire lock", slogutil.Error(err))
os.Exit(1)
} else if !locked {
slog.Error("Failed to acquire lock: is another Syncthing instance already running?")
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
}
ctx, cancel := context.WithCancel(context.Background())
@@ -505,13 +498,17 @@ func (c *serveCmd) syncthingMain() {
if err := syncthing.TryMigrateDatabase(ctx, c.DBDeleteRetentionInterval); err != nil {
slog.Error("Failed to migrate old-style database", slogutil.Error(err))
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
}
sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database), c.DBDeleteRetentionInterval)
if err != nil {
slog.Error("Error opening database", slogutil.Error(err))
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
}
if c.DebugPerfStats {
startPerfStats(sdb)
}
migratingAPICancel() // we're done with the temporary API server
@@ -914,7 +911,7 @@ func (u upgradeCmd) Run() error {
switch {
case err != nil && !os.IsNotExist(err):
slog.Error("Failed to lock for upgrade", slogutil.Error(err))
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
case locked || os.IsNotExist(err):
// We got the lock, or the config directory didn't exist, so we
// can do a direct upgrade
@@ -934,14 +931,38 @@ func (u upgradeCmd) Run() error {
return nil
}
type browserCmd struct{}
type browserCmd struct {
Verify bool `help:"Verify that the GUI is reachable before launching browser"`
}
func (browserCmd) Run() error {
if err := openGUI(); err != nil {
slog.Error("Failed to open web UI", slogutil.Error(err))
func (c browserCmd) Run() error {
cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
guiCfg := cfg.GUI()
if !guiCfg.Enabled {
slog.Error("Browser: GUI is currently disabled")
os.Exit(svcutil.ExitError.AsInt())
}
return nil
url := guiCfg.URL()
if c.Verify {
// Do an HTTP request to verify the GUI/API is up and available
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
_, err = http.DefaultClient.Do(req) //nolint:bodyclose // we're exiting in a millisecond
if err != nil {
slog.Error("GUI not available", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) //nolint:gocritic // deferred cancel
}
}
return openURL(url)
}
type debugCmd struct {
+8 -3
View File
@@ -171,10 +171,11 @@ func (c *serveCmd) monitorMain() {
exiterr := &exec.ExitError{}
if errors.As(err, &exiterr) {
exitCode := exiterr.ExitCode()
if stopped || c.NoRestart {
switch {
case stopped || c.NoRestart:
os.Exit(exitCode)
}
if exitCode == svcutil.ExitUpgrade.AsInt() {
case exitCode == svcutil.ExitUpgrade.AsInt():
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
slog.Info("Restarting monitor...")
@@ -182,6 +183,10 @@ func (c *serveCmd) monitorMain() {
slog.Error("Failed to restart monitor", slogutil.Error(err))
}
os.Exit(exitCode)
case exitCode == svcutil.ExitNoRestart.AsInt():
// Requested to not restart the child
os.Exit(exitCode)
}
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package main
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package main
+15 -6
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !solaris && !windows
// +build !solaris,!windows
package main
@@ -16,6 +15,7 @@ import (
"syscall"
"time"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/osutil"
@@ -23,11 +23,11 @@ import (
"golang.org/x/exp/constraints"
)
func startPerfStats() {
go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()))
func startPerfStats(db db.DB) {
go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()), db)
}
func savePerfStats(file string) {
func savePerfStats(file string, db db.DB) {
fd, err := os.Create(file)
if err != nil {
panic(err)
@@ -42,7 +42,7 @@ func savePerfStats(file string) {
syscall.Getrusage(syscall.RUSAGE_SELF, &prevRus)
runtime.ReadMemStats(&prevMem)
fmt.Fprintf(fd, "TIME_S\tCPU_S\tHEAP_KIB\tRSS_KIB\tNETIN_KBPS\tNETOUT_KBPS\tDBSIZE_KIB\n")
fmt.Fprintf(fd, "TIME_S\tCPU_S\tHEAP_KIB\tRSS_KIB\tNETIN_KBPS\tNETOUT_KBPS\tDBSIZE_KIB\tDBLOCAL\n")
for t := range time.NewTicker(250 * time.Millisecond).C {
syscall.Getrusage(syscall.RUSAGE_SELF, &curRus)
@@ -55,7 +55,15 @@ func savePerfStats(file string) {
rss /= 1024
}
fmt.Fprintf(fd, "%.03f\t%f\t%d\t%d\t%.0f\t%.0f\t%d\n",
folders, _ := db.ListFolders()
var dbLocal int
for _, f := range folders {
local, _ := db.CountLocal(f, protocol.LocalDeviceID)
dbLocal += local.Files + local.Directories + local.Symlinks
}
fmt.Fprintf(
fd, "%.03f\t%f\t%d\t%d\t%.0f\t%.0f\t%d\t%d\n",
t.Sub(t0).Seconds(),
rate(cpusec(&prevRus), cpusec(&curRus), timeDiff, 1),
(curMem.Sys-curMem.HeapReleased)/1024,
@@ -63,6 +71,7 @@ func savePerfStats(file string) {
rate(prevIn, in, timeDiff, 1e3),
rate(prevOut, out, timeDiff, 1e3),
osutil.DirSize(locations.Get(locations.Database))/1024,
dbLocal,
)
prevTime = t
+3 -2
View File
@@ -5,9 +5,10 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build solaris || windows
// +build solaris windows
package main
func startPerfStats() {
import "github.com/syncthing/syncthing/internal/db"
func startPerfStats(_ db.DB) {
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build go1.7
// +build go1.7
package main
+2
View File
@@ -125,6 +125,8 @@
"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 will reappear if the device connects again.": "لا تقم بإضافته إلى قائمة التجاهل، حتى يتم اخطارك عند اعادة اتصال الجهاز.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "لا تقم بإضافته إلى قائمة التجاهل، حتى يتم اخطارك عند اعادة اتصال الجهاز المزوّد لهذا المجلد.",
"Do not restore": "الغاء الاستعادة",
"Do not restore all": "الغاء استعادة الكل",
"Do you want to enable watching for changes for all your folders?": "هل تريد تفعيل مراقبة التغيرات على كل المجلدات؟",
+3 -1
View File
@@ -78,7 +78,7 @@
"Copied from original": "Vom Original kopiert",
"Copied!": "Kopiert!",
"Copy": "Kopieren",
"Copy failed! Try to select and copy manually.": "Kopieren fehlgeschlagen! Versuchen Sie, den Text manuell zu markieren und kopieren.",
"Copy failed! Try to select and copy manually.": "Kopieren fehlgeschlagen! Versuchen Sie, den Text manuell auszuwählen und zu kopieren.",
"Currently Shared With Devices": "Derzeit mit Geräten geteilt",
"Custom Range": "Eigener Zeitraum",
"Danger!": "Achtung!",
@@ -125,6 +125,8 @@
"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 add it to the ignore list, so this notification will reappear if the device connects again.": "Nicht zur Ignorierliste hinzufügen, damit diese Benachrichtigung erneut angezeigt wird, wenn das Gerät wieder eine Verbindung herstellt.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Nicht zur Ignorierliste hinzufügen, damit diese Benachrichtigung erneut angezeigt wird, falls das Gerät, das diesen Ordner bereitstellt, wieder eine Verbindung herstellt.",
"Do not restore": "Nicht wiederherstellen",
"Do not restore all": "Nicht alle wiederherstellen",
"Do you want to enable watching for changes for all your folders?": "Möchten Sie die Überwachung von Änderungen für all Ihre Ordner aktivieren?",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "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 add it to the ignore list, so this notification will reappear if the device connects again.": "Do not add it to the ignore list, so this notification will reappear if the device connects again.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.",
"Do not restore": "Do not restore",
"Do not restore all": "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?",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Stato de malkovrado",
"Dismiss": "Forsendi",
"Do not add it to the ignore list, so this notification may recur.": "Ne aldonu ĝin al la listo de ignoraĵoj, por ke tiu ĉi sciigo povos reaperi poste.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Ne aldonu ĝin al la listo de ignoraĵoj, por ke tiu ĉi sciigo reaperos, se la aparato konektos denove.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Ne aldonu ĝin al la listo de ignoraĵoj, por ke tiu ĉi sciigo reaperos, se la aparato, oferanta tiun ĉi dosierujon, konektos denove.",
"Do not restore": "Ne restarigu",
"Do not restore all": "Ne restarigu ĉion",
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi atendadon por ŝanĝoj por ĉiuj viaj dosierujoj?",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Estado de descubrimiento",
"Dismiss": "Descartar",
"Do not add it to the ignore list, so this notification may recur.": "No añadirlo a la lista de ignorados, de modo que esta notificación sea recurrente.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "No lo añadas a la lista de ignorados, para que la notificación reaparezca si el dispositivo se conecta de nuevo.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "No lo añadas a la lista de ignorados, para que la notificación reaparezca si el dispositivo que ofrece la carpeta se conecta de nuevo.",
"Do not restore": "No restaurar",
"Do not restore all": "No restaurar todos",
"Do you want to enable watching for changes for all your folders?": "¿Quieres activar el control de cambios en todas tus carpetas?",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "État de la découverte",
"Dismiss": "Écarter",
"Do not add it to the ignore list, so this notification may recur.": "Attendre l'expiration de cette demande : évite l'ajout immédiat à la liste noire persistante.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Ne pas ajouter cet appareil à la liste des bloqués pour que cette notification revienne s'il tente de se reconnecter.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Ne pas ajouter ce partage à la liste des bloqués pour que cette notification revienne quand l'appareil qui le propose se reconnecte.",
"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 ?",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Stádas Fionnachtana",
"Dismiss": "Ruaig",
"Do not add it to the ignore list, so this notification may recur.": "Ná cuir leis an liosta neamhairde é, mar sin d'fhéadfadh an fógra seo tarlú arís.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Ná cuir leis an liosta neamhaird é, mar sin feicfear an fógra seo arís má nascann an gléas arís.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Ná cuir leis an liosta neamhaird é, mar sin feicfear an fógra seo arís má cheanglaíonn an gléas a thairgeann an fillteán seo arís.",
"Do not restore": "Ná hathchóirigh",
"Do not restore all": "Ná cuir gach rud ar ais",
"Do you want to enable watching for changes for all your folders?": "An bhfuil fonn ort féachaint ar athruithe do d'fhillteáin go léir?",
+5 -3
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Stato Individuazione",
"Dismiss": "Scartato",
"Do not add it to the ignore list, so this notification may recur.": "Non aggiungerlo all'elenco da ignorare, quindi questa notifica potrebbe ripresentarsi.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Non aggiungerlo all'elenco dei dispositivi da ignorare, cosicché questa notifica venga visualizzata nuovamente se si connetterà nuovamente.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Non aggiungerlo all'elenco dei dispositivi da ignorare, cosicché questa notifica venga visualizzata nuovamente se il dispositivo che offre questa cartella si connetterà di nuovamente.",
"Do not restore": "Non ripristinare",
"Do not restore all": "Non ripristinare tutto",
"Do you want to enable watching for changes for all your folders?": "Vuoi abilitare il monitoraggio delle modifiche per tutte le tue cartelle?",
@@ -189,7 +191,7 @@
"GUI Authentication User": "Utente dell'Interfaccia Grafica",
"GUI Authentication: Set User and Password": "Autenticazione GUI: usa utente e password",
"GUI Listen Address": "Indirizzo dell'Interfaccia Grafica",
"GUI Override Directory": "GUI Sostituisci Directory",
"GUI Override Directory": "Cartella di override GUI",
"GUI Theme": "Tema GUI",
"General": "Generale",
"Generate": "Genera",
@@ -447,7 +449,7 @@
"The following text will automatically be inserted into a new message.": "Il seguente testo verrà automaticamente inserito in un nuovo messaggio.",
"The following unexpected items were found.": "Sono stati trovati i seguenti elementi imprevisti.",
"The interval must be a positive number of seconds.": "L'intervallo deve essere un numero positivo di secondi.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "L'intervallo, in secondi, per l'esecuzione della pulizia nella directory delle versioni. Zero per disabilitare la pulizia periodica.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "L'intervallo, in secondi, per l'esecuzione della pulizia nella cartella delle versioni. Zero per disabilitare la pulizia periodica.",
"The maximum age must be a number and cannot be blank.": "La durata massima dev'essere un numero e non può essere vuoto.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La durata massima di una versione (in giorni, imposta a 0 per mantenere le versioni per sempre).",
"The number of connections must be a non-negative number.": "Il numero di connessioni deve essere un numero non negativo.",
@@ -543,7 +545,7 @@
"days": "giorni",
"deleted": "cancellato",
"deny": "negare",
"directories": "directory",
"directories": "cartelle",
"file": "file",
"files": "file",
"folder": "cartella",
+3
View File
@@ -125,6 +125,8 @@
"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 will reappear if the device connects again.": "무시 항목에 추가되지 않으니 해당 기기가 다시 연결되면 이 알림은 다시 표시될 것입니다.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "무시 항목에 추가되지 않으니 이 폴더를 공유하는 기가가 연결되면 이 알림은 다시 표시될 것입니다.",
"Do not restore": "복구하지 않기",
"Do not restore all": "모두 복구하지 않기",
"Do you want to enable watching for changes for all your folders?": "변경 항목 감시를 모든 폴더에서 활성화하시겠습니까?",
@@ -395,6 +397,7 @@
"Staggered": "시차제",
"Staggered File Versioning": "시차제 파일 버전 관리",
"Start Browser": "브라우저 열기",
"Starting": "시작 중",
"Statistics": "통계",
"Stay logged in": "로그인 상태 유지",
"Stopped": "중지됨",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Stan odnajdywania",
"Dismiss": "Odrzuć",
"Do not add it to the ignore list, so this notification may recur.": "Nie dodaje do listy ignorowanych, więc powiadomienie to może się powtórzyć.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie po raz kolejny się połączy.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie oferujące ten folder po raz kolejny się połączy.",
"Do not restore": "Nie przywracaj",
"Do not restore all": "Nie przywracaj wszystkich",
"Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich folderach?",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Status da Descoberta",
"Dismiss": "Descartar",
"Do not add it to the ignore list, so this notification may recur.": "Não o adicione à lista de ignorados, portanto, esta notificação pode ocorrer novamente.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Não o adicione à lista de ignorados, pois esta notificação reaparecerá se o dispositivo se conectar novamente.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Não o adicione à lista de ignorados, pois esta notificação reaparecerá se o dispositivo que oferece esta pasta se conectar novamente.",
"Do not restore": "Não restaurar",
"Do not restore all": "Não restaurar nenhum",
"Do you want to enable watching for changes for all your folders?": "Você deseja ativar a observação de alterações em todas as suas pastas?",
+2 -1
View File
@@ -137,7 +137,7 @@
"Edit Device Defaults": "Изменить настройки устройств",
"Edit Folder": "Редактирование папки",
"Edit Folder Defaults": "Изменить настройки папок",
"Editing {%path%}.": "Правка {{path}}.",
"Editing {%path%}.": "Редактирование {{path}}.",
"Enable Crash Reporting": "Включить отчёты о сбоях",
"Enable NAT traversal": "Использовать обход NAT",
"Enable Relaying": "Использовать ретрансляторы",
@@ -396,6 +396,7 @@
"Staggered": "Ступенчато",
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
"Start Browser": "Запускать браузер",
"Starting": "Запуск",
"Statistics": "Статистика",
"Stay logged in": "Оставаться в системе",
"Stopped": "Остановлено",
+59 -57
View File
@@ -25,7 +25,7 @@
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alla mappar som delas med den här enheten måste skyddas av ett lösenord, så att alla skickade data är oläsliga utan det angivna lösenordet.",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användningsrapportering?",
"Allowed Networks": "Tillåtna nätverk",
"Alphabetic": "Alfabetisk",
"Alphabetic": "Alfabetiskt",
"Altered by ignoring deletes.": "Ändrad eftersom borttagningar ignoreras.",
"Always turned on when the folder type is \"{%foldertype%}\".": "Alltid på när mapptypen är \"{{foldertype}}\".",
"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.": "Ett externt kommando hanterar versionshanteringen. Det måste ta bort filen från den delade mappen. Om sökvägen till programmet innehåller mellanslag ska den omges av citattecken.",
@@ -41,7 +41,7 @@
"Are you sure you want to revert all local changes?": "Är du säker på att du vill återställa alla lokala ändringar?",
"Are you sure you want to upgrade?": "Är du säker på att du vill uppgradera?",
"Authentication Required": "Autentisering krävs",
"Authors": "Upphovsmän",
"Authors": "Upphovspersoner",
"Auto Accept": "Acceptera automatiskt",
"Automatic Crash Reporting": "Automatisk kraschrapportering",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatisk uppgradering erbjuder nu valet mellan stabila utgåvor och utgåvskandidater.",
@@ -51,7 +51,7 @@
"Available debug logging facilities:": "Tillgängliga funktioner för felsökningsloggning:",
"Be careful!": "Var försiktig!",
"Block Indexing": "Blockindexering",
"Body:": "Meddelande:",
"Body:": "Meddelandetext:",
"Bugs": "Felrapporter",
"Cancel": "Avbryt",
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Kan inte aktiveras när mapptypen är \"{{foldertype}}\".",
@@ -62,7 +62,7 @@
"Click to see full identification string and QR code.": "Klicka för att se fullständig identifieringssträng och QR-kod.",
"Close": "Stäng",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, vid användning i början av en rad",
"Comment, when used at the start of a line": "Kommentar när det används i början av en rad",
"Compression": "Komprimering",
"Configuration Directory": "Konfigurationsmapp",
"Configuration File": "Konfigurationsfil",
@@ -90,7 +90,7 @@
"Default Device": "Standardenhet",
"Default Folder": "Standardmapp",
"Default Ignore Patterns": "Standardignoreringsmönster",
"Defaults": "Standard",
"Defaults": "Standardvärden",
"Delete": "Ta bort",
"Delete Unexpected Items": "Ta bort oväntade objekt",
"Deleted {%file%}": "Tog bort {{file}}",
@@ -106,8 +106,8 @@
"Device Name": "Enhetsnamn",
"Device Status": "Enhetsstatus",
"Device is untrusted, enter encryption password": "Enheten är inte betrodd. Ange krypteringslösenordet.",
"Device rate limits": "Hastighetsgränser för enheten",
"Device that last modified the item": "Enhet som senast ändrade objektet",
"Device rate limits": "Enhetens hastighetsgränser",
"Device that last modified the item": "Enheten som senast ändrade objektet",
"Devices": "Enheter",
"Disable Crash Reporting": "Inaktivera kraschrapportering",
"Disabled": "Inaktiverad",
@@ -121,10 +121,12 @@
"Disconnected (Unused)": "Frånkopplad (oanvänd)",
"Discovered": "Upptäckt",
"Discovery": "Annonsering",
"Discovery Failures": "Annonseringsmisslyckanden",
"Discovery Failures": "Fel vid annonsering",
"Discovery Status": "Annonseringsstatus",
"Dismiss": "Avfärda",
"Do not add it to the ignore list, so this notification may recur.": "Lägg inte till den i ignoreringslistan, så denna avisering kan återkomma.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Lägg inte till den i ignoreringslistan. Då visas aviseringen igen om enheten ansluter på nytt.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Lägg inte till den i ignoreringslistan. Då visas aviseringen igen om enheten som erbjuder den här mappen ansluter på nytt.",
"Do not restore": "Återställ inte",
"Do not restore all": "Återställ inte allt",
"Do you want to enable watching for changes for all your folders?": "Vill du aktivera bevakning av ändringar på alla dina mappar?",
@@ -180,29 +182,29 @@
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Mapptypen \"{{receiveEncrypted}}\" kan bara ställas in när en ny mapp läggs till.",
"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.": "Mapptypen \"{{receiveEncrypted}}\" kan inte ändras efter att mappen har lagts till. Du måste ta bort mappen, ta bort eller dekryptera data på disken och lägga till mappen igen.",
"Folders": "Mappar",
"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.": "För följande mappar uppstod ett fel när bevakning av ändringar skulle startas. Försöket upprepas varje minut, så felen kan snart försvinna. Om de fortsätter, försök att åtgärda det underliggande problemet och fråga om hjälp om du inte kan.",
"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.": "För följande mappar uppstod ett fel när bevakning av ändringar skulle startas. Försöket upprepas varje minut, så felen kan snart försvinna. Om de kvarstår bör du försöka åtgärda det underliggande problemet och be om hjälp om du inte lyckas.",
"Forever": "För alltid",
"Full Rescan Interval (s)": "Intervall för fullständig omskanning (s)",
"GUI": "Grafiskt användargränssnitt",
"GUI / API HTTPS Certificate": "HTTPS-certifikat för gränssnitt och API",
"GUI Authentication Password": "Autentiseringslösenord för gränssnitt",
"GUI Authentication User": "Autentiseringsanvändare för gränssnitt",
"GUI Authentication: Set User and Password": "Autentisering för det grafiska gränssnittet: ange användarnamn och lösenord",
"GUI Listen Address": "Lyssnaradress för gränssnitt",
"GUI Override Directory": "Mapp som åsidosätter gränssnittet",
"GUI Theme": "Tema för gränssnitt",
"GUI / API HTTPS Certificate": "HTTPS-certifikat för gränssnittet och API:t",
"GUI Authentication Password": "Lösenord för gränssnittsautentisering",
"GUI Authentication User": "Användarnamn för gränssnittsautentisering",
"GUI Authentication: Set User and Password": "Gränssnittsautentisering: ange användarnamn och lösenord",
"GUI Listen Address": "Gränssnittets lyssnaradress",
"GUI Override Directory": "Åsidosättningsmapp för gränssnittet",
"GUI Theme": "Gränssnittstema",
"General": "Allmänt",
"Generate": "Generera",
"Global Discovery": "Global annonsering",
"Global Discovery Servers": "Globala annonseringsservrar",
"Global State": "Globalt tillstånd",
"Help": "Hjälp",
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Tips: Endast nekande regler hittades samtidigt som standardåtgärden är att neka. Överväg att lägga till ”tillåt alla” som sista regel.",
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Tips: Endast nekanderegler hittades när standardåtgärden är att neka. Överväg att lägga till ”permit any” som sista regel.",
"Home page": "Webbplats",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Dina aktuella inställningar tyder dock på att du kanske inte vill ha det aktiverat. Vi har inaktiverat automatisk kraschrapportering för dig.",
"Identification": "Identifiering",
"If untrusted, enter encryption password": "Om enheten inte är betrodd anger du krypteringslösenordet",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Om du vill förhindra att andra användare på den här datorn får åtkomst till Syncthing och därigenom dina filer bör du konfigurera autentisering.",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Om du vill förhindra att andra användare på den här datorn får åtkomst till Syncthing och därigenom dina filer, bör du konfigurera autentisering.",
"Ignore": "Ignorera",
"Ignore Patterns": "Ignoreringsmönster",
"Ignore Permissions": "Ignorera behörigheter",
@@ -232,7 +234,7 @@
"Learn more": "Läs mer",
"Learn more at {%url%}": "Läs mer på {{url}}",
"Limit": "Gräns",
"Limit Bandwidth in LAN": "Begränsa bandbredd i LAN",
"Limit Bandwidth in LAN": "Begränsa bandbredden i LAN",
"Listener Failures": "Lyssnarfel",
"Listener Status": "Lyssnarstatus",
"Listeners": "Lyssnare",
@@ -255,7 +257,7 @@
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Upprätthåll ett index över alla block i mappen, vilket möjliggör återanvändning av block från andra filer vid synkronisering av ändringar. Inaktivera för att minska databasens storlek på bekostnad av att inte kunna återanvända block mellan filer.",
"Major Upgrade": "Uppgradering av huvudversion",
"Mass actions": "Massåtgärder",
"Maximum Age": "Högsta ålder",
"Maximum Age": "Maximal ålder",
"Maximum single entry size": "Största tillåtna poststorlek",
"Maximum total size": "Största tillåtna totala storlek",
"Metadata Only": "Endast metadata",
@@ -264,7 +266,7 @@
"Mod. Time": "Tid för ändring",
"More than a month ago": "För mer än en månad sedan",
"More than a week ago": "För mer än en vecka sedan",
"More than a year ago": "Mer än ett år sedan",
"More than a year ago": "För mer än ett år sedan",
"Move to top of queue": "Flytta längst upp i kön",
"Multi level wildcard (matches multiple directory levels)": "Jokertecken på flera nivåer (matchar flera mappnivåer)",
"Never": "Aldrig",
@@ -307,10 +309,10 @@
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning körs med angivet intervall, men bevakning av ändringar kunde inte konfigureras. Ett nytt försök görs varje minut:",
"Permanently add it to the ignore list, suppressing further notifications.": "Lägg till den permanent i ignoreringslistan och dölj framtida aviseringar.",
"Please consult the release notes before performing a major upgrade.": "Läs versionsinformationen innan du uppgraderar till en ny huvudversion.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ställ in en autentiseringsanvändare och ett lösenord för det grafiska användargränssnittet i inställningsdialogrutan.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ange ett användarnamn och lösenord för gränssnittsautentisering i dialogrutan Inställningar.",
"Please wait": "Vänta",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix som indikerar att filen kan tas bort om den förhindrar mappborttagning",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix som indikerar att mönstret ska matchas utan skiftlägeskänslighet",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix som anger att filen kan tas bort om den förhindrar att mappen tas bort",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix som anger att mönstret ska matchas utan hänsyn till skiftläge",
"Preparing to Sync": "Förbereder synkronisering",
"Preview": "Förhandsgranska",
"Preview Usage Report": "Förhandsgranska användningsrapport",
@@ -351,18 +353,18 @@
"Saving changes": "Sparar ändringar",
"Scan Time Remaining": "Återstående skanningstid",
"Scanning": "Skannar",
"See external versioning help for supported templated command line parameters.": "Se hjälpen för extern versionshantering för information om kommandoradsparametrar som stöder mallar.",
"See external versioning help for supported templated command line parameters.": "Se hjälpen för extern versionshantering för information om vilka mallbaserade kommandoradsparametrar som stöds.",
"Select All": "Markera alla",
"Select a version": "Välj en version",
"Select additional devices to share this folder with.": "Välj ytterligare enheter för att dela denna mapp med.",
"Select additional folders to share with this device.": "Välj ytterligare mappar att dela med denna enhet.",
"Select additional devices to share this folder with.": "Välj ytterligare enheter som den här mappen ska delas med.",
"Select additional folders to share with this device.": "Välj ytterligare mappar som ska delas med den här enheten.",
"Select latest version": "Välj senaste versionen",
"Select oldest version": "Välj äldsta versionen",
"Send & Receive": "Skicka och ta emot",
"Send Extended Attributes": "Skicka utökade attribut",
"Send Only": "Skicka endast",
"Send Ownership": "Skicka ägarskap",
"Set Ignores on Added Folder": "Ange ignoreringsmönster för tillagd mapp",
"Set Ignores on Added Folder": "Ange ignoreringsmönster för den nya mappen",
"Settings": "Inställningar",
"Share": "Dela",
"Share Folder": "Dela mapp",
@@ -386,15 +388,15 @@
"Single level wildcard (matches within a directory only)": "Jokertecken på en nivå (matchar endast i en mapp)",
"Size": "Storlek",
"Smallest First": "Minsta först",
"Some discovery methods could not be established for finding other devices or announcing this device:": "Vissa annonseringsmetoder kunde inte upprättas för att hitta andra enheter eller annonsera denna enhet:",
"Some discovery methods could not be established for finding other devices or announcing this device:": "Vissa metoder för att hitta andra enheter eller annonsera den här enheten kunde inte upprättas:",
"Some items could not be restored:": "Vissa objekt kunde inte återställas:",
"Some listening addresses could not be enabled to accept connections:": "Vissa lyssningsadresser kunde inte aktiveras för att acceptera anslutningar:",
"Source Code": "Källkod",
"Stable releases and release candidates": "Stabila utgåvor och utgåvskandidater",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabila utgåvor publiceras cirka två veckor senare. Under tiden testas de som utgåvskandidater.",
"Stable releases only": "Endast stabila utgåvor",
"Staggered": "Förskjuten",
"Staggered File Versioning": "Filversionshantering i intervall",
"Staggered": "Intervallbaserad",
"Staggered File Versioning": "Intervallbaserad filversionshantering",
"Start Browser": "Starta webbläsaren",
"Starting": "Startar",
"Statistics": "Statistik",
@@ -434,47 +436,47 @@
"The device ID cannot be blank.": "Enhets-ID får inte lämnas tomt.",
"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).": "Enhets-ID som du anger här finns i dialogrutan \"Åtgärder > Visa ID\" på den andra enheten. Mellanslag och bindestreck är valfria (ignoreras).",
"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.": "Den krypterade användningsrapporten skickas dagligen. Den används för att kartlägga vanliga plattformar, mappstorlekar och programversioner. Om den rapporterade datauppsättningen ändras visas den här dialogrutan igen.",
"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.": "Det inmatade enhets-ID:t verkar inte vara korrekt. Det ska vara en sträng med 52 eller 56 tecken, bestående av bokstäver och siffror, där mellanslag och bindestreck är valfria.",
"The folder ID cannot be blank.": "Mapp-ID får inte vara tomt.",
"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.": "Det angivna enhets-ID:t verkar inte vara giltigt. Det ska vara en sträng med 52 eller 56 tecken som består av bokstäver och siffror. Mellanslag och bindestreck är valfria.",
"The folder ID cannot be blank.": "Mapp-ID får inte lämnas tomt.",
"The folder ID must be unique.": "Mapp-ID måste vara unikt.",
"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.": "Mappinnehållet på andra enheter skrivs över så att det blir identiskt med innehållet på den här enheten. Filer som inte finns här tas bort från de andra enheterna.",
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "Mappinnehållet på den här enheten skrivs över så att det blir identiskt med innehållet på de andra enheterna. Filer som nyligen har lagts till här tas bort.",
"The folder path cannot be blank.": "Mappsökvägen kan inte vara tom.",
"The folder path cannot be blank.": "Mappsökvägen får inte lämnas tom.",
"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.": "Följande intervall används: under den första timmen sparas en version var 30:e sekund, under den första dagen en version varje timme, under de första 30 dagarna en version varje dag och därefter en version varje vecka tills den maximala åldern nås.",
"The following items could not be synchronized.": "Följande objekt kunde inte synkroniseras.",
"The following items were changed locally.": "Följande objekt ändrades lokalt.",
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Följande metoder används för att upptäcka andra enheter i nätverket och annonsera den här enheten så att andra kan hitta den:",
"The following text will automatically be inserted into a new message.": "Följande text kommer automatiskt att infogas i ett nytt meddelande.",
"The following text will automatically be inserted into a new message.": "Följande text infogas automatiskt i ett nytt meddelande.",
"The following unexpected items were found.": "Följande oväntade objekt hittades.",
"The interval must be a positive number of seconds.": "Intervallet måste vara ett positivt antal sekunder.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Intervallet i sekunder mellan rensningar av versionsmappen. Ange noll för att inaktivera periodisk rensning.",
"The maximum age must be a number and cannot be blank.": "Den högsta åldern måste vara ett tal och får inte lämnas tom.",
"The maximum age must be a number and cannot be blank.": "Den maximala åldern måste vara ett tal och får inte lämnas tom.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden som en version ska behållas, i dagar. Ange 0 för att behålla versioner för alltid.",
"The number of connections must be a non-negative number.": "Antalet anslutningar måste vara ett icke-negativt tal.",
"The number of days must be a number and cannot be blank.": "Antalet dagar måste vara ett tal och får inte lämnas tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "Antalet dagar som filer ligger kvar i papperskorgen. Noll betyder för alltid.",
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas per fil.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett tal och får inte lämnas tomt.",
"The path cannot be blank.": "Sökvägen kan inte vara tom.",
"The path cannot be blank.": "Sökvägen får inte lämnas tom.",
"The rate limit is applied to the accumulated traffic of all connections to this device.": "Hastighetsgränsen tillämpas på den sammanlagda trafiken för alla anslutningar till den här enheten.",
"The rate limit must be a non-negative number (0: no limit)": "Hastighetsgränsen måste vara ett icke-negativt tal (0: ingen gräns)",
"The remote device has not accepted sharing this folder.": "Fjärrenheten har inte godkänt att den här mappen delas.",
"The remote device has paused this folder.": "Fjärrenheten har pausat denna mapp.",
"The remote device has paused this folder.": "Fjärrenheten har pausat den här mappen.",
"The rescan interval must be a non-negative number of seconds.": "Omskanningsintervallet måste vara ett icke-negativt antal sekunder.",
"There are no devices to share this folder with.": "Det finns inga enheter att dela denna mapp med.",
"There are no devices to share this folder with.": "Det finns inga enheter som den här mappen kan delas med.",
"There are no file versions to restore.": "Det finns inga filversioner att återställa.",
"There are no folders to share with this device.": "Det finns inga mappar att dela med denna enhet.",
"There are no folders to share with this device.": "Det finns inga mappar som kan delas med den här enheten.",
"They are retried automatically and will be synced when the error is resolved.": "Försöken upprepas automatiskt och objekten synkroniseras när felet är löst.",
"This Device": "Den här enheten",
"This Month": "Den här månaden",
"This can easily give hackers access to read and change any files on your computer.": "Det kan enkelt ge hackare möjlighet att läsa och ändra alla filer på datorn.",
"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.": "Denna enhet kan inte automatiskt upptäcka andra enheter eller annonsera sin egen adress så att andra kan hitta den. Endast enheter med statiskt konfigurerade adresser kan ansluta.",
"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.": "Den här enheten kan inte automatiskt hitta andra enheter eller annonsera sin egen adress så att andra kan hitta den. Endast enheter med statiskt konfigurerade adresser kan ansluta.",
"This is a major version upgrade.": "Det här är en uppgradering till en ny huvudversion.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Den här inställningen styr hur mycket ledigt utrymme som krävs på disken där hemmappen, det vill säga indexdatabasen, finns.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Den här inställningen styr hur mycket ledigt utrymme som krävs på hemmadisken, det vill säga disken där indexdatabasen finns.",
"Time": "Tid",
"Time the item was last modified": "Tidpunkt då objektet senast ändrades",
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "För att ansluta till Syncthing-enheten med namnet \"{{devicename}}\", lägg till en ny fjärrenhet med detta ID:",
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "För att tillåta en regel, markera kryssrutan. För att neka en regel, lämna den omarkerad.",
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "Lägg till en ny fjärrenhet med följande ID för att ansluta till Syncthing-enheten {{devicename}}:",
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Markera kryssrutan för att tillåta en regel. Lämna den omarkerad för att neka regeln.",
"Today": "Idag",
"Trash Can": "Papperskorgen",
"Trash Can File Versioning": "Filversionshantering med papperskorg",
@@ -482,9 +484,9 @@
"UNIX Permissions": "UNIX-behörigheter",
"Unavailable": "Otillgänglig",
"Unavailable/Disabled by administrator or maintainer": "Otillgängligt/inaktiverat av administratör eller underhållare",
"Undecided (will prompt)": "Inte avgjort (fråga användaren)",
"Undecided (will prompt)": "Inte bestämt (en fråga visas)",
"Unexpected Items": "Oväntade objekt",
"Unexpected items have been found in this folder.": "Oväntade objekt har hittats i denna mapp.",
"Unexpected items have been found in this folder.": "Oväntade objekt har hittats i den här mappen.",
"Unignore": "Sluta ignorera",
"Unknown": "Okänd",
"Unshared": "Inte delad",
@@ -498,12 +500,12 @@
"Upgrading": "Uppgraderar",
"Upload Rate": "Sändningshastighet",
"Uptime": "Driftstid",
"Usage reporting is always enabled for candidate releases.": "Användningsrapportering är alltid aktiverad för kandidatutgåvor.",
"Use HTTPS for GUI": "Använd HTTPS för gränssnitt",
"Usage reporting is always enabled for candidate releases.": "Användningsrapportering är alltid aktiverad för utgåvskandidater.",
"Use HTTPS for GUI": "Använd HTTPS för gränssnittet",
"Use notifications from the filesystem to detect changed items.": "Använd aviseringar från filsystemet för att upptäcka ändrade objekt.",
"User": "Användare",
"User Home": "Användarens hemmapp",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Användarnamn och lösenord har inte angetts för autentisering av det grafiska gränssnittet. Överväg att konfigurera dem.",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Användarnamn och lösenord har inte angetts för gränssnittsautentisering. Överväg att konfigurera dem.",
"Using a QUIC connection over LAN": "Använder en QUIC-anslutning över LAN",
"Using a QUIC connection over WAN": "Använder en QUIC-anslutning över WAN",
"Using a direct TCP connection over LAN": "Använder en direkt TCP-anslutning över LAN",
@@ -516,15 +518,15 @@
"Waiting to Scan": "Väntar på skanning",
"Waiting to Sync": "Väntar på synkronisering",
"Warning": "Varning",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en överordnad mapp för en befintlig mapp \"{{otherFolder}}\".",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Varning, denna sökväg är en överordnad mapp för en befintlig mapp \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en undermapp för en befintlig mapp \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Varning, denna sökväg är en undermapp för en befintlig mapp \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Varning: Den här sökvägen är en överordnad mapp till den befintliga mappen ”{{otherFolder}}.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Varning: Den här sökvägen är en överordnad mapp till den befintliga mappen ”{{otherFolderLabel}} ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Varning: Den här sökvägen är en undermapp till den befintliga mappen ”{{otherFolder}}.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Varning: Den här sökvägen är en undermapp till den befintliga mappen ”{{otherFolderLabel}} ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Varning: Om du använder en extern bevakare som {{syncthingInotify}}, bör du se till att den är inaktiverad.",
"Watch for Changes": "Bevaka ändringar",
"Watching for Changes": "Bevakar ändringar",
"Watching for changes discovers most changes without periodic scanning.": "Bevakning av ändringar upptäcker de flesta ändringar utan periodisk skanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet måste den här enheten även läggas till på den andra sidan.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet måste den här enheten även läggas till på den andra enheten.",
"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.": "När du lägger till en ny mapp bör du tänka på att mapp-ID:t används för att koppla ihop mappar mellan enheter. Mapp-ID:n är skiftlägeskänsliga och måste stämma exakt överens på alla enheter.",
"When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.": "När värdet är större än ett på båda enheterna försöker Syncthing upprätta flera samtidiga anslutningar. Om värdena skiljer sig används det högsta. Ange noll för att låta Syncthing bestämma.",
"Yes": "Ja",
@@ -532,14 +534,14 @@
"You can also copy and paste the text into a new message manually.": "Du kan också kopiera och klistra in texten i ett nytt meddelande manuellt.",
"You can also select one of these nearby devices:": "Du kan också välja en av dessa närliggande enheter:",
"You can change your choice at any time in the Settings dialog.": "Du kan ändra ditt val när som helst i inställningsdialogrutan.",
"You can read more about the two release channels at the link below.": "Du kan läsa mer om de två utgivningskanalerna via länken nedan.",
"You can read more about the two release channels at the link below.": "Du kan läsa mer om de två utgåvekanalerna via 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 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.",
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Din sms-app bör öppnas så att du kan välja mottagare och skicka meddelandet från ditt eget nummer.",
"Your email app should open to let you choose the recipient and send it from your own address.": "Din e-postapp bör öppnas så att du kan välja mottagare och skicka den från din egen adress.",
"Your email app should open to let you choose the recipient and send it from your own address.": "Din e-postapp bör öppnas så att du kan välja mottagare och skicka meddelandet från din egen adress.",
"days": "dagar",
"deleted": "borttagen",
"deny": "neka",
@@ -563,5 +565,5 @@
"unknown device": "okänd enhet",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela mappen \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vill dela mappen \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} kan återinföra denna enhet."
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} kan återinföra den här enheten."
}
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Keşif Durumu",
"Dismiss": "Yoksay",
"Do not add it to the ignore list, so this notification may recur.": "Yoksayma listesine eklemeyin, böylece bu bildirim tekrarlayabilir.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Bunu yoksayma listesine eklemeyin, böylece bu cihaz tekrar bağlanırsa bu bildirim yeniden görünecektir.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Bunu yoksayma listesine eklemeyin, böylece bu klasörü sunan cihaz tekrar bağlanırsa bu bildirim yeniden görünecektir.",
"Do not restore": "Geri yükleme yapma",
"Do not restore all": "Hiçbirini geri yükleme",
"Do you want to enable watching for changes for all your folders?": "Tüm klasörleriniz için değişiklikleri izlemeyi etkinleştirmek istiyor musunuz?",
+7 -5
View File
@@ -124,7 +124,9 @@
"Discovery Failures": "设备发现失败",
"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 will reappear if the device connects again.": "不要将其添加到忽略列表中,否则如果设备再次连接,此通知将重新出现。",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "不要将其添加到忽略列表中,否则如果提供此文件夹的设备再次连接,此通知将重新出现。",
"Do not restore": "不要恢复",
"Do not restore all": "不要全部恢复",
"Do you want to enable watching for changes for all your folders?": "是否要启用监视所有文件夹的更改?",
@@ -222,7 +224,7 @@
"Inversion of the given condition (i.e. do not exclude)": "给定条件的反转(即不排除)",
"Keep Versions": "保留版本数量",
"LDAP": "LDAP",
"Largest First": "最大优先",
"Largest First": "从大到小",
"Last 30 Days": "最近 30 天",
"Last 7 Days": "最近 7 天",
"Last Month": "上个月",
@@ -270,7 +272,7 @@
"Never": "从未",
"New Device": "新设备",
"New Folder": "新文件夹",
"Newest First": "最新优先",
"Newest First": "从新到旧",
"No": "否",
"No File Versioning": "不启用文件版本控制",
"No files will be deleted as a result of this operation.": "此操作结果不会删除任何文件。",
@@ -281,7 +283,7 @@
"Number of Connections": "连接数",
"OK": "确定",
"Off": "关闭",
"Oldest First": "最旧优先",
"Oldest First": "从旧到新",
"Optional descriptive label for the folder. Can be different on each device.": "文件夹的可选描述性标签。每个设备上可能不同。",
"Optional group for the device. Can be different on each device.": "设备的可选分组。各设备可设置不同分组。",
"Optional group for the folder. Can be different on each device.": "文件夹的可选分组。各设备可设置不同分组。",
@@ -385,7 +387,7 @@
"Simple File Versioning": "简单文件版本控制",
"Single level wildcard (matches within a directory only)": "单级通配符(仅匹配单层文件夹)",
"Size": "大小",
"Smallest First": "最小优先",
"Smallest First": "从小到大",
"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:": "无法启用某些监听地址以接受连接:",
+4 -4
View File
@@ -49,7 +49,7 @@ type baseDB struct {
tplInput map[string]any
}
func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScripts []string) (*baseDB, error) {
func openBase(path string, maxOpenConns, maxIdleConns int, pragmas, schemaScripts, migrationScripts []string) (*baseDB, error) {
// Open the database with options to enable foreign keys and recursive
// triggers (needed for the delete+insert triggers on row replace).
pathURL := url.URL{
@@ -62,8 +62,8 @@ func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScript
return nil, wrap(err)
}
sqlDB.SetMaxOpenConns(maxConns)
sqlDB.SetMaxIdleConns(maxConns)
sqlDB.SetMaxOpenConns(maxOpenConns)
sqlDB.SetMaxIdleConns(maxIdleConns)
for _, pragma := range pragmas {
if _, err := sqlDB.Exec("PRAGMA " + pragma); err != nil {
@@ -313,7 +313,7 @@ nextScript:
// files on lines containing only a semicolon and execute them
// separately. We require it on a separate line because there are
// also statement-internal semicolons in the triggers.
for _, stmt := range strings.Split(string(bs), "\n;") {
for stmt := range strings.SplitSeq(string(bs), "\n;") {
if _, err := tx.Exec(s.expandTemplateVars(stmt)); err != nil {
if strings.Contains(stmt, "syncthing:ignore-failure") {
// We're ok with this failing. Just note it.
+17 -3
View File
@@ -11,6 +11,7 @@ import (
"log/slog"
"os"
"path/filepath"
"runtime"
"sync"
"time"
@@ -20,10 +21,23 @@ import (
)
const (
maxDBConns = 6
// maxIdleConns is set so that most db operations will usually fit
// within one of those connections and hence use their persistent page
// cache. Additional connections on top of these will allocate their own
// page cache (same as any other), but it will be deallocated on
// connection close.
maxIdleConns = 4
minDeleteRetention = 24 * time.Hour
)
// maxOpenConns is sized for handling spikes. The primary driver is the
// Copiers folder option which may result in up to 2*NumCPU concurrent
// iterations. We reserve additional space on top of this to serve
// additional operations, some of which may be reentrant (queries within
// iterators) without deadlock.
var maxOpenConns = max(16, 4*runtime.NumCPU())
type DB struct {
*baseDB
@@ -69,7 +83,7 @@ func Open(path string, opts ...Option) (*DB, error) {
initTmpDir(path)
mainPath := filepath.Join(path, "main.db")
mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, migrations)
mainBase, err := openBase(mainPath, maxOpenConns, maxIdleConns, pragmas, schemas, migrations)
if err != nil {
return nil, err
}
@@ -120,7 +134,7 @@ func OpenForMigration(path string) (*DB, error) {
initTmpDir(path)
mainPath := filepath.Join(path, "main.db")
mainBase, err := openBase(mainPath, 1, pragmas, schemas, migrations)
mainBase, err := openBase(mainPath, 1, 1, pragmas, schemas, migrations)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -38,7 +38,7 @@ func openFolderDB(folder, path string, deleteRetention time.Duration) (*folderDB
"sql/migrations/folder/*",
}
base, err := openBase(path, maxDBConns, pragmas, schemas, migrations)
base, err := openBase(path, maxOpenConns, maxIdleConns, pragmas, schemas, migrations)
if err != nil {
return nil, err
}
@@ -75,7 +75,7 @@ func openFolderDBForMigration(folder, path string, deleteRetention time.Duration
"sql/schema/folder/*",
}
base, err := openBase(path, 1, pragmas, schemas, nil)
base, err := openBase(path, 1, 1, pragmas, schemas, nil)
if err != nil {
return nil, err
}
+1 -4
View File
@@ -474,10 +474,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error
// The global version is the first one in the list that is not invalid,
// or just the first one in the list if all are invalid.
var global fileRow
globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() })
if globIdx < 0 {
globIdx = 0
}
globIdx := max(slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() }), 0)
global = es[globIdx]
// We "have" the file if the position in the list of versions is at the
+2 -2
View File
@@ -41,8 +41,8 @@ func SetDefaultLevel(level slog.Level) {
}
func SetLevelOverrides(sttrace string) {
pkgs := strings.Split(sttrace, ",")
for _, pkg := range pkgs {
pkgs := strings.SplitSeq(sttrace, ",")
for pkg := range pkgs {
pkg = strings.TrimSpace(pkg)
if pkg == "" {
continue
+2 -2
View File
@@ -45,11 +45,11 @@ type adapter struct {
l *slog.Logger
}
func (a adapter) Debugln(vals ...interface{}) {
func (a adapter) Debugln(vals ...any) {
a.log(strings.TrimSpace(fmt.Sprintln(vals...)), slog.LevelDebug)
}
func (a adapter) Debugf(format string, vals ...interface{}) {
func (a adapter) Debugf(format string, vals ...any) {
a.log(fmt.Sprintf(format, vals...), slog.LevelDebug)
}
+28 -20
View File
@@ -202,7 +202,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
return listener, nil
}
func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
func sendJSON(w http.ResponseWriter, jsonObject any) {
w.Header().Set("Content-Type", "application/json")
// Marshalling might fail, in which case we should return a 500 with the
// actual error.
@@ -696,7 +696,7 @@ func (*service) getSystemPaths(w http.ResponseWriter, _ *http.Request) {
}
func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
meta, _ := json.Marshal(map[string]interface{}{
meta, _ := json.Marshal(map[string]any{
"deviceID": s.id.String(),
"deviceIDShort": s.id.Short().String(),
"authenticated": true,
@@ -706,7 +706,7 @@ func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
}
func (*service) getSystemVersion(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"version": build.Version,
"codename": build.Codename,
"longVersion": build.LongVersion,
@@ -838,7 +838,7 @@ func (s *service) getDBNeed(w http.ResponseWriter, r *http.Request) {
}
// Convert the struct to a more loose structure, and inject the size.
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"progress": toJsonFileInfoSlice(progress),
"queued": toJsonFileInfoSlice(queued),
"rest": toJsonFileInfoSlice(rest),
@@ -866,7 +866,7 @@ func (s *service) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
return
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"files": toJsonFileInfoSlice(files),
"page": page,
"perpage": perpage,
@@ -886,7 +886,7 @@ func (s *service) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
return
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"files": toJsonFileInfoSlice(files),
"page": page,
"perpage": perpage,
@@ -951,7 +951,7 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
return
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
"availability": av,
@@ -967,7 +967,7 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
gf, _, _ := s.model.CurrentGlobalFile(folder, file)
av, _ := s.model.Availability(folder, protocol.FileInfo{Name: file}, protocol.BlockInfo{})
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
"availability": av,
@@ -1037,7 +1037,7 @@ func (s *service) getSystemStatus(w http.ResponseWriter, _ *http.Request) {
runtime.ReadMemStats(&m)
tilde, _ := fs.ExpandTilde("~")
res := make(map[string]interface{})
res := make(map[string]any)
res["myID"] = s.id.String()
res["goroutines"] = runtime.NumGoroutine()
res["alloc"] = m.Alloc
@@ -1184,10 +1184,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
}
// Metrics data as text
var metricsBuf bytes.Buffer
wr := bufferedResponseWriter{Writer: &metricsBuf}
promhttp.Handler().ServeHTTP(wr, &http.Request{Method: http.MethodGet})
files = append(files, fileEntry{name: "metrics.txt", data: metricsBuf.Bytes()})
files = append(files, fileEntry{name: "metrics.txt", data: prometheusMetrics()})
// Connection data as JSON
connStats := s.model.ConnectionStats()
@@ -1258,6 +1255,16 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
io.Copy(w, &zipFilesBuffer)
}
func prometheusMetrics() []byte {
var metricsBuf bytes.Buffer
wr := bufferedResponseWriter{Writer: &metricsBuf}
promhttp.Handler().ServeHTTP(wr, &http.Request{
Method: http.MethodGet,
URL: &url.URL{Scheme: "http://", Host: "localhost", Path: "/metrics"},
})
return metricsBuf.Bytes()
}
func (s *service) getSystemDiscovery(w http.ResponseWriter, _ *http.Request) {
devices := make(map[string]discover.CacheEntry)
@@ -1302,7 +1309,7 @@ func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
folder := qs.Get("folder")
lines, patterns, err := s.model.LoadIgnores(folder)
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"ignore": lines,
"expanded": patterns,
"error": errorString(err),
@@ -1411,7 +1418,7 @@ func (s *service) getSystemUpgrade(w http.ResponseWriter, _ *http.Request) {
httpError(w, err)
return
}
res := make(map[string]interface{})
res := make(map[string]any)
res["running"] = build.Version
res["latest"] = rel.Tag
res["newer"] = upgrade.CompareVersions(rel.Tag, build.Version) == upgrade.Newer
@@ -1439,7 +1446,7 @@ func (*service) getDeviceID(w http.ResponseWriter, r *http.Request) {
func (*service) getLang(w http.ResponseWriter, r *http.Request) {
lang := r.Header.Get("Accept-Language")
weights := make(map[string]float64)
for _, l := range strings.Split(lang, ",") {
for l := range strings.SplitSeq(lang, ",") {
parts := strings.SplitN(l, ";", 2)
code := strings.ToLower(strings.TrimSpace(parts[0]))
weights[code] = 1.0
@@ -1638,7 +1645,7 @@ func (s *service) getFolderErrors(w http.ResponseWriter, r *http.Request) {
}
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"folder": folder,
"errors": errors,
"page": page,
@@ -1770,8 +1777,8 @@ func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(m)
}
func fileIntfJSONMap(f protocol.FileInfo) map[string]interface{} {
out := map[string]interface{}{
func fileIntfJSONMap(f protocol.FileInfo) map[string]any {
out := map[string]any{
"name": f.FileName(),
"type": f.FileType().String(),
"size": f.Size,
@@ -1946,7 +1953,8 @@ func sanitizedHostname(name string) (string, error) {
return r > unicode.MaxASCII ||
!unicode.IsLetter(r) && !unicode.IsNumber(r) &&
r != '.' && r != '-'
})))
})),
)
name, _, err := transform.String(t, name)
if err != nil {
return "", err
+1 -4
View File
@@ -311,10 +311,7 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
func formatOptionalPercentS(template string, username string) string {
var replacements []any
nReps := strings.Count(template, "%s") - strings.Count(template, "%%s")
if nReps < 0 {
nReps = 0
}
nReps := max(strings.Count(template, "%s")-strings.Count(template, "%%s"), 0)
for range nReps {
replacements = append(replacements, username)
}
+9 -2
View File
@@ -433,7 +433,6 @@ func TestAPIServiceRequests(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(tc.URL, func(t *testing.T) {
t.Parallel()
testHTTPRequest(t, baseURL, tc, testAPIKey)
@@ -1723,7 +1722,7 @@ func TestConfigChanges(t *testing.T) {
return resp
}
mod := func(method, path string, data interface{}) {
mod := func(method, path string, data any) {
t.Helper()
bs, err := json.Marshal(data)
if err != nil {
@@ -1830,6 +1829,14 @@ func TestSanitizedHostname(t *testing.T) {
}
}
func TestPrometheusMetrics(t *testing.T) {
// We should get some form of reasonable metrics response
bs := prometheusMetrics()
if !bytes.Contains(bs, []byte("TYPE go_info gauge")) {
t.Error("metrics should include go_info gauge")
}
}
// runningInContainer returns true if we are inside Docker or LXC. It might
// be prone to false negatives if things change in the future, but likely
// not false positives.
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build noassets
// +build noassets
package auto
+1 -1
View File
@@ -441,7 +441,7 @@ func (c *configMuxBuilder) adjustLDAP(w http.ResponseWriter, r *http.Request, ld
}
// Unmarshals the content of the given body and stores it in to (i.e. to must be a pointer).
func unmarshalTo(body io.ReadCloser, to interface{}) error {
func unmarshalTo(body io.ReadCloser, to any) error {
bs, err := io.ReadAll(body)
body.Close()
if err != nil {
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build race
// +build race
package build
+2 -2
View File
@@ -664,7 +664,7 @@ func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[pr
defaults.Device.prepare(nil)
}
func ensureZeroForNodefault(empty interface{}, target interface{}) {
func ensureZeroForNodefault(empty any, target any) {
copyMatchingTag(empty, target, "nodefault", func(v string) bool {
if len(v) > 0 && v != "true" {
panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
@@ -674,7 +674,7 @@ func ensureZeroForNodefault(empty interface{}, target interface{}) {
}
// copyMatchingTag copies fields tagged tag:"value" from "from" struct onto "to" struct.
func copyMatchingTag(from interface{}, to interface{}, tag string, shouldCopy func(value string) bool) {
func copyMatchingTag(from any, to any, tag string, shouldCopy func(value string) bool) {
fromStruct := reflect.ValueOf(from).Elem()
fromType := fromStruct.Type()
+6 -6
View File
@@ -1158,8 +1158,8 @@ func TestInvalidDeviceIDRejected(t *testing.T) {
// Change the device ID of the first device to "invalid". Fast and loose
// with the type assertions as we know what the JSON decoder returns.
devs := cfg["devices"].([]interface{})
dev0 := devs[0].(map[string]interface{})
devs := cfg["devices"].([]any)
dev0 := devs[0].(map[string]any)
dev0["deviceID"] = tc.id
devs[0] = dev0
@@ -1197,8 +1197,8 @@ func TestInvalidFolderIDRejected(t *testing.T) {
// Change the folder ID of the first folder to the empty string.
// Fast and loose with the type assertions as we know what the JSON
// decoder returns.
devs := cfg["folders"].([]interface{})
dev0 := devs[0].(map[string]interface{})
devs := cfg["folders"].([]any)
dev0 := devs[0].(map[string]any)
dev0["id"] = tc.id
devs[0] = dev0
@@ -1324,7 +1324,7 @@ func adjustFolderConfiguration(cfg *FolderConfiguration, id, label string, fsTyp
// defaultConfigAsMap returns a valid default config as a JSON-decoded
// map[string]interface{}. This is useful to override random elements and
// re-encode into JSON.
func defaultConfigAsMap() map[string]interface{} {
func defaultConfigAsMap() map[string]any {
cfg := New(device1)
dev := cfg.Defaults.Device.Copy()
adjustDeviceConfiguration(&dev, device2, "name")
@@ -1337,7 +1337,7 @@ func defaultConfigAsMap() map[string]interface{} {
// can't happen
panic(err)
}
var tmp map[string]interface{}
var tmp map[string]any
if err := json.Unmarshal(bs, &tmp); err != nil {
// can't happen
panic(err)
+2 -3
View File
@@ -9,6 +9,7 @@ package config
import (
"encoding/json"
"encoding/xml"
"maps"
"slices"
"strings"
@@ -45,9 +46,7 @@ type internalParam struct {
func (c VersioningConfiguration) Copy() VersioningConfiguration {
cp := c
cp.Params = make(map[string]string, len(c.Params))
for k, v := range c.Params {
cp.Params[k] = v
}
maps.Copy(cp.Params, c.Params)
return cp
}
+2 -2
View File
@@ -401,7 +401,7 @@ func TestConnectionEstablishment(t *testing.T) {
}
}
func withConnectionPair(b interface{ Fatal(...interface{}) }, connUri string, h func(client, server internalConn)) {
func withConnectionPair(b interface{ Fatal(...any) }, connUri string, h func(client, server internalConn)) {
// Root of the service tree.
supervisor := suture.New("main", suture.Spec{
PassThroughPanics: true,
@@ -494,7 +494,7 @@ func withConnectionPair(b interface{ Fatal(...interface{}) }, connUri string, h
_ = serverConn.Close()
}
func mustGetCert(b interface{ Fatal(...interface{}) }) tls.Certificate {
func mustGetCert(b interface{ Fatal(...any) }) tls.Certificate {
cert, err := tlsutil.NewCertificateInMemory("bench", 10)
if err != nil {
b.Fatal(err)
+2 -2
View File
@@ -54,7 +54,7 @@ func TestDialQueueSort(t *testing.T) {
var seen1, seen2 int
for i := 0; i < 100; i++ {
for range 100 {
queue.Sort()
res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) {
@@ -90,7 +90,7 @@ func TestDialQueueSort(t *testing.T) {
var seen1, seen2 int
for i := 0; i < 100; i++ {
for range 100 {
queue.Sort()
res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) {
+5 -9
View File
@@ -256,18 +256,14 @@ func (w *limitedWriter) Write(buf []byte) (int, error) {
// try to be a bit adaptable. We range from the minimum write size of 1
// KiB up to the limiter burst size, aiming for about a write every
// 10ms.
singleWriteSize := int(w.waiter.Limit() / 100) // 10ms worth of data
singleWriteSize = ((singleWriteSize / 1024) + 1) * 1024 // round up to the next kibibyte
if singleWriteSize > limiterBurstSize {
singleWriteSize = limiterBurstSize
}
singleWriteSize := int(w.waiter.Limit() / 100) // 10ms worth of data
singleWriteSize = min(
// round up to the next kibibyte
((singleWriteSize/1024)+1)*1024, limiterBurstSize)
written := 0
for written < len(buf) {
toWrite := singleWriteSize
if toWrite > len(buf)-written {
toWrite = len(buf) - written
}
toWrite := min(singleWriteSize, len(buf)-written)
w.take(toWrite)
n, err := w.writer.Write(buf[written : written+toWrite])
written += n
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build go1.15 && !noquic
// +build go1.15,!noquic
package connections
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !noquic
// +build !noquic
package connections
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !noquic
// +build !noquic
package connections
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build noquic
// +build noquic
package connections
+6 -6
View File
@@ -18,23 +18,23 @@ import (
type Registry struct {
mut sync.Mutex
available map[string][]interface{}
available map[string][]any
}
func New() *Registry {
return &Registry{
available: make(map[string][]interface{}),
available: make(map[string][]any),
}
}
func (r *Registry) Register(scheme string, item interface{}) {
func (r *Registry) Register(scheme string, item any) {
r.mut.Lock()
defer r.mut.Unlock()
r.available[scheme] = append(r.available[scheme], item)
}
func (r *Registry) Unregister(scheme string, item interface{}) {
func (r *Registry) Unregister(scheme string, item any) {
r.mut.Lock()
defer r.mut.Unlock()
@@ -49,12 +49,12 @@ func (r *Registry) Unregister(scheme string, item interface{}) {
// Get returns an item for a schema compatible with the given scheme.
// If any item satisfies preferred, that has precedence over other items.
func (r *Registry) Get(scheme string, preferred func(interface{}) bool) interface{} {
func (r *Registry) Get(scheme string, preferred func(any) bool) any {
r.mut.Lock()
defer r.mut.Unlock()
var (
best interface{}
best any
bestPref bool
bestScheme string
)
+4 -4
View File
@@ -14,8 +14,8 @@ import (
func TestRegistry(t *testing.T) {
r := New()
want := func(i int) func(interface{}) bool {
return func(x interface{}) bool { return x.(int) == i }
want := func(i int) func(any) bool {
return func(x any) bool { return x.(int) == i }
}
if res := r.Get("int", want(1)); res != nil {
@@ -73,7 +73,7 @@ func TestShortSchemeFirst(t *testing.T) {
r.Register("foobar", 1)
// If we don't care about the value, we should get the one with "foo".
res := r.Get("foo", func(interface{}) bool { return false })
res := r.Get("foo", func(any) bool { return false })
if res != 0 {
t.Error("unexpected", res)
}
@@ -89,7 +89,7 @@ func BenchmarkGet(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Get("tcp", func(x interface{}) bool {
r.Get("tcp", func(x any) bool {
return x.(*net.TCPAddr).IP.IsUnspecified()
})
}
+3 -4
View File
@@ -19,6 +19,7 @@ import (
"fmt"
"io"
"log/slog"
"maps"
"math"
"net"
"net/url"
@@ -817,7 +818,7 @@ func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
}
func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) {
s.evLogger.Log(events.ListenAddressesChanged, map[string]interface{}{
s.evLogger.Log(events.ListenAddressesChanged, map[string]any{
"address": l.URI,
"lan": l.LANAddresses,
"wan": l.WANAddresses,
@@ -995,9 +996,7 @@ func newConnectionStatusHandler() connectionStatusHandler {
func (s *connectionStatusHandler) ConnectionStatus() map[string]ConnectionStatusEntry {
result := make(map[string]ConnectionStatusEntry)
s.connectionStatusMut.RLock()
for k, v := range s.connectionStatus {
result[k] = v
}
maps.Copy(result, s.connectionStatus)
s.connectionStatusMut.RUnlock()
return result
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !solaris && !windows
// +build !solaris,!windows
package dialer
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build solaris
// +build solaris
package dialer
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package dialer
+1 -1
View File
@@ -111,7 +111,7 @@ func DialContextReusePortFunc(registry *registry.Registry) func(ctx context.Cont
return DialContext(ctx, network, addr)
}
localAddrInterface := registry.Get(network, func(addr interface{}) bool {
localAddrInterface := registry.Get(network, func(addr any) bool {
return addr.(*net.TCPAddr).IP.IsUnspecified()
})
if localAddrInterface == nil {
+2 -3
View File
@@ -7,6 +7,7 @@
package discover
import (
"maps"
"sync"
"time"
@@ -60,9 +61,7 @@ func (c *cache) Get(id protocol.DeviceID) (CacheEntry, bool) {
func (c *cache) Cache() map[protocol.DeviceID]CacheEntry {
c.mut.Lock()
m := make(map[protocol.DeviceID]CacheEntry, len(c.entries))
for k, v := range c.entries {
m[k] = v
}
maps.Copy(m, c.entries)
c.mut.Unlock()
return m
}
+1 -1
View File
@@ -294,7 +294,7 @@ func (c *localClient) registerDevice(src net.Addr, device *discoproto.Announce)
})
if isNewDevice {
c.evLogger.Log(events.DeviceDiscovered, map[string]interface{}{
c.evLogger.Log(events.DeviceDiscovered, map[string]any{
"device": id.String(),
"addrs": validAddresses,
})
+7 -7
View File
@@ -235,7 +235,7 @@ const BufferSize = 64
type Logger interface {
suture.Service
Log(t EventType, data interface{})
Log(t EventType, data any)
Subscribe(mask EventType) Subscription
}
@@ -253,10 +253,10 @@ type Event struct {
// Per-subscription sequential event ID. Named "id" for backwards compatibility with the REST API
SubscriptionID int `json:"id"`
// Global ID of the event across all subscriptions
GlobalID int `json:"globalID"`
Time time.Time `json:"time"`
Type EventType `json:"type"`
Data interface{} `json:"data"`
GlobalID int `json:"globalID"`
Time time.Time `json:"time"`
Type EventType `json:"type"`
Data any `json:"data"`
}
type Subscription interface {
@@ -325,7 +325,7 @@ loop:
return nil
}
func (l *logger) Log(t EventType, data interface{}) {
func (l *logger) Log(t EventType, data any) {
l.events <- Event{
Time: time.Now(), // intentionally high precision
Type: t,
@@ -559,7 +559,7 @@ var NoopLogger Logger = &noopLogger{}
func (*noopLogger) Serve(_ context.Context) error { return nil }
func (*noopLogger) Log(_ EventType, _ interface{}) {}
func (*noopLogger) Log(_ EventType, _ any) {}
func (*noopLogger) Subscribe(_ EventType) Subscription {
return &noopSubscription{}
+4 -4
View File
@@ -127,7 +127,7 @@ func TestBufferOverflow(t *testing.T) {
t0 := time.Now()
const nEvents = BufferSize * 2
for i := 0; i < nEvents; i++ {
for range nEvents {
l.Log(DeviceConnected, "foo")
}
if d := time.Since(t0); d > 15*time.Second {
@@ -237,7 +237,7 @@ func TestBufferedSub(t *testing.T) {
bs := NewBufferedSubscription(s, 10*BufferSize)
go func() {
for i := 0; i < 10*BufferSize; i++ {
for i := range 10 * BufferSize {
l.Log(DeviceConnected, fmt.Sprintf("event-%d", i))
if i%30 == 0 {
// Give the buffer routine time to pick up the events
@@ -378,7 +378,7 @@ func TestUnsubscribeContention(t *testing.T) {
stopListeners := make(chan struct{})
var listenerWg sync.WaitGroup
for i := 0; i < listeners; i++ {
for range listeners {
listenerWg.Go(func() {
s := l.Subscribe(AllEvents)
defer s.Unsubscribe()
@@ -400,7 +400,7 @@ func TestUnsubscribeContention(t *testing.T) {
stopSenders := make(chan struct{})
defer close(stopSenders)
var senderWg sync.WaitGroup
for i := 0; i < senders; i++ {
for range senders {
senderWg.Go(func() {
t := time.NewTicker(time.Millisecond)
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || solaris
// +build linux solaris
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || android
// +build linux android
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !linux && !android && !windows
// +build !linux,!android,!windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package fs
+1 -1
View File
@@ -580,7 +580,7 @@ func TestXattr(t *testing.T) {
// Create a set of random attributes that we will set and read back
var attrs []protocol.Xattr
for i := 0; i < 10; i++ {
for i := range 10 {
key := fmt.Sprintf("user.test-%d", i)
value := make([]byte, xattrSize())
rand.Read(value)
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux
// +build !linux
package fs
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build darwin && !kqueue && cgo && !ios
// +build darwin,!kqueue,cgo,!ios
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build solaris && cgo
// +build solaris,cgo
package fs
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build dragonfly || freebsd || netbsd || openbsd || ios || kqueue
// +build dragonfly freebsd netbsd openbsd ios kqueue
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux && !windows && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !darwin && !cgo && !ios
// +build !linux,!windows,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!darwin,!cgo,!ios
// Catch all platforms that are not specifically handled to use the generic
// event types.
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !dragonfly && !freebsd && !netbsd && !openbsd && !kqueue && !ios
// +build !dragonfly,!freebsd,!netbsd,!openbsd,!kqueue,!ios
package fs
+2 -4
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build (!solaris && !darwin) || (solaris && cgo) || (darwin && cgo)
// +build !solaris,!darwin solaris,cgo darwin,cgo
package fs
@@ -364,8 +363,7 @@ func TestWatchSymlinkedRoot(t *testing.T) {
linkedFs := NewFilesystem(FilesystemTypeBasic, filepath.Join(testFs.URI(), link))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := t.Context()
if _, _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil {
panic(err)
}
@@ -635,6 +633,6 @@ func (fakeEventInfo) Event() notify.Event {
return notify.Write
}
func (fakeEventInfo) Sys() interface{} {
func (fakeEventInfo) Sys() any {
return nil
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build (solaris && !cgo) || (darwin && !cgo) || (darwin && kqueue)
// +build solaris,!cgo darwin,!cgo darwin,kqueue
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build freebsd || netbsd
// +build freebsd netbsd
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || darwin
// +build linux darwin
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows && !dragonfly && !illumos && !solaris && !openbsd
// +build !windows,!dragonfly,!illumos,!solaris,!openbsd
package fs

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