Compare commits
181
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
240e7b0835 | ||
|
|
baf5191433 | ||
|
|
2645e87766 | ||
|
|
ec8bc02d33 | ||
|
|
c1c41242bb | ||
|
|
169ff73d26 | ||
|
|
d985ed553a | ||
|
|
dea1ef24d9 | ||
|
|
49f29a0453 | ||
|
|
76af9ba53d | ||
|
|
2de364414f | ||
|
|
5ae84970e7 | ||
|
|
141b0d38a6 | ||
|
|
44891b6924 | ||
|
|
f008588307 | ||
|
|
e481d03b5e | ||
|
|
a8a73b60c4 | ||
|
|
7e8b76e8ea | ||
|
|
36c746bd9f | ||
|
|
7476c583e7 | ||
|
|
89928ca8e4 | ||
|
|
38a3bf3ada | ||
|
|
96b3d31b42 | ||
|
|
362ae5c4bb | ||
|
|
dc303c2a71 | ||
|
|
be2ca0ea22 | ||
|
|
460cb19839 | ||
|
|
ddfebb17cf | ||
|
|
375c9dd116 | ||
|
|
15716a0772 | ||
|
|
6f6c1cd330 | ||
|
|
36ac757c3a | ||
|
|
b614cfffcb | ||
|
|
b58a52c7b4 | ||
|
|
a80fc1b062 | ||
|
|
90d18189da | ||
|
|
22a2e95126 | ||
|
|
11e1a99e14 | ||
|
|
3c6bfb880d | ||
|
|
ad2c05c3f5 | ||
|
|
fa75f54a05 | ||
|
|
6405fd4770 | ||
|
|
f42f20c70c | ||
|
|
209bc59e8c | ||
|
|
84ee86f6b3 | ||
|
|
ed7791d824 | ||
|
|
0580b10385 | ||
|
|
47c7351b16 | ||
|
|
b158072a15 | ||
|
|
b6486c26e6 | ||
|
|
da0ac96704 | ||
|
|
af1fbda892 | ||
|
|
2234c45c19 | ||
|
|
fd38fb684a | ||
|
|
e0a16e08dd | ||
|
|
43189dfe3a | ||
|
|
46d4f6037d | ||
|
|
e522811a52 | ||
|
|
6124bbb12a | ||
|
|
fbf911cf7e | ||
|
|
7fdfa81fb8 | ||
|
|
a4673f3007 | ||
|
|
24c499d282 | ||
|
|
4581c57478 | ||
|
|
f177924629 | ||
|
|
633e888ba7 | ||
|
|
4316992d95 | ||
|
|
5ecb8bdd8a | ||
|
|
3b81d4b8a5 | ||
|
|
f91191218b | ||
|
|
05c79ac8c2 | ||
|
|
6e126fb97e | ||
|
|
22783d8f6c | ||
|
|
372c96c6b2 | ||
|
|
7073b8721a | ||
|
|
fccb9c0bf4 | ||
|
|
f9f12131ae | ||
|
|
7e0106da0c | ||
|
|
d180bc794b | ||
|
|
eab5fd5bdd | ||
|
|
e3ca797dad | ||
|
|
11b2815b88 | ||
|
|
541d05df1b | ||
|
|
c0554c9fbf | ||
|
|
84365882de | ||
|
|
68c5dcd83d | ||
|
|
7fe1fdd8c7 | ||
|
|
37cbe68204 | ||
|
|
f76a66fc55 | ||
|
|
d7949aa58e | ||
|
|
f0c0c5483f | ||
|
|
388a29bbe2 | ||
|
|
7c6a310179 | ||
|
|
6a36ec63d7 | ||
|
|
9c8b907ff1 | ||
|
|
f769df16e8 | ||
|
|
c6f5075721 | ||
|
|
a693698279 | ||
|
|
4949e3ba41 | ||
|
|
ebcdea63c0 | ||
|
|
eb29989dff | ||
|
|
78ef42daa1 | ||
|
|
22e24fc387 | ||
|
|
516d88b072 | ||
|
|
77457e91e9 | ||
|
|
049d92b525 | ||
|
|
f9bd59f031 | ||
|
|
6d0d5bd566 | ||
|
|
35d20a19bc | ||
|
|
dab1c4cfc9 | ||
|
|
f86946c6df | ||
|
|
e97f75cad5 | ||
|
|
2505f82ce5 | ||
|
|
7996ef0d45 | ||
|
|
b05c1a5bb9 | ||
|
|
9dd6f848bd | ||
|
|
e1959afb6b | ||
|
|
c68c78d412 | ||
|
|
b72d31f87f | ||
|
|
95e15c95f2 | ||
|
|
9f871a3726 | ||
|
|
e19e2c123e | ||
|
|
cbe44e1fff | ||
|
|
8e191c8e6b | ||
|
|
19d742b9e4 | ||
|
|
e7db264803 | ||
|
|
3d8a71fdb2 | ||
|
|
6277c0595c | ||
|
|
aa9eda1979 | ||
|
|
1d76efcbcd | ||
|
|
34c2c1ec16 | ||
|
|
bf7fea9a0a | ||
|
|
fdf15f3ca3 | ||
|
|
1cb5875b20 | ||
|
|
1a59a5478f | ||
|
|
5750443371 | ||
|
|
f9132cae85 | ||
|
|
17149741a7 | ||
|
|
d2ec40bb67 | ||
|
|
1a4398cc55 | ||
|
|
529d91fb9d | ||
|
|
108b4e2e10 | ||
|
|
cd0cce4195 | ||
|
|
9f1a72ec88 | ||
|
|
a10c621e33 | ||
|
|
2e2d479103 | ||
|
|
35f0e355bf | ||
|
|
4395711d26 | ||
|
|
aba915037f | ||
|
|
442e93d3fc | ||
|
|
3450b5f80c | ||
|
|
f76b5d8002 | ||
|
|
7e54868206 | ||
|
|
d84a8e6404 | ||
|
|
4833b6085c | ||
|
|
2ceaca8828 | ||
|
|
d02158c0ef | ||
|
|
6213c4f2cd | ||
|
|
cd34eea017 | ||
|
|
7a0a702ec0 | ||
|
|
d9ed8e125e | ||
|
|
36708a5067 | ||
|
|
8c32955da1 | ||
|
|
c111ed4b20 | ||
|
|
a3ea9427d1 | ||
|
|
09b534b8a3 | ||
|
|
190c61ba2f | ||
|
|
dc71ec734d | ||
|
|
3af96e50bd | ||
|
|
ddc56c8a0d | ||
|
|
e0da2764c9 | ||
|
|
ad29093ac1 | ||
|
|
28610a9a42 | ||
|
|
65eb528e2d | ||
|
|
ec9d68960f | ||
|
|
c618eba9a9 | ||
|
|
1ef8378a30 | ||
|
|
43289103cb | ||
|
|
1bc5632771 | ||
|
|
4b488a2d28 | ||
|
|
f28367bcfc |
@@ -1,6 +1,7 @@
|
||||
# This is the official list of Syncthing authors for copyright purposes.
|
||||
|
||||
Aaron Bieber <qbit@deftly.net>
|
||||
Adam Piggott <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
|
||||
Alexander Graf <register-github@alex-graf.de>
|
||||
Andrew Dunham <andrew@du.nham.ca>
|
||||
Antony Male <antony.male@gmail.com>
|
||||
@@ -47,6 +48,8 @@ Lord Landon Agahnim <lordlandon@gmail.com>
|
||||
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
|
||||
Marc Pujol <kilburn@la3.org>
|
||||
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||
Mateon1 <matin1111@wp.pl>
|
||||
Matt Burke <mburke@amplify.com>
|
||||
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||
Michael Tilli <pyfisch@gmail.com>
|
||||
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
|
||||
Generated
+4
-19
@@ -13,10 +13,6 @@
|
||||
"ImportPath": "github.com/calmh/du",
|
||||
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/logger",
|
||||
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/luhn",
|
||||
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
|
||||
@@ -37,25 +33,14 @@
|
||||
"ImportPath": "github.com/kardianos/osext",
|
||||
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syncthing/protocol",
|
||||
"Rev": "f91191218b192ace841c878f161832d19c09145a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syncthing/relaysrv/client",
|
||||
"Rev": "6e126fb97e2ff566d35f8d8824e86793d22b2147"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syncthing/relaysrv/protocol",
|
||||
"Rev": "6e126fb97e2ff566d35f8d8824e86793d22b2147"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||
"Rev": "1a9d62f03ea92815b46fcaab357cfd4df264b1a0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/thejerf/suture",
|
||||
"Rev": "860b44045335c64a6d54ac7eed22a3aedfc687c9"
|
||||
"Comment": "v1.0.1",
|
||||
"Rev": "99c1f2d613756768fc4299acd9dc621e11ed3fd7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vitrun/qart/coding",
|
||||
@@ -79,11 +64,11 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/iana",
|
||||
"Rev": "66f0418ca41253f8d1a024eb9754e9441a8e79b9"
|
||||
"Rev": "db8e4de5b2d6653f66aea53094624468caad15d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/ipv6",
|
||||
"Rev": "66f0418ca41253f8d1a024eb9754e9441a8e79b9"
|
||||
"Rev": "db8e4de5b2d6653f66aea53094624468caad15d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/transform",
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
logger
|
||||
======
|
||||
|
||||
A small wrapper around `log` to provide log levels.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
http://godoc.org/github.com/calmh/logger
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT
|
||||
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||
|
||||
// Package logger implements a standardized logger with callback functionality
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
LevelDebug LogLevel = iota
|
||||
LevelVerbose
|
||||
LevelInfo
|
||||
LevelOK
|
||||
LevelWarn
|
||||
LevelFatal
|
||||
NumLevels
|
||||
)
|
||||
|
||||
// A MessageHandler is called with the log level and message text.
|
||||
type MessageHandler func(l LogLevel, msg string)
|
||||
|
||||
type Logger struct {
|
||||
logger *log.Logger
|
||||
handlers [NumLevels][]MessageHandler
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
// The default logger logs to standard output with a time prefix.
|
||||
var DefaultLogger = New()
|
||||
|
||||
func New() *Logger {
|
||||
if os.Getenv("LOGGER_DISCARD") != "" {
|
||||
// Hack to completely disable logging, for example when running benchmarks.
|
||||
return &Logger{
|
||||
logger: log.New(ioutil.Discard, "", 0),
|
||||
}
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
logger: log.New(os.Stdout, "", log.Ltime),
|
||||
}
|
||||
}
|
||||
|
||||
// AddHandler registers a new MessageHandler to receive messages with the
|
||||
// specified log level or above.
|
||||
func (l *Logger) AddHandler(level LogLevel, h MessageHandler) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
l.handlers[level] = append(l.handlers[level], h)
|
||||
}
|
||||
|
||||
// See log.SetFlags
|
||||
func (l *Logger) SetFlags(flag int) {
|
||||
l.logger.SetFlags(flag)
|
||||
}
|
||||
|
||||
// See log.SetPrefix
|
||||
func (l *Logger) SetPrefix(prefix string) {
|
||||
l.logger.SetPrefix(prefix)
|
||||
}
|
||||
|
||||
func (l *Logger) callHandlers(level LogLevel, s string) {
|
||||
for _, h := range l.handlers[level] {
|
||||
h(level, strings.TrimSpace(s))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugln logs a line with a DEBUG prefix.
|
||||
func (l *Logger) Debugln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "DEBUG: "+s)
|
||||
l.callHandlers(LevelDebug, s)
|
||||
}
|
||||
|
||||
// Debugf logs a formatted line with a DEBUG prefix.
|
||||
func (l *Logger) Debugf(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "DEBUG: "+s)
|
||||
l.callHandlers(LevelDebug, s)
|
||||
}
|
||||
|
||||
// Infoln logs a line with a VERBOSE prefix.
|
||||
func (l *Logger) Verboseln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "VERBOSE: "+s)
|
||||
l.callHandlers(LevelVerbose, s)
|
||||
}
|
||||
|
||||
// Infof logs a formatted line with a VERBOSE prefix.
|
||||
func (l *Logger) Verbosef(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "VERBOSE: "+s)
|
||||
l.callHandlers(LevelVerbose, s)
|
||||
}
|
||||
|
||||
// Infoln logs a line with an INFO prefix.
|
||||
func (l *Logger) Infoln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "INFO: "+s)
|
||||
l.callHandlers(LevelInfo, s)
|
||||
}
|
||||
|
||||
// Infof logs a formatted line with an INFO prefix.
|
||||
func (l *Logger) Infof(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "INFO: "+s)
|
||||
l.callHandlers(LevelInfo, s)
|
||||
}
|
||||
|
||||
// Okln logs a line with an OK prefix.
|
||||
func (l *Logger) Okln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "OK: "+s)
|
||||
l.callHandlers(LevelOK, s)
|
||||
}
|
||||
|
||||
// Okf logs a formatted line with an OK prefix.
|
||||
func (l *Logger) Okf(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "OK: "+s)
|
||||
l.callHandlers(LevelOK, s)
|
||||
}
|
||||
|
||||
// Warnln logs a formatted line with a WARNING prefix.
|
||||
func (l *Logger) Warnln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "WARNING: "+s)
|
||||
l.callHandlers(LevelWarn, s)
|
||||
}
|
||||
|
||||
// Warnf logs a formatted line with a WARNING prefix.
|
||||
func (l *Logger) Warnf(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "WARNING: "+s)
|
||||
l.callHandlers(LevelWarn, s)
|
||||
}
|
||||
|
||||
// Fatalln logs a line with a FATAL prefix and exits the process with exit
|
||||
// code 1.
|
||||
func (l *Logger) Fatalln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "FATAL: "+s)
|
||||
l.callHandlers(LevelFatal, s)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf logs a formatted line with a FATAL prefix and exits the process with
|
||||
// exit code 1.
|
||||
func (l *Logger) Fatalf(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "FATAL: "+s)
|
||||
l.callHandlers(LevelFatal, s)
|
||||
os.Exit(1)
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
l := New()
|
||||
l.SetFlags(0)
|
||||
l.SetPrefix("testing")
|
||||
|
||||
debug := 0
|
||||
l.AddHandler(LevelDebug, checkFunc(t, LevelDebug, "test 0", &debug))
|
||||
info := 0
|
||||
l.AddHandler(LevelInfo, checkFunc(t, LevelInfo, "test 1", &info))
|
||||
warn := 0
|
||||
l.AddHandler(LevelWarn, checkFunc(t, LevelWarn, "test 2", &warn))
|
||||
ok := 0
|
||||
l.AddHandler(LevelOK, checkFunc(t, LevelOK, "test 3", &ok))
|
||||
|
||||
l.Debugf("test %d", 0)
|
||||
l.Debugln("test", 0)
|
||||
l.Infof("test %d", 1)
|
||||
l.Infoln("test", 1)
|
||||
l.Warnf("test %d", 2)
|
||||
l.Warnln("test", 2)
|
||||
l.Okf("test %d", 3)
|
||||
l.Okln("test", 3)
|
||||
|
||||
if debug != 2 {
|
||||
t.Errorf("Debug handler called %d != 2 times", debug)
|
||||
}
|
||||
if info != 2 {
|
||||
t.Errorf("Info handler called %d != 2 times", info)
|
||||
}
|
||||
if warn != 2 {
|
||||
t.Errorf("Warn handler called %d != 2 times", warn)
|
||||
}
|
||||
if ok != 2 {
|
||||
t.Errorf("Ok handler called %d != 2 times", ok)
|
||||
}
|
||||
}
|
||||
|
||||
func checkFunc(t *testing.T, expectl LogLevel, expectmsg string, counter *int) func(LogLevel, string) {
|
||||
return func(l LogLevel, msg string) {
|
||||
*counter++
|
||||
if l != expectl {
|
||||
t.Errorf("Incorrect message level %d != %d", l, expectl)
|
||||
}
|
||||
if !strings.HasSuffix(msg, expectmsg) {
|
||||
t.Errorf("%q does not end with %q", msg, expectmsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
// Copyright (C) 2014 The Protocol Authors.
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "protocol") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
)
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "relay") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
)
|
||||
+11
@@ -37,3 +37,14 @@ easily fit into the OTP paradigm. It ought to someday be considered a good
|
||||
idea to distribute libraries that provide some sort of supervisor tree
|
||||
functionality out of the box. It is possible to provide this functionality
|
||||
without explicitly depending on the Suture library.
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
suture uses semantic versioning.
|
||||
|
||||
1. 1.0.0
|
||||
* Initial release.
|
||||
2. 1.0.1
|
||||
* Fixed data race on the .state variable.
|
||||
|
||||
|
||||
+23
-4
@@ -59,6 +59,7 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
@@ -123,7 +124,6 @@ type Supervisor struct {
|
||||
lastFail time.Time
|
||||
failures float64
|
||||
restartQueue []serviceID
|
||||
state uint8
|
||||
serviceCounter serviceID
|
||||
control chan supervisorMessage
|
||||
resumeTimer <-chan time.Time
|
||||
@@ -143,6 +143,9 @@ type Supervisor struct {
|
||||
// a minimal chunk.
|
||||
getNow func() time.Time
|
||||
getResume func(time.Duration) <-chan time.Time
|
||||
|
||||
sync.Mutex
|
||||
state uint8
|
||||
}
|
||||
|
||||
// Spec is used to pass arguments to the New function to create a
|
||||
@@ -373,6 +376,7 @@ func (s *Supervisor) Add(service Service) ServiceToken {
|
||||
supervisor.logBackoff = s.logBackoff
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
if s.state == notRunning {
|
||||
id := s.serviceCounter
|
||||
s.serviceCounter++
|
||||
@@ -380,8 +384,10 @@ func (s *Supervisor) Add(service Service) ServiceToken {
|
||||
s.services[id] = service
|
||||
s.restartQueue = append(s.restartQueue, id)
|
||||
|
||||
s.Unlock()
|
||||
return ServiceToken{uint64(s.id)<<32 | uint64(id)}
|
||||
}
|
||||
s.Unlock()
|
||||
|
||||
response := make(chan serviceID)
|
||||
s.control <- addService{service, response}
|
||||
@@ -408,16 +414,19 @@ func (s *Supervisor) Serve() {
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.Lock()
|
||||
s.state = notRunning
|
||||
s.Unlock()
|
||||
}()
|
||||
|
||||
s.Lock()
|
||||
if s.state != notRunning {
|
||||
// FIXME: Don't explain why I don't need a semaphore, just use one
|
||||
// This doesn't use a semaphore because it's just a sanity check.
|
||||
s.Unlock()
|
||||
panic("Running a supervisor while it is already running?")
|
||||
}
|
||||
|
||||
s.state = normal
|
||||
s.Unlock()
|
||||
|
||||
// for all the services I currently know about, start them
|
||||
for _, id := range s.restartQueue {
|
||||
@@ -472,7 +481,9 @@ func (s *Supervisor) Serve() {
|
||||
// excessive thrashing
|
||||
// FIXME: Ought to permit some spacing of these functions, rather
|
||||
// than simply hammering through them
|
||||
s.Lock()
|
||||
s.state = normal
|
||||
s.Unlock()
|
||||
s.failures = 0
|
||||
s.logBackoff(s, false)
|
||||
for _, id := range s.restartQueue {
|
||||
@@ -499,7 +510,9 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
|
||||
}
|
||||
|
||||
if s.failures > s.failureThreshold {
|
||||
s.Lock()
|
||||
s.state = paused
|
||||
s.Unlock()
|
||||
s.logBackoff(s, true)
|
||||
s.resumeTimer = s.getResume(s.failureBackoff)
|
||||
}
|
||||
@@ -511,7 +524,13 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
|
||||
// It is possible for a service to be no longer monitored
|
||||
// by the time we get here. In that case, just ignore it.
|
||||
if monitored {
|
||||
if s.state == normal {
|
||||
// this may look dangerous because the state could change, but this
|
||||
// code is only ever run in the one goroutine that is permitted to
|
||||
// change the state, so nothing else will.
|
||||
s.Lock()
|
||||
curState := s.state
|
||||
s.Unlock()
|
||||
if curState == normal {
|
||||
s.runService(failedService, id)
|
||||
s.logFailure(s, failedService, s.failures, s.failureThreshold, true, err, stacktrace)
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ func (i *Incrementor) Serve() {
|
||||
for {
|
||||
select {
|
||||
case i.next <- i.current:
|
||||
i.current += 1
|
||||
i.current++
|
||||
case <-i.stop:
|
||||
// We sync here just to guarantee the output of "Stopping the service",
|
||||
// so this passes the test reliably.
|
||||
|
||||
+20
-4
@@ -478,6 +478,22 @@ func TestNilSupervisorAdd(t *testing.T) {
|
||||
s.Add(s)
|
||||
}
|
||||
|
||||
// https://github.com/thejerf/suture/issues/11
|
||||
//
|
||||
// The purpose of this test is to verify that it does not cause data races,
|
||||
// so there are no obvious assertions.
|
||||
func TestIssue11(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := NewSimple("main")
|
||||
s.ServeBackground()
|
||||
|
||||
subsuper := NewSimple("sub")
|
||||
s.Add(subsuper)
|
||||
|
||||
subsuper.Add(NewService("may cause data race"))
|
||||
}
|
||||
|
||||
// http://golangtutorials.blogspot.com/2011/10/gotest-unit-testing-and-benchmarking-go.html
|
||||
// claims test function are run in the same order as the source file...
|
||||
// I'm not sure if this is part of the contract, though. Especially in the
|
||||
@@ -509,7 +525,7 @@ func (s *FailableService) Serve() {
|
||||
everMultistarted = true
|
||||
panic("Multi-started the same service! " + s.name)
|
||||
}
|
||||
s.existing += 1
|
||||
s.existing++
|
||||
|
||||
s.started <- true
|
||||
|
||||
@@ -522,13 +538,13 @@ func (s *FailableService) Serve() {
|
||||
case Happy:
|
||||
// Do nothing on purpose. Life is good!
|
||||
case Fail:
|
||||
s.existing -= 1
|
||||
s.existing--
|
||||
if useStopChan {
|
||||
s.stop <- true
|
||||
}
|
||||
return
|
||||
case Panic:
|
||||
s.existing -= 1
|
||||
s.existing--
|
||||
panic("Panic!")
|
||||
case Hang:
|
||||
// or more specifically, "hang until I release you"
|
||||
@@ -537,7 +553,7 @@ func (s *FailableService) Serve() {
|
||||
useStopChan = true
|
||||
}
|
||||
case <-s.shutdown:
|
||||
s.existing -= 1
|
||||
s.existing--
|
||||
if useStopChan {
|
||||
s.stop <- true
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ brbecker <brbecker@gmail.com>
|
||||
brendanlong <self@brendanlong.com>
|
||||
brgmnn <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||
bsidhom <bsidhom@gmail.com>
|
||||
burkemw3 <mburke@amplify.com>
|
||||
calmh <jakob@nym.se>
|
||||
canton7 <antony.male@gmail.com>
|
||||
cdata <chris@scriptolo.gy>
|
||||
@@ -41,6 +42,7 @@ kozec <kozec@kozec.com>
|
||||
krozycki <rozycki.karol@gmail.com>
|
||||
marcindziadus <dziadus.marcin@gmail.com>
|
||||
marclaporte <marc@marclaporte.com>
|
||||
mateon1 <matin1111@wp.pl>
|
||||
mogwa1 <devriesb@gmail.com>
|
||||
moshen <moshen.colin@gmail.com>
|
||||
mvdan <mvdan@mvdan.cc>
|
||||
@@ -56,6 +58,7 @@ rumpelsepp <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
|
||||
sciurius <jvromans@squirrel.nl>
|
||||
seehuhn <voss@seehuhn.de>
|
||||
snnd <dw@risu.io>
|
||||
simplypeachy <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
|
||||
timabell <tim@timwise.co.uk>
|
||||
tnn2 <tnn@nygren.pp.se>
|
||||
tojrobinson <tully@tojr.org>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Syncthing
|
||||
=========
|
||||
# Syncthing
|
||||
|
||||
[](http://build.syncthing.net/job/syncthing/lastBuild/)
|
||||
[](https://ci.appveyor.com/project/calmh/syncthing)
|
||||
@@ -11,8 +10,7 @@ This is the Syncthing project which pursues the following goals:
|
||||
1. Define a protocol for synchronization of a folder between a number of
|
||||
collaborating devices. This protocol should be well defined, unambiguous,
|
||||
easily understood, free to use, efficient, secure and language neutral.
|
||||
This is called the [Block Exchange
|
||||
Protocol](https://github.com/syncthing/specs/blob/master/BEPv1.md).
|
||||
This is called the [Block Exchange Protocol][1].
|
||||
|
||||
2. Provide the reference implementation to demonstrate the usability of
|
||||
said protocol. This is the `syncthing` utility. We hope that
|
||||
@@ -22,38 +20,38 @@ The two are evolving together; the protocol is not to be considered
|
||||
stable until Syncthing 1.0 is released, at which point it is locked down
|
||||
for incompatible changes.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
## Getting Started
|
||||
|
||||
Take a look at the [getting started
|
||||
guide](http://docs.syncthing.net/intro/getting-started.html).
|
||||
Take a look at the [getting started guide][2].
|
||||
|
||||
There are a few examples for keeping Syncthing running in the background
|
||||
on your system in [the etc directory](https://github.com/syncthing/syncthing/blob/master/etc).
|
||||
on your system in [the etc directory][3].
|
||||
|
||||
There is an IRC channel, `#syncthing` on Freenode, for talking directly
|
||||
There is an IRC channel, `#syncthing` on [Freenode][4], for talking directly
|
||||
to developers and users.
|
||||
|
||||
Building
|
||||
--------
|
||||
## Building
|
||||
|
||||
Building Syncthing from source is easy, and there's a
|
||||
[guide](http://docs.syncthing.net/dev/building.html).
|
||||
Building Syncthing from source is easy, and there's a [guide][5].
|
||||
that describes it for both Unix and Windows systems.
|
||||
|
||||
Signed Releases
|
||||
---------------
|
||||
## Signed Releases
|
||||
|
||||
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
|
||||
with the key D26E6ED000654A3E (see https://syncthing.net/security.html).
|
||||
For release binaries, MD5 and SHA1 checksums are calculated and signed,
|
||||
available in the md5sum.txt.asc and sha1sum.txt.asc files.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
## Documentation
|
||||
|
||||
Please see the [Syncthing
|
||||
documentation site](http://docs.syncthing.net/).
|
||||
Please see the [Syncthing documentation site][6].
|
||||
|
||||
All code is licensed under the
|
||||
[MPLv2 License](https://github.com/syncthing/syncthing/blob/master/LICENSE).
|
||||
All code is licensed under the [MPLv2 License][7].
|
||||
|
||||
[1]: https://github.com/syncthing/specs/blob/master/BEPv1.md
|
||||
[2]: http://docs.syncthing.net/intro/getting-started.html
|
||||
[3]: https://github.com/syncthing/syncthing/blob/master/etc
|
||||
[4]: https://webchat.freenode.net/
|
||||
[5]: http://docs.syncthing.net/dev/building.html
|
||||
[6]: http://docs.syncthing.net/
|
||||
[7]: https://github.com/syncthing/syncthing/blob/master/LICENSE
|
||||
|
||||
@@ -401,7 +401,7 @@ func assets() {
|
||||
}
|
||||
|
||||
func xdr() {
|
||||
runPrint("go", "generate", "./lib/discover", "./lib/db")
|
||||
runPrint("go", "generate", "./lib/discover", "./lib/db", "./lib/protocol")
|
||||
}
|
||||
|
||||
func translate() {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/scanner"
|
||||
)
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/discover"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
var timeout = 5 * time.Second
|
||||
|
||||
+1
-1
@@ -13,8 +13,8 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
+17
-3
@@ -9,10 +9,24 @@ package main
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
|
||||
debugHTTP = strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all"
|
||||
debugSuture = strings.Contains(os.Getenv("STTRACE"), "suture") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger.NewFacility("main", "Main package")
|
||||
httpl = logger.DefaultLogger.NewFacility("http", "REST API")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("main", strings.Contains(os.Getenv("STTRACE"), "main") || os.Getenv("STTRACE") == "all")
|
||||
l.SetDebug("http", strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
func shouldDebugMain() bool {
|
||||
return l.ShouldDebug("main")
|
||||
}
|
||||
|
||||
func shouldDebugHTTP() bool {
|
||||
return l.ShouldDebug("http")
|
||||
}
|
||||
|
||||
+100
-50
@@ -20,19 +20,20 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/auto"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/discover"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/relay"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -41,21 +42,14 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type guiError struct {
|
||||
Time time.Time `json:"time"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
var (
|
||||
configInSync = true
|
||||
guiErrors = []guiError{}
|
||||
guiErrorsMut = sync.NewMutex()
|
||||
startTime = time.Now()
|
||||
)
|
||||
|
||||
type apiSvc struct {
|
||||
id protocol.DeviceID
|
||||
cfg config.GUIConfiguration
|
||||
cfg *config.Wrapper
|
||||
assetDir string
|
||||
model *model.Model
|
||||
eventSub *events.BufferedSubscription
|
||||
@@ -65,9 +59,12 @@ type apiSvc struct {
|
||||
fss *folderSummarySvc
|
||||
stop chan struct{}
|
||||
systemConfigMut sync.Mutex
|
||||
|
||||
guiErrors *logger.Recorder
|
||||
systemLog *logger.Recorder
|
||||
}
|
||||
|
||||
func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc) (*apiSvc, error) {
|
||||
func newAPISvc(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) (*apiSvc, error) {
|
||||
svc := &apiSvc{
|
||||
id: id,
|
||||
cfg: cfg,
|
||||
@@ -77,14 +74,21 @@ func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir strin
|
||||
discoverer: discoverer,
|
||||
relaySvc: relaySvc,
|
||||
systemConfigMut: sync.NewMutex(),
|
||||
guiErrors: errors,
|
||||
systemLog: systemLog,
|
||||
}
|
||||
|
||||
var err error
|
||||
svc.listener, err = svc.getListener(cfg)
|
||||
svc.listener, err = svc.getListener(cfg.GUI())
|
||||
return svc, err
|
||||
}
|
||||
|
||||
func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error) {
|
||||
if guiAddress != "" {
|
||||
// Override from the environment
|
||||
cfg.Address = guiAddress
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
|
||||
if err != nil {
|
||||
l.Infoln("Loading HTTPS certificate:", err)
|
||||
@@ -133,8 +137,6 @@ func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error)
|
||||
func (s *apiSvc) Serve() {
|
||||
s.stop = make(chan struct{})
|
||||
|
||||
l.AddHandler(logger.LevelWarn, s.showGuiError)
|
||||
|
||||
// The GET handlers
|
||||
getRestMux := http.NewServeMux()
|
||||
getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
|
||||
@@ -159,6 +161,9 @@ func (s *apiSvc) Serve() {
|
||||
getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
|
||||
getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
|
||||
getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
|
||||
getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
|
||||
getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
|
||||
getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
|
||||
|
||||
// The POST handlers
|
||||
postRestMux := http.NewServeMux()
|
||||
@@ -176,6 +181,7 @@ func (s *apiSvc) Serve() {
|
||||
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
|
||||
postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
|
||||
postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
|
||||
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
|
||||
|
||||
// Debug endpoints, not for general use
|
||||
getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
|
||||
@@ -195,33 +201,37 @@ func (s *apiSvc) Serve() {
|
||||
assets: auto.Assets(),
|
||||
})
|
||||
|
||||
guiCfg := s.cfg.GUI()
|
||||
if guiAPIKey != "" {
|
||||
// Override from the environment
|
||||
guiCfg.APIKey = guiAPIKey
|
||||
}
|
||||
|
||||
// Wrap everything in CSRF protection. The /rest prefix should be
|
||||
// protected, other requests will grant cookies.
|
||||
handler := csrfMiddleware(s.id.String()[:5], "/rest", s.cfg.APIKey, mux)
|
||||
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey, mux)
|
||||
|
||||
// Add our version and ID as a header to responses
|
||||
handler = withDetailsMiddleware(s.id, handler)
|
||||
|
||||
// Wrap everything in basic auth, if user/password is set.
|
||||
if len(s.cfg.User) > 0 && len(s.cfg.Password) > 0 {
|
||||
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], s.cfg, handler)
|
||||
if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
|
||||
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
|
||||
}
|
||||
|
||||
// Redirect to HTTPS if we are supposed to
|
||||
if s.cfg.UseTLS {
|
||||
if guiCfg.UseTLS {
|
||||
handler = redirectToHTTPSMiddleware(handler)
|
||||
}
|
||||
|
||||
if debugHTTP {
|
||||
handler = debugMiddleware(handler)
|
||||
}
|
||||
handler = debugMiddleware(handler)
|
||||
|
||||
srv := http.Server{
|
||||
Handler: handler,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
s.fss = newFolderSummarySvc(s.model)
|
||||
s.fss = newFolderSummarySvc(s.cfg, s.model)
|
||||
defer s.fss.Stop()
|
||||
s.fss.ServeBackground()
|
||||
|
||||
@@ -273,7 +283,6 @@ func (s *apiSvc) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// method.
|
||||
return false
|
||||
}
|
||||
s.cfg = to.GUI
|
||||
|
||||
close(s.stop)
|
||||
|
||||
@@ -369,6 +378,36 @@ func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiSvc) getSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
names := l.Facilities()
|
||||
enabled := l.FacilityDebugging()
|
||||
sort.Strings(enabled)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"facilities": names,
|
||||
"enabled": enabled,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiSvc) postSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
q := r.URL.Query()
|
||||
for _, f := range strings.Split(q.Get("enable"), ",") {
|
||||
if f == "" {
|
||||
continue
|
||||
}
|
||||
l.SetDebug(f, true)
|
||||
l.Infof("Enabled debug data for %q", f)
|
||||
}
|
||||
for _, f := range strings.Split(q.Get("disable"), ",") {
|
||||
if f == "" {
|
||||
continue
|
||||
}
|
||||
l.SetDebug(f, false)
|
||||
l.Infof("Disabled debug data for %q", f)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiSvc) getDBBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
@@ -409,12 +448,12 @@ func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBStatus(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
res := folderSummary(s.model, folder)
|
||||
res := folderSummary(s.cfg, s.model, folder)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func folderSummary(m *model.Model, folder string) map[string]interface{} {
|
||||
func folderSummary(cfg *config.Wrapper, m *model.Model, folder string) map[string]interface{} {
|
||||
var res = make(map[string]interface{})
|
||||
|
||||
res["invalid"] = cfg.Folders()[folder].Invalid
|
||||
@@ -524,7 +563,7 @@ func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *apiSvc) getSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(cfg.Raw())
|
||||
json.NewEncoder(w).Encode(s.cfg.Raw())
|
||||
}
|
||||
|
||||
func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -539,7 +578,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if to.GUI.Password != cfg.GUI().Password {
|
||||
if to.GUI.Password != s.cfg.GUI().Password {
|
||||
if to.GUI.Password != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
|
||||
if err != nil {
|
||||
@@ -554,7 +593,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Fixup usage reporting settings
|
||||
|
||||
if curAcc := cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
|
||||
if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
|
||||
// UR was enabled
|
||||
to.Options.URAccepted = usageReportVersion
|
||||
to.Options.URUniqueID = randomString(8)
|
||||
@@ -566,9 +605,9 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Activate and save
|
||||
|
||||
resp := cfg.Replace(to)
|
||||
resp := s.cfg.Replace(to)
|
||||
configInSync = !resp.RequiresRestart
|
||||
cfg.Save()
|
||||
s.cfg.Save()
|
||||
}
|
||||
|
||||
func (s *apiSvc) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -586,7 +625,7 @@ func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
|
||||
folder := qs.Get("folder")
|
||||
|
||||
if len(folder) > 0 {
|
||||
if _, ok := cfg.Folders()[folder]; !ok {
|
||||
if _, ok := s.cfg.Folders()[folder]; !ok {
|
||||
http.Error(w, "Invalid folder ID", 500)
|
||||
return
|
||||
}
|
||||
@@ -594,7 +633,7 @@ func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(folder) == 0 {
|
||||
// Reset all folders.
|
||||
for folder := range cfg.Folders() {
|
||||
for folder := range s.cfg.Folders() {
|
||||
s.model.ResetFolder(folder)
|
||||
}
|
||||
s.flushResponse(`{"ok": "resetting database"}`, w)
|
||||
@@ -632,7 +671,7 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
res["alloc"] = m.Alloc
|
||||
res["sys"] = m.Sys - m.HeapReleased
|
||||
res["tilde"] = tilde
|
||||
if cfg.Options().LocalAnnEnabled || cfg.Options().GlobalAnnEnabled {
|
||||
if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
|
||||
res["discoveryEnabled"] = true
|
||||
discoErrors := make(map[string]string)
|
||||
discoMethods := 0
|
||||
@@ -674,30 +713,41 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *apiSvc) getSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
guiErrorsMut.Lock()
|
||||
json.NewEncoder(w).Encode(map[string][]guiError{"errors": guiErrors})
|
||||
guiErrorsMut.Unlock()
|
||||
json.NewEncoder(w).Encode(map[string][]logger.Line{
|
||||
"errors": s.guiErrors.Since(time.Time{}),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiSvc) postSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
bs, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
s.showGuiError(0, string(bs))
|
||||
l.Warnln(string(bs))
|
||||
}
|
||||
|
||||
func (s *apiSvc) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
|
||||
guiErrorsMut.Lock()
|
||||
guiErrors = []guiError{}
|
||||
guiErrorsMut.Unlock()
|
||||
s.guiErrors.Clear()
|
||||
}
|
||||
|
||||
func (s *apiSvc) showGuiError(l logger.LogLevel, err string) {
|
||||
guiErrorsMut.Lock()
|
||||
guiErrors = append(guiErrors, guiError{time.Now(), err})
|
||||
if len(guiErrors) > 5 {
|
||||
guiErrors = guiErrors[len(guiErrors)-5:]
|
||||
func (s *apiSvc) getSystemLog(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
since, err := time.Parse(time.RFC3339, q.Get("since"))
|
||||
l.Debugln(err)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
json.NewEncoder(w).Encode(map[string][]logger.Line{
|
||||
"messages": s.systemLog.Since(since),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiSvc) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
since, err := time.Parse(time.RFC3339, q.Get("since"))
|
||||
l.Debugln(err)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
for _, line := range s.systemLog.Since(since) {
|
||||
fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
|
||||
}
|
||||
guiErrorsMut.Unlock()
|
||||
}
|
||||
|
||||
func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -718,7 +768,7 @@ func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *apiSvc) getReport(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(reportData(s.model))
|
||||
json.NewEncoder(w).Encode(reportData(s.cfg, s.model))
|
||||
}
|
||||
|
||||
func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -787,7 +837,7 @@ func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
|
||||
return
|
||||
}
|
||||
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
|
||||
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
@@ -830,7 +880,7 @@ func (s *apiSvc) getLang(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
|
||||
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
|
||||
if err != nil {
|
||||
l.Warnln("getting latest release:", err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
@@ -928,7 +978,7 @@ func (s *apiSvc) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
tot := map[string]float64{}
|
||||
count := map[string]float64{}
|
||||
|
||||
for _, folder := range cfg.Folders() {
|
||||
for _, folder := range s.cfg.Folders() {
|
||||
for _, device := range folder.DeviceIDs() {
|
||||
deviceStr := device.String()
|
||||
if s.model.ConnectedTo(device) {
|
||||
|
||||
@@ -25,7 +25,6 @@ var (
|
||||
)
|
||||
|
||||
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
|
||||
next.ServeHTTP(w, r)
|
||||
@@ -43,9 +42,7 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
|
||||
}
|
||||
}
|
||||
|
||||
if debugHTTP {
|
||||
l.Debugln("Sessionless HTTP request with authentication; this is expensive.")
|
||||
}
|
||||
httpl.Debugln("Sessionless HTTP request with authentication; this is expensive.")
|
||||
|
||||
error := func() {
|
||||
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
|
||||
|
||||
+176
-182
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -21,19 +22,20 @@ import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/connections"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/discover"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/relay"
|
||||
"github.com/syncthing/syncthing/lib/symlinks"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -43,7 +45,6 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/thejerf/suture"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -72,9 +73,17 @@ const (
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
tlsRSABits = 3072
|
||||
pingEventInterval = time.Minute
|
||||
maxSystemErrors = 5
|
||||
initialSystemLog = 10
|
||||
maxSystemLog = 250
|
||||
)
|
||||
|
||||
var l = logger.DefaultLogger
|
||||
// The discovery results are sorted by their source priority.
|
||||
const (
|
||||
ipv6LocalDiscoveryPriority = iota
|
||||
ipv4LocalDiscoveryPriority
|
||||
globalDiscoveryPriority
|
||||
)
|
||||
|
||||
func init() {
|
||||
if Version != "unknown-dev" {
|
||||
@@ -107,15 +116,12 @@ func init() {
|
||||
}
|
||||
|
||||
var (
|
||||
cfg *config.Wrapper
|
||||
myID protocol.DeviceID
|
||||
confDir string
|
||||
logFlags = log.Ltime
|
||||
writeRateLimit *ratelimit.Bucket
|
||||
readRateLimit *ratelimit.Bucket
|
||||
stop = make(chan int)
|
||||
cert tls.Certificate
|
||||
lans []*net.IPNet
|
||||
myID protocol.DeviceID
|
||||
confDir string
|
||||
logFlags = log.Ltime
|
||||
stop = make(chan int)
|
||||
cert tls.Certificate
|
||||
lans []*net.IPNet
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -145,25 +151,11 @@ Development Settings
|
||||
The following environment variables modify syncthing's behavior in ways that
|
||||
are mostly useful for developers. Use with care.
|
||||
|
||||
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.
|
||||
STGUIASSETS Directory to load GUI assets from. Overrides compiled in
|
||||
assets.
|
||||
|
||||
STTRACE A comma separated string of facilities to trace. The valid
|
||||
facility strings are:
|
||||
|
||||
- "beacon" (the beacon package)
|
||||
- "discover" (the discover package)
|
||||
- "events" (the events package)
|
||||
- "files" (the files package)
|
||||
- "http" (the main package; HTTP requests)
|
||||
- "locks" (the sync package; trace long held locks)
|
||||
- "net" (the main package; connections & network messages)
|
||||
- "model" (the model package)
|
||||
- "scanner" (the scanner package)
|
||||
- "stats" (the stats package)
|
||||
- "suture" (the suture package; service management)
|
||||
- "upnp" (the upnp package)
|
||||
- "xdr" (the xdr package)
|
||||
- "all" (all of the above)
|
||||
facility strings listed below.
|
||||
|
||||
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
|
||||
profiler with HTTP access.
|
||||
@@ -186,33 +178,40 @@ are mostly useful for developers. Use with care.
|
||||
|
||||
GOGC Percentage of heap growth at which to trigger GC. Default is
|
||||
100. Lower numbers keep peak memory usage down, at the price
|
||||
of CPU usage (ie. performance).`
|
||||
of CPU usage (ie. performance).
|
||||
|
||||
|
||||
Debugging Facilities
|
||||
--------------------
|
||||
|
||||
The following are valid values for the STTRACE variable:
|
||||
|
||||
%s`
|
||||
)
|
||||
|
||||
// Command line and environment options
|
||||
var (
|
||||
reset bool
|
||||
showVersion bool
|
||||
doUpgrade bool
|
||||
doUpgradeCheck bool
|
||||
upgradeTo string
|
||||
noBrowser bool
|
||||
noConsole bool
|
||||
generateDir string
|
||||
logFile string
|
||||
auditEnabled bool
|
||||
verbose bool
|
||||
paused bool
|
||||
noRestart = os.Getenv("STNORESTART") != ""
|
||||
noUpgrade = os.Getenv("STNOUPGRADE") != ""
|
||||
guiAddress = os.Getenv("STGUIADDRESS") // legacy
|
||||
guiAuthentication = os.Getenv("STGUIAUTH") // legacy
|
||||
guiAPIKey = os.Getenv("STGUIAPIKEY") // legacy
|
||||
profiler = os.Getenv("STPROFILER")
|
||||
guiAssets = os.Getenv("STGUIASSETS")
|
||||
cpuProfile = os.Getenv("STCPUPROFILE") != ""
|
||||
stRestarting = os.Getenv("STRESTART") != ""
|
||||
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
|
||||
reset bool
|
||||
showVersion bool
|
||||
doUpgrade bool
|
||||
doUpgradeCheck bool
|
||||
upgradeTo string
|
||||
noBrowser bool
|
||||
noConsole bool
|
||||
generateDir string
|
||||
logFile string
|
||||
auditEnabled bool
|
||||
verbose bool
|
||||
paused bool
|
||||
noRestart = os.Getenv("STNORESTART") != ""
|
||||
noUpgrade = os.Getenv("STNOUPGRADE") != ""
|
||||
guiAddress = os.Getenv("STGUIADDRESS") // legacy
|
||||
guiAPIKey = os.Getenv("STGUIAPIKEY") // legacy
|
||||
profiler = os.Getenv("STPROFILER")
|
||||
guiAssets = os.Getenv("STGUIASSETS")
|
||||
cpuProfile = os.Getenv("STCPUPROFILE") != ""
|
||||
stRestarting = os.Getenv("STRESTART") != ""
|
||||
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -229,7 +228,6 @@ func main() {
|
||||
|
||||
flag.StringVar(&generateDir, "generate", "", "Generate key and config in specified dir, then exit")
|
||||
flag.StringVar(&guiAddress, "gui-address", guiAddress, "Override GUI address")
|
||||
flag.StringVar(&guiAuthentication, "gui-authentication", guiAuthentication, "Override GUI authentication; username:password")
|
||||
flag.StringVar(&guiAPIKey, "gui-apikey", guiAPIKey, "Override GUI API key")
|
||||
flag.StringVar(&confDir, "home", "", "Set configuration directory")
|
||||
flag.IntVar(&logFlags, "logflags", logFlags, "Select information in log line prefix")
|
||||
@@ -244,7 +242,8 @@ func main() {
|
||||
flag.BoolVar(&verbose, "verbose", false, "Print verbose log output")
|
||||
flag.BoolVar(&paused, "paused", false, "Start with all devices paused")
|
||||
|
||||
flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, baseDirs["config"]))
|
||||
longUsage := fmt.Sprintf(extraUsage, baseDirs["config"], debugFacilities())
|
||||
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
|
||||
flag.Parse()
|
||||
|
||||
if noConsole {
|
||||
@@ -345,7 +344,11 @@ func main() {
|
||||
}
|
||||
|
||||
if doUpgrade || doUpgradeCheck {
|
||||
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
|
||||
releasesURL := "https://api.github.com/repos/syncthing/syncthing/releases?per_page=30"
|
||||
if cfg, _, err := loadConfig(locations[locConfigFile]); err == nil {
|
||||
releasesURL = cfg.Options().ReleasesURL
|
||||
}
|
||||
rel, err := upgrade.LatestRelease(releasesURL, Version)
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err) // exits 1
|
||||
}
|
||||
@@ -392,6 +395,28 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func debugFacilities() string {
|
||||
facilities := l.Facilities()
|
||||
|
||||
// Get a sorted list of names
|
||||
var names []string
|
||||
maxLen := 0
|
||||
for name := range facilities {
|
||||
names = append(names, name)
|
||||
if len(name) > maxLen {
|
||||
maxLen = len(name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
// Format the choices
|
||||
b := new(bytes.Buffer)
|
||||
for _, name := range names {
|
||||
fmt.Fprintf(b, " %-*s - %s\n", maxLen, name, facilities[name])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func upgradeViaRest() error {
|
||||
cfg, err := config.Load(locations[locConfigFile], protocol.LocalDeviceID)
|
||||
if err != nil {
|
||||
@@ -434,9 +459,7 @@ func syncthingMain() {
|
||||
// We want any logging it does to go through our log system.
|
||||
mainSvc := suture.New("main", suture.Spec{
|
||||
Log: func(line string) {
|
||||
if debugSuture {
|
||||
l.Debugln(line)
|
||||
}
|
||||
l.Debugln(line)
|
||||
},
|
||||
})
|
||||
mainSvc.ServeBackground()
|
||||
@@ -453,6 +476,9 @@ func syncthingMain() {
|
||||
mainSvc.Add(newVerboseSvc())
|
||||
}
|
||||
|
||||
errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0)
|
||||
systemLog := logger.NewRecorder(l, logger.LevelDebug, maxSystemLog, initialSystemLog)
|
||||
|
||||
// Event subscription for the API; must start early to catch the early events.
|
||||
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000)
|
||||
|
||||
@@ -496,33 +522,21 @@ func syncthingMain() {
|
||||
|
||||
cfgFile := locations[locConfigFile]
|
||||
|
||||
var myName string
|
||||
|
||||
// Load the configuration file, if it exists.
|
||||
// If it does not, create a template.
|
||||
|
||||
if info, err := os.Stat(cfgFile); err == nil {
|
||||
if !info.Mode().IsRegular() {
|
||||
l.Fatalln("Config file is not a file?")
|
||||
}
|
||||
cfg, err = config.Load(cfgFile, myID)
|
||||
if err == nil {
|
||||
myCfg := cfg.Devices()[myID]
|
||||
if myCfg.Name == "" {
|
||||
myName, _ = os.Hostname()
|
||||
} else {
|
||||
myName = myCfg.Name
|
||||
}
|
||||
cfg, myName, err := loadConfig(cfgFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
l.Infoln("No config file; starting with empty defaults")
|
||||
myName, _ = os.Hostname()
|
||||
newCfg := defaultConfig(myName)
|
||||
cfg = config.Wrap(cfgFile, newCfg)
|
||||
cfg.Save()
|
||||
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
|
||||
} else {
|
||||
l.Fatalln("Configuration:", err)
|
||||
l.Fatalln("Loading config:", err)
|
||||
}
|
||||
} else {
|
||||
l.Infoln("No config file; starting with empty defaults")
|
||||
myName, _ = os.Hostname()
|
||||
newCfg := defaultConfig(myName)
|
||||
cfg = config.Wrap(cfgFile, newCfg)
|
||||
cfg.Save()
|
||||
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
|
||||
}
|
||||
|
||||
if cfg.Raw().OriginalVersion != config.CurrentVersion {
|
||||
@@ -577,13 +591,6 @@ func syncthingMain() {
|
||||
symlinks.Supported = false
|
||||
}
|
||||
|
||||
if opts.MaxSendKbps > 0 {
|
||||
writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxSendKbps), int64(5*1000*opts.MaxSendKbps))
|
||||
}
|
||||
if opts.MaxRecvKbps > 0 {
|
||||
readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
|
||||
}
|
||||
|
||||
if (opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0) && !opts.LimitBandwidthInLan {
|
||||
lans, _ = osutil.GetLans()
|
||||
networks := make([]string, 0, len(lans))
|
||||
@@ -602,9 +609,10 @@ func syncthingMain() {
|
||||
}
|
||||
|
||||
dbFile := locations[locDatabase]
|
||||
ldb, err := leveldb.OpenFile(dbFile, dbOpts())
|
||||
dbOpts := dbOpts(cfg)
|
||||
ldb, err := leveldb.OpenFile(dbFile, dbOpts)
|
||||
if leveldbIsCorrupted(err) {
|
||||
ldb, err = leveldb.RecoverFile(dbFile, dbOpts())
|
||||
ldb, err = leveldb.RecoverFile(dbFile, dbOpts)
|
||||
}
|
||||
if leveldbIsCorrupted(err) {
|
||||
// The database is corrupted, and we've tried to recover it but it
|
||||
@@ -614,7 +622,7 @@ func syncthingMain() {
|
||||
if err := resetDB(); err != nil {
|
||||
l.Fatalln("Remove database:", err)
|
||||
}
|
||||
ldb, err = leveldb.OpenFile(dbFile, dbOpts())
|
||||
ldb, err = leveldb.OpenFile(dbFile, dbOpts)
|
||||
}
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
|
||||
@@ -723,7 +731,7 @@ func syncthingMain() {
|
||||
// Each global discovery server gets its results cached for five
|
||||
// minutes, and is not asked again for a minute when it's returned
|
||||
// unsuccessfully.
|
||||
cachedDiscovery.Add(gd, 5*time.Minute, time.Minute)
|
||||
cachedDiscovery.Add(gd, 5*time.Minute, time.Minute, globalDiscoveryPriority)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,24 +741,24 @@ func syncthingMain() {
|
||||
if err != nil {
|
||||
l.Warnln("IPv4 local discovery:", err)
|
||||
} else {
|
||||
cachedDiscovery.Add(bcd, 0, 0)
|
||||
cachedDiscovery.Add(bcd, 0, 0, ipv4LocalDiscoveryPriority)
|
||||
}
|
||||
// v6 multicasts
|
||||
mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, addrList, relaySvc)
|
||||
if err != nil {
|
||||
l.Warnln("IPv6 local discovery:", err)
|
||||
} else {
|
||||
cachedDiscovery.Add(mcd, 0, 0)
|
||||
cachedDiscovery.Add(mcd, 0, 0, ipv6LocalDiscoveryPriority)
|
||||
}
|
||||
}
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc)
|
||||
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc, errors, systemLog)
|
||||
|
||||
// Start connection management
|
||||
|
||||
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg, cachedDiscovery, relaySvc)
|
||||
connectionSvc := connections.NewConnectionSvc(cfg, myID, m, tlsCfg, cachedDiscovery, relaySvc, bepProtocolName, tlsDefaultCommonName, lans)
|
||||
mainSvc.Add(connectionSvc)
|
||||
|
||||
if cpuProfile {
|
||||
@@ -786,7 +794,7 @@ func syncthingMain() {
|
||||
// The usageReportingManager registers itself to listen to configuration
|
||||
// changes, and there's nothing more we need to tell it from the outside.
|
||||
// Hence we don't keep the returned pointer.
|
||||
newUsageReportingManager(m, cfg)
|
||||
newUsageReportingManager(cfg, m)
|
||||
|
||||
if opts.RestartOnWakeup {
|
||||
go standbyMonitor()
|
||||
@@ -796,7 +804,7 @@ func syncthingMain() {
|
||||
if noUpgrade {
|
||||
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
|
||||
} else if IsRelease {
|
||||
go autoUpgrade()
|
||||
go autoUpgrade(cfg)
|
||||
} else {
|
||||
l.Infof("No automatic upgrades; %s is not a release version.", Version)
|
||||
}
|
||||
@@ -822,7 +830,30 @@ func syncthingMain() {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func dbOpts() *opt.Options {
|
||||
func loadConfig(cfgFile string) (*config.Wrapper, string, error) {
|
||||
info, err := os.Stat(cfgFile)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil, "", errors.New("configuration is not a file")
|
||||
}
|
||||
|
||||
cfg, err := config.Load(cfgFile, myID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
myCfg := cfg.Devices()[myID]
|
||||
myName := myCfg.Name
|
||||
if myName == "" {
|
||||
myName, _ = os.Hostname()
|
||||
}
|
||||
|
||||
return cfg, myName, nil
|
||||
}
|
||||
|
||||
func dbOpts(cfg *config.Wrapper) *opt.Options {
|
||||
// Calculate a suitable database block cache capacity.
|
||||
|
||||
// Default is 8 MiB.
|
||||
@@ -873,48 +904,53 @@ func startAuditing(mainSvc *suture.Supervisor) {
|
||||
l.Infoln("Audit log in", auditFile)
|
||||
}
|
||||
|
||||
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc) {
|
||||
opts := cfg.Options()
|
||||
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
|
||||
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) {
|
||||
guiCfg := cfg.GUI()
|
||||
|
||||
if guiCfg.Enabled && guiCfg.Address != "" {
|
||||
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
|
||||
if !guiCfg.Enabled {
|
||||
return
|
||||
}
|
||||
if guiCfg.Address == "" {
|
||||
return
|
||||
}
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
|
||||
if err != nil {
|
||||
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
|
||||
} else {
|
||||
var hostOpen, hostShow string
|
||||
switch {
|
||||
case addr.IP == nil:
|
||||
hostOpen = "localhost"
|
||||
hostShow = "0.0.0.0"
|
||||
case addr.IP.IsUnspecified():
|
||||
hostOpen = "localhost"
|
||||
hostShow = addr.IP.String()
|
||||
default:
|
||||
hostOpen = addr.IP.String()
|
||||
hostShow = hostOpen
|
||||
}
|
||||
|
||||
var proto = "http"
|
||||
if guiCfg.UseTLS {
|
||||
proto = "https"
|
||||
}
|
||||
|
||||
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
|
||||
l.Infoln("Starting web GUI on", urlShow)
|
||||
|
||||
api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog)
|
||||
if err != nil {
|
||||
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
|
||||
} else {
|
||||
var hostOpen, hostShow string
|
||||
switch {
|
||||
case addr.IP == nil:
|
||||
hostOpen = "localhost"
|
||||
hostShow = "0.0.0.0"
|
||||
case addr.IP.IsUnspecified():
|
||||
hostOpen = "localhost"
|
||||
hostShow = addr.IP.String()
|
||||
default:
|
||||
hostOpen = addr.IP.String()
|
||||
hostShow = hostOpen
|
||||
}
|
||||
l.Fatalln("Cannot start GUI:", err)
|
||||
}
|
||||
cfg.Subscribe(api)
|
||||
mainSvc.Add(api)
|
||||
|
||||
var proto = "http"
|
||||
if guiCfg.UseTLS {
|
||||
proto = "https"
|
||||
}
|
||||
|
||||
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
|
||||
l.Infoln("Starting web GUI on", urlShow)
|
||||
api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub, discoverer, relaySvc)
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot start GUI:", err)
|
||||
}
|
||||
cfg.Subscribe(api)
|
||||
mainSvc.Add(api)
|
||||
|
||||
if opts.StartBrowser && !noBrowser && !stRestarting {
|
||||
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
|
||||
// Can potentially block if the utility we are invoking doesn't
|
||||
// fork, and just execs, hence keep it in it's own routine.
|
||||
go openURL(urlOpen)
|
||||
}
|
||||
if cfg.Options().StartBrowser && !noBrowser && !stRestarting {
|
||||
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
|
||||
// Can potentially block if the utility we are invoking doesn't
|
||||
// fork, and just execs, hence keep it in it's own routine.
|
||||
go openURL(urlOpen)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1010,48 +1046,6 @@ func getFreePort(host string, ports ...int) (int, error) {
|
||||
return addr.Port, nil
|
||||
}
|
||||
|
||||
func overrideGUIConfig(cfg config.GUIConfiguration, address, authentication, apikey string) config.GUIConfiguration {
|
||||
if address != "" {
|
||||
cfg.Enabled = true
|
||||
|
||||
if !strings.Contains(address, "//") {
|
||||
// Assume just an IP was given. Don't touch he TLS setting.
|
||||
cfg.Address = address
|
||||
} else {
|
||||
parsed, err := url.Parse(address)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
}
|
||||
cfg.Address = parsed.Host
|
||||
switch parsed.Scheme {
|
||||
case "http":
|
||||
cfg.UseTLS = false
|
||||
case "https":
|
||||
cfg.UseTLS = true
|
||||
default:
|
||||
l.Fatalln("Unknown scheme:", parsed.Scheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if authentication != "" {
|
||||
authenticationParts := strings.SplitN(authentication, ":", 2)
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(authenticationParts[1]), 0)
|
||||
if err != nil {
|
||||
l.Fatalln("Invalid GUI password:", err)
|
||||
}
|
||||
|
||||
cfg.User = authenticationParts[0]
|
||||
cfg.Password = string(hash)
|
||||
}
|
||||
|
||||
if apikey != "" {
|
||||
cfg.APIKey = apikey
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func standbyMonitor() {
|
||||
restartDelay := time.Duration(60 * time.Second)
|
||||
now := time.Now()
|
||||
@@ -1072,7 +1066,7 @@ func standbyMonitor() {
|
||||
}
|
||||
}
|
||||
|
||||
func autoUpgrade() {
|
||||
func autoUpgrade(cfg *config.Wrapper) {
|
||||
timer := time.NewTimer(0)
|
||||
sub := events.Default.Subscribe(events.DeviceConnected)
|
||||
for {
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -9,6 +9,7 @@ package main
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
type folderSummarySvc struct {
|
||||
*suture.Supervisor
|
||||
|
||||
cfg *config.Wrapper
|
||||
model *model.Model
|
||||
stop chan struct{}
|
||||
immediate chan string
|
||||
@@ -33,9 +35,10 @@ type folderSummarySvc struct {
|
||||
lastEventReqMut sync.Mutex
|
||||
}
|
||||
|
||||
func newFolderSummarySvc(m *model.Model) *folderSummarySvc {
|
||||
func newFolderSummarySvc(cfg *config.Wrapper, m *model.Model) *folderSummarySvc {
|
||||
svc := &folderSummarySvc{
|
||||
Supervisor: suture.NewSimple("folderSummarySvc"),
|
||||
cfg: cfg,
|
||||
model: m,
|
||||
stop: make(chan struct{}),
|
||||
immediate: make(chan string),
|
||||
@@ -162,13 +165,13 @@ func (c *folderSummarySvc) foldersToHandle() []string {
|
||||
func (c *folderSummarySvc) sendSummary(folder string) {
|
||||
// The folder summary contains how many bytes, files etc
|
||||
// are in the folder and how in sync we are.
|
||||
data := folderSummary(c.model, folder)
|
||||
data := folderSummary(c.cfg, c.model, folder)
|
||||
events.Default.Log(events.FolderSummary, map[string]interface{}{
|
||||
"folder": folder,
|
||||
"summary": data,
|
||||
})
|
||||
|
||||
for _, devCfg := range cfg.Folders()[folder].Devices {
|
||||
for _, devCfg := range c.cfg.Folders()[folder].Devices {
|
||||
if devCfg.DeviceID.Equals(myID) {
|
||||
// We already know about ourselves.
|
||||
continue
|
||||
|
||||
@@ -98,9 +98,7 @@ func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
|
||||
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
|
||||
events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
|
||||
}
|
||||
if debugNet {
|
||||
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
|
||||
}
|
||||
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
|
||||
return extPort
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
@@ -32,12 +32,14 @@ import (
|
||||
const usageReportVersion = 2
|
||||
|
||||
type usageReportingManager struct {
|
||||
cfg *config.Wrapper
|
||||
model *model.Model
|
||||
sup *suture.Supervisor
|
||||
}
|
||||
|
||||
func newUsageReportingManager(m *model.Model, cfg *config.Wrapper) *usageReportingManager {
|
||||
func newUsageReportingManager(cfg *config.Wrapper, m *model.Model) *usageReportingManager {
|
||||
mgr := &usageReportingManager{
|
||||
cfg: cfg,
|
||||
model: m,
|
||||
}
|
||||
|
||||
@@ -58,9 +60,7 @@ func (m *usageReportingManager) VerifyConfiguration(from, to config.Configuratio
|
||||
func (m *usageReportingManager) CommitConfiguration(from, to config.Configuration) bool {
|
||||
if to.Options.URAccepted >= usageReportVersion && m.sup == nil {
|
||||
// Usage reporting was turned on; lets start it.
|
||||
svc := &usageReportingService{
|
||||
model: m.model,
|
||||
}
|
||||
svc := newUsageReportingService(m.cfg, m.model)
|
||||
m.sup = suture.NewSimple("usageReporting")
|
||||
m.sup.Add(svc)
|
||||
m.sup.ServeBackground()
|
||||
@@ -79,7 +79,7 @@ func (m *usageReportingManager) String() string {
|
||||
|
||||
// reportData returns the data to be sent in a usage report. It's used in
|
||||
// various places, so not part of the usageReportingSvc object.
|
||||
func reportData(m *model.Model) map[string]interface{} {
|
||||
func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
res["urVersion"] = usageReportVersion
|
||||
res["uniqueID"] = cfg.Options().URUniqueID
|
||||
@@ -238,12 +238,21 @@ func stringIn(needle string, haystack []string) bool {
|
||||
}
|
||||
|
||||
type usageReportingService struct {
|
||||
cfg *config.Wrapper
|
||||
model *model.Model
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func newUsageReportingService(cfg *config.Wrapper, model *model.Model) *usageReportingService {
|
||||
return &usageReportingService{
|
||||
cfg: cfg,
|
||||
model: model,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *usageReportingService) sendUsageReport() error {
|
||||
d := reportData(s.model)
|
||||
d := reportData(s.cfg, s.model)
|
||||
var b bytes.Buffer
|
||||
json.NewEncoder(&b).Encode(d)
|
||||
|
||||
@@ -256,12 +265,12 @@ func (s *usageReportingService) sendUsageReport() error {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Options().URPostInsecurely {
|
||||
if s.cfg.Options().URPostInsecurely {
|
||||
transp.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
_, err := client.Post(cfg.Options().URURL, "application/json", &b)
|
||||
_, err := client.Post(s.cfg.Options().URURL, "application/json", &b)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -271,7 +280,7 @@ func (s *usageReportingService) Serve() {
|
||||
l.Infoln("Starting usage reporting")
|
||||
defer l.Infoln("Stopping usage reporting")
|
||||
|
||||
t := time.NewTimer(time.Duration(cfg.Options().URInitialDelayS) * time.Second) // time to initial report at start
|
||||
t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second) // time to initial report at start
|
||||
for {
|
||||
select {
|
||||
case <-s.stop:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Description=Syncthing - Open Source Continuous File Synchronization for %I
|
||||
Documentation=http://docs.syncthing.net/
|
||||
After=network.target
|
||||
Wants=syncthing-inotify@.service
|
||||
|
||||
[Service]
|
||||
User=%i
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Description=Syncthing - Open Source Continuous File Synchronization
|
||||
Documentation=http://docs.syncthing.net/
|
||||
After=network.target
|
||||
Wants=syncthing-inotify.service
|
||||
|
||||
[Service]
|
||||
Environment=STNORESTART=yes
|
||||
|
||||
@@ -21,6 +21,10 @@ ul+h5 {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.panel-progress {
|
||||
background: #3498db;
|
||||
height: 3px;
|
||||
@@ -64,6 +68,7 @@ identicon {
|
||||
|
||||
.popover {
|
||||
max-width: none;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.panel-heading .fa, .modal-header .fa {
|
||||
@@ -249,7 +254,3 @@ ul.three-columns li, ul.two-columns li {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Когато добавяш нов идентификатор на папка помни, че той се използва за свързване на папките на различни устройства. Главни/малки букви са от значение и трябва да са еднакви на всички устройства.",
|
||||
"Yes": "Да",
|
||||
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
|
||||
"days": "дни",
|
||||
"full documentation": "пълна документация",
|
||||
"items": "артикула",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папка \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Quan s'afegeix una nova carpeta recorda que el ID d'aquesta s'utilitza per lligar repositoris entre els dispositius. Es distingeix entre majúscules i minúscules i ha de ser exactament iguals entre tots els dispositius.",
|
||||
"Yes": "Si",
|
||||
"You must keep at least one version.": "Has de mantenir com a mínim una versió.",
|
||||
"days": "days",
|
||||
"full documentation": "documentació sencera",
|
||||
"items": "Elements",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartir la carpeta \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Quant s'afig una nova carpeta, hi ha que tindre en compte que l'ID de la carpeta s'utilitza per a juntar les carpetes entre dispositius. Són sensibles a les majúscules i deuen coincidir exactament entre tots els dispositius.",
|
||||
"Yes": "Sí",
|
||||
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
|
||||
"days": "days",
|
||||
"full documentation": "Documentació completa",
|
||||
"items": "Elements",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\"."
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
"Source Code": "Zdrojový kód",
|
||||
"Staggered File Versioning": "Postupné verzování souborů",
|
||||
"Start Browser": "Otevřít prohlížeč",
|
||||
"Statistics": "Statistika",
|
||||
"Statistics": "Statistiky",
|
||||
"Stopped": "Pozastaveno",
|
||||
"Support": "Podpora",
|
||||
"Sync Protocol Listen Addresses": "Adresa naslouchání synchronizačního protokolu",
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
|
||||
"Yes": "Ano",
|
||||
"You must keep at least one version.": "Je třeba ponechat alespoň jednu verzi.",
|
||||
"days": "dní",
|
||||
"full documentation": "plná dokumentace",
|
||||
"items": "položky",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce sdílet adresář \"{{folder}}\"."
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"Bugs": "Fehler",
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Changelog": "Änderungsprotokoll",
|
||||
"Clean out after": "Aufräumen nach",
|
||||
"Clean out after": "Löschen nach",
|
||||
"Close": "Schließen",
|
||||
"Command": "Kommando",
|
||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
|
||||
@@ -35,7 +35,7 @@
|
||||
"Delete": "Löschen",
|
||||
"Deleted": "Gelöscht",
|
||||
"Device ID": "Geräte ID",
|
||||
"Device Identification": "Gerät Identifikation",
|
||||
"Device Identification": "Geräte Identifikation",
|
||||
"Device Name": "Gerätename",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Gerät {{device}} ({{address}}) möchte sich verbinden. Gerät hinzufügen?",
|
||||
"Devices": "Geräte",
|
||||
@@ -47,9 +47,9 @@
|
||||
"Edit": "Bearbeiten",
|
||||
"Edit Device": "Gerät bearbeiten",
|
||||
"Edit Folder": "Verzeichnis bearbeiten",
|
||||
"Editing": "Bearbeiten",
|
||||
"Editing": "Bearbeitet",
|
||||
"Enable UPnP": "UPnP aktivieren",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Geben Sie durch Komma getrennte Adressen an (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\", um die Adresse automatisch zu ermitteln.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Kommagetrennte Adressen (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\" eingeben, um die Adresse automatisch zu ermitteln.",
|
||||
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
|
||||
"Error": "Fehler",
|
||||
"External File Versioning": "Externe Dateiversionierung",
|
||||
@@ -57,9 +57,9 @@
|
||||
"File Pull Order": "Dateiübertragungsreihenfolge",
|
||||
"File Versioning": "Dateiversionierung",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Wenn Dateien von Syncthing ersetzt oder gelöscht werden sollen, werden sie vorher in den .stversions Ordner verschoben.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, datiert in das Verzeichnis .stversions verschoben.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind auf diesem Gerät schreibgeschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
|
||||
"Folder": "Verzeichnis",
|
||||
"Folder ID": "Verzeichnis ID",
|
||||
"Folder Master": "Master Verzeichnis - schreibgeschützt",
|
||||
@@ -71,7 +71,7 @@
|
||||
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
|
||||
"Generate": "Generieren",
|
||||
"Global Discovery": "Globale Gerätesuche",
|
||||
"Global Discovery Server": "Globale(r) Indexserver",
|
||||
"Global Discovery Server": "Globaler Gerätesuchserver",
|
||||
"Global State": "Globaler Status",
|
||||
"Help": "Hilfe",
|
||||
"Home page": "Homepage",
|
||||
@@ -111,18 +111,18 @@
|
||||
"Out of Sync Items": "Nicht synchronisierte Objekte",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limit Datenrate (ausgehend) (KB/s)",
|
||||
"Override Changes": "Änderungen überschreiben",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem die Versionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Gerät. Ordner werden erzeugt, wenn sie nicht existieren. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem alte Dateiversionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
|
||||
"Pause": "Pause",
|
||||
"Paused": "Pausiert",
|
||||
"Please consult the release notes before performing a major upgrade.": "BItte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
|
||||
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
|
||||
"Please wait": "Bitte warten",
|
||||
"Preview": "Vorschau",
|
||||
"Preview Usage Report": "Vorschau des Nutzungsberichts",
|
||||
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Suchstrukturen",
|
||||
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
|
||||
"RAM Utilization": "RAM Auslastung",
|
||||
"Random": "Zufall",
|
||||
"Relayed via": "Weiterleitung über",
|
||||
"Relayed via": "Weitergeleitet über",
|
||||
"Relays": "Weiterleitungen",
|
||||
"Release Notes": "Veröffentlichungsnotizen",
|
||||
"Remove": "Entfernen",
|
||||
@@ -135,7 +135,7 @@
|
||||
"Resume": "Fortfahren",
|
||||
"Reused": "Erneut benutzt",
|
||||
"Save": "Speichern",
|
||||
"Scanning": "Wird gescannt",
|
||||
"Scanning": "Scannen",
|
||||
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du dieses Verzeichnis teilen willst.",
|
||||
"Select the folders to share with this device.": "Wähle die Verzeichnisse aus, die du mit diesem Gerät teilen möchtest",
|
||||
"Settings": "Einstellungen",
|
||||
@@ -147,8 +147,8 @@
|
||||
"Shared With": "Geteilt mit",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Kurze ID für das Verzeichnis. Muss auf allen Verbunds-Geräten gleich sein.",
|
||||
"Show ID": "ID anzeigen",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients weitergegeben.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird auf den Namen aktualisiert, den das Gerät angibt.",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients im Cluster weitergegeben.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt.Wird auf den Namen aktualisiert, den das Gerät angibt, wenn nichts eingetragen wird.",
|
||||
"Shutdown": "Herunterfahren",
|
||||
"Shutdown Complete": "Vollständig Heruntergefahren",
|
||||
"Simple File Versioning": "Einfache Dateiversionierung",
|
||||
@@ -163,13 +163,13 @@
|
||||
"Sync Protocol Listen Addresses": "Adresse(n) für das Synchronisierungsprotokoll",
|
||||
"Syncing": "Synchronisiere",
|
||||
"Syncthing has been shut down.": "Syncthing wurde heruntergefahren.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile davon:",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile von:",
|
||||
"Syncthing is restarting.": "Syncthing wird neu gestartet",
|
||||
"Syncthing is upgrading.": "Syncthing wird aktualisiert",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Es scheint als ob Syncthing ein Problem mit der Verarbeitung ihrer Eingabe hat. Bitte laden sie die Seite neu oder führen sie einen Neustart von Syncthing durch, falls das Problem weiterhin besteht.",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing scheint ein Problem mit der Verarbeitung Deiner Eingabe zu haben. Bitte lade die Seite neu oder führe einen Neustart durch, falls das Problem weiterhin besteht.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Die gesammelten Statistiken sind öffentlich verfügbar unter {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu übernehmen.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber noch nicht aktiviert. Syncthing muss neugestartet werden, um die neue Konfiguration zu übernehmen.",
|
||||
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
|
||||
"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.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird verwendet, um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
|
||||
@@ -179,18 +179,18 @@
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Bezeichnung haben (64 Zeichen oder weniger). Sie darf nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
|
||||
"The folder ID must be unique.": "Die Verzeichnis ID darf nur einmal existieren.",
|
||||
"The folder path cannot be blank.": "Der Verzeichnispfad darf nicht leer sein.",
|
||||
"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.": "Es wird in folgenden Abständen versioniert: in der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag, danach wird bis zum Höchstalter eine Version pro Woche beibehalten.",
|
||||
"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.": "Es wird in folgenden Abständen versioniert: In der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag. Danach wird bis zum angegebenen Höchstalter eine Version pro Woche behalten.",
|
||||
"The following items could not be synchronized.": "Die folgenden Objekte konnten nicht synchronisiert werden.",
|
||||
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen) (0 um alte Versionen für immer zu behalten).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Der Prozentsatz des minimal freien Festplattenspeichers muss eine nichtnegative Zahl zwischen (inklusive) 0 und 100 sein. ",
|
||||
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
|
||||
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Dauer in Tagen für welche die Dateien aufgehoben werden sollen. 0 bedeutet für immer.",
|
||||
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
|
||||
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
|
||||
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei behalten werden sollen.",
|
||||
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
|
||||
"The path cannot be blank.": "Der Pfad darf nicht leer sein.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Das Daterate-Limit muss eine nicht negative Anzahl sein (0: kein Limit)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl von Sekunden sein.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Das Daterate-Limit muss eine nicht negative Anzahl sein (0 = kein Limit).",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl (in Sekunden) sein.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Sie werden automatisch heruntergeladen und werden synchronisiert, wenn der Fehler behoben wurde.",
|
||||
"This is a major version upgrade.": "Dies ist eine neue Hauptversion.",
|
||||
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
|
||||
@@ -207,11 +207,12 @@
|
||||
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Versionierungspfad",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Versionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die Höchstzahl der Dateien je Zeitabschnitt überschritten wird.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf der Gegenseite hinzugefügt werden muss.",
|
||||
"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.": "Beim Hinzufügen eines neuen Verzeichnisses beachte, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Dateiversionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die angegebene Höchstzahl an Dateien erreicht ist.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf den anderen Geräten hinzugefügt werden muss.",
|
||||
"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.": "Beachte bitte beim Hinzufügen eines neuen Verzeichnisses, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
|
||||
"days": "Tage",
|
||||
"full documentation": "Komplette Dokumentation",
|
||||
"items": "Objekte",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
|
||||
"Yes": "Ναι",
|
||||
"You must keep at least one version.": "Πρέπει να τηρήσεις τουλάχιστον μια έκδοση.",
|
||||
"days": "days",
|
||||
"full documentation": "πλήρης τεκμηρίωση",
|
||||
"items": "εγγραφές",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}»."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "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.",
|
||||
"Yes": "Yes",
|
||||
"You must keep at least one version.": "You must keep at least one version.",
|
||||
"days": "days",
|
||||
"full documentation": "full documentation",
|
||||
"items": "items",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Cuando añada una nueva carpeta, tenga en cuenta que su ID se usa para unir carpetas entre dispositivos. Son sensibles a las mayúsculas y deben coincidir exactamente entre todos los dispositivos.",
|
||||
"Yes": "Si",
|
||||
"You must keep at least one version.": "Debes mantener al menos una versión.",
|
||||
"days": "days",
|
||||
"full documentation": "Documentación completa",
|
||||
"items": "Elementos",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Al agregar un nuevo repositorio, tenga en cuenta que la ID del repositorio se utiliza para conectar los repositorios entre dispositivos. Se distingue entre mayúsculas y minúsculas y debe ser exactamente igual en todos los dispositivos.",
|
||||
"Yes": "Sí",
|
||||
"You must keep at least one version.": "Debe mantener al menos una versión",
|
||||
"days": "days",
|
||||
"full documentation": "documentación completa",
|
||||
"items": "ítems",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Lisättäessä uutta kansiota, muista että kansion ID:tä käytetään solmimaan kansiot yhteen laitteiden välillä. Ne ovat riippuvaisia kirjankoosta ja niiden tulee täsmätä kaikkien laitteiden välillä.",
|
||||
"Yes": "Kyllä",
|
||||
"You must keep at least one version.": "Sinun tulee säilyttää ainakin yksi versio.",
|
||||
"days": "days",
|
||||
"full documentation": "täysi dokumentaatio",
|
||||
"items": "kohteet",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que son ID est utilisé pour lier les répertoires à travers les appareils. Les ID sont sensibles à la casse et doivent être identiques à travers tous les nœuds.",
|
||||
"Yes": "Oui",
|
||||
"You must keep at least one version.": "Vous devez garder au minimum une version.",
|
||||
"days": "Jours",
|
||||
"full documentation": "documentation complète",
|
||||
"items": "éléments",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que son ID est utilisé pour lier les répertoires à travers les appareils. Les ID sont sensibles à la casse et doivent être identiques à travers tous les nœuds.",
|
||||
"Yes": "Oui",
|
||||
"You must keep at least one version.": "Vous devez garder au minimum une version.",
|
||||
"days": "Jours",
|
||||
"full documentation": "documentation complète",
|
||||
"items": "éléments",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
"A new major version may not be compatible with previous versions.": "Az új főverzió nem kompatibilis az előző főverzióval.",
|
||||
"API Key": "API kulcs",
|
||||
"About": "Névjegy",
|
||||
"Actions": "Tevékenységek",
|
||||
"Actions": "Műveletek",
|
||||
"Add": "Hozzáadás",
|
||||
"Add Device": "Eszköz hozzáadása",
|
||||
"Add Folder": "Mappa hozzáadása",
|
||||
"Add new folder?": " ",
|
||||
"Add new folder?": " Új mappa hozzáadás?",
|
||||
"Address": "Cím",
|
||||
"Addresses": "Címek",
|
||||
"Advanced": "Speciális",
|
||||
"Advanced Configuration": "Speciális beállítások",
|
||||
"Advanced": "Haladó",
|
||||
"Advanced Configuration": "Haladó beállítások",
|
||||
"All Data": "Minden adat",
|
||||
"Allow Anonymous Usage Reporting?": "Engedélyezed a névtelen felhasználási adatok küldését?",
|
||||
"Alphabetic": "ABC rendben",
|
||||
@@ -37,7 +37,7 @@
|
||||
"Device ID": "Eszköz azonosító",
|
||||
"Device Identification": "Eszköz azonosító",
|
||||
"Device Name": "Eszköz neve",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne.",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne. Hozzáadod az új eszközt?",
|
||||
"Devices": "Eszközök",
|
||||
"Disconnected": "Kapcsolat bontva",
|
||||
"Documentation": "Dokumentáció",
|
||||
@@ -56,7 +56,7 @@
|
||||
"Failed Items": "Hibás elemek",
|
||||
"File Pull Order": "Fájl küldési sorrend",
|
||||
"File Versioning": "Fájl verziózás",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszereken használatakor.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszerek használatakor.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing áthelyezi vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing áthelyezi vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve, időbélyegzővel ellátva.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "A fájlok védve vannak a más eszközökön történt változásokkal szemben, de az ezen az eszközön történt változások érvényesek lesznek a többire.",
|
||||
@@ -65,7 +65,7 @@
|
||||
"Folder Master": "Központi mappa",
|
||||
"Folder Path": "Mappa elérési útja",
|
||||
"Folders": "Mappák",
|
||||
"GUI": "Felület",
|
||||
"GUI": "Grafikus felület",
|
||||
"GUI Authentication Password": "Grafikus felület jelszava",
|
||||
"GUI Authentication User": "Grafikus felület felhasználó neve ",
|
||||
"GUI Listen Addresses": "Grafikus felület címe",
|
||||
@@ -75,8 +75,8 @@
|
||||
"Global State": "Globális állapot",
|
||||
"Help": "Segítség",
|
||||
"Home page": "Főoldal",
|
||||
"Ignore": "Visszautasítás",
|
||||
"Ignore Patterns": "Figyelmen kívül hagyás",
|
||||
"Ignore": "Figyelmen kívül hagyás",
|
||||
"Ignore Patterns": "Minták figyelmen kívül hagyása",
|
||||
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
|
||||
"Incoming Rate Limit (KiB/s)": "Bejövő sebesség korlát (KIB/mp)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Helytelen konfiguráció esetén károsodhat a mappák tartalma és működésképtelenné válhat a Syncthing.",
|
||||
@@ -107,23 +107,23 @@
|
||||
"Off": "Kikapcsolva",
|
||||
"Oldest First": "Régebbi először",
|
||||
"Options": "Opciók",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"Out of Sync": "Nincs szinkronban",
|
||||
"Out of Sync Items": "Nem szinkronizált elemek",
|
||||
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
|
||||
"Override Changes": "Változtatások felülbírálása",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "A mappa elérési útja az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Elérési út ahol a verziók tárolásra kerülnek (szabadon hagyva az alapértelmezett .stversions mappa lesz használva)",
|
||||
"Pause": "Pause",
|
||||
"Paused": "Paused",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Pause": "Szünet",
|
||||
"Paused": "Szünetel",
|
||||
"Please consult the release notes before performing a major upgrade.": "Nagyobb frissítés előtt ellenőrizni kell a kiadási megjegyzéseket.",
|
||||
"Please wait": "Kérlek, várj",
|
||||
"Preview": "Előnézet",
|
||||
"Preview Usage Report": "Felhasználási adatok átnézése",
|
||||
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
|
||||
"RAM Utilization": "Memória használat",
|
||||
"Random": "Véletlenszerű",
|
||||
"Relayed via": "Relayed via",
|
||||
"Relays": "Relays",
|
||||
"Relayed via": "Közvetítve",
|
||||
"Relays": "Közvetítések",
|
||||
"Release Notes": "Kiadási megjegyzések",
|
||||
"Remove": "Eltávolítás",
|
||||
"Rescan": "Átnézés",
|
||||
@@ -132,7 +132,7 @@
|
||||
"Restart": "Újraindítás",
|
||||
"Restart Needed": "Újraindítás szükséges",
|
||||
"Restarting": "Újraindulás",
|
||||
"Resume": "Resume",
|
||||
"Resume": "Folytatás",
|
||||
"Reused": "Újrafelhasználva",
|
||||
"Save": "Mentés",
|
||||
"Scanning": "Átnézés",
|
||||
@@ -150,7 +150,7 @@
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Az eszköz azonosító helyett jelenik meg. A többi eszközön alapértelmezett névként használható. ",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Az eszköz azonosító helyett jelenik meg. Üresen hagyva az eszköz saját neve lesz használva ",
|
||||
"Shutdown": "Leállítás",
|
||||
"Shutdown Complete": "Leállítás",
|
||||
"Shutdown Complete": "Leállítás kész",
|
||||
"Simple File Versioning": "Egyszerű fájl verziókövetés",
|
||||
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
|
||||
"Smallest First": "Kisebb előbb",
|
||||
@@ -167,7 +167,7 @@
|
||||
"Syncthing is restarting.": "Syncthing újraindul",
|
||||
"Syncthing is upgrading.": "Syncthing frissül",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Úgy tűnik, hogy a Syncthing nem működik, vagy valami probléma van az hálózati kapcsolattal. Újra próbálom...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Úgy tűnik, hogy a Syncthing problémába ütközött a kérés feldolgozása során. Ha a probléma továbbra is fennáll, akkor frissíteni kell az oldalt, vagy újra kell indítani a Syncthinget.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Az összevont statisztikák nyilvánosan elérhetők a {{url}} címen.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A beállítások elmentésre kerültek, de nem lettek aktiválva. Indítsd újra a Syncthing-et, hogy aktiváld őket.",
|
||||
"The device ID cannot be blank.": "Az eszköz azonosító nem lehet üres.",
|
||||
@@ -189,9 +189,9 @@
|
||||
"The number of old versions to keep, per file.": "A megtartott régi verziók száma, fájlonként.",
|
||||
"The number of versions must be a number and cannot be blank.": "A megtartott verziók száma nem lehet üres",
|
||||
"The path cannot be blank.": "Elérési út nem lehet üres.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Az arány limitnek pozitív számnak kell lennie (0: nincs limit)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Az átnézési intervallum nullánál nagyobb másodperc érték kell legyen",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "A hiba javítása után automatikusan újra megpróbálja a szinkronizálást.",
|
||||
"This is a major version upgrade.": "Ez egy főverzió frissítés.",
|
||||
"Trash Can File Versioning": "Szemetes fájl verziózás",
|
||||
"Unknown": "Ismeretlen",
|
||||
@@ -204,7 +204,7 @@
|
||||
"Upgrading": "Frissítés",
|
||||
"Upload Rate": "Feltöltési sebesség",
|
||||
"Uptime": "Üzemidő",
|
||||
"Use HTTPS for GUI": "HTTPS használata a GUI-hoz",
|
||||
"Use HTTPS for GUI": "HTTPS használata a felülethez",
|
||||
"Version": "Verzió",
|
||||
"Versions Path": "Verziók útvonala",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "A régi verziók automatikusan törlődnek, amennyiben öregebbek mint a maximum kor, vagy már több van belőlük mint az adott időszakban megtartható maximum.",
|
||||
@@ -212,7 +212,8 @@
|
||||
"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.": "Amikor új mappát adsz hozzá, tartsd észben, hogy a mappa azonosító arra való hogy összekösd a mappákat az eszközeiden. Az azonosító kisbetű-nagybetű érzékeny és pontosan egyeznie kell az eszközökön.",
|
||||
"Yes": "Igen",
|
||||
"You must keep at least one version.": "Legalább egy verziót meg kell tartanod",
|
||||
"days": "nap",
|
||||
"full documentation": "teljes dokumentáció",
|
||||
"items": "elemek",
|
||||
"items": "elem",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} meg szeretné osztani a \"{{folder}}\" nevű mappát."
|
||||
}
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Quando aggiungi una nuova cartella, ricordati che gli ID vengono utilizzati per collegare le cartelle nei dispositivi. Distinguono maiuscole e minuscole e devono corrispondere esattamente su tutti i dispositivi.",
|
||||
"Yes": "Sì",
|
||||
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
|
||||
"days": "days",
|
||||
"full documentation": "documentazione completa",
|
||||
"items": "elementi",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vuole condividere la cartella \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "新しいフォルダーを追加する際、フォルダーIDはデバイス間でフォルダーの対応づけに使われることに注意してください。フォルダーIDは大文字と小文字が区別され、共有するすべてのデバイスの間で完全に一致しなくてはなりません。",
|
||||
"Yes": "はい",
|
||||
"You must keep at least one version.": "少なくとも一つのバージョンを保存してください。",
|
||||
"days": "日",
|
||||
"full documentation": "詳細なマニュアル",
|
||||
"items": "項目",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。"
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
|
||||
"Yes": "예",
|
||||
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
|
||||
"days": "days",
|
||||
"full documentation": "전체 문서",
|
||||
"items": "항목",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다."
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"The number of old versions to keep, per file.": "Kiek failo versijų saugoti.",
|
||||
"The number of versions must be a number and cannot be blank.": "Versijų skaičius turi būti skaitmuo ir negali būti tuščias laukelis.",
|
||||
"The path cannot be blank.": "Kelias negali būti tuščias.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Srauto maksimalus greitis privalo būti ne neigiamas skaičius (0: nėra apribojimo)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "Tai yra stambus atnaujinimas.",
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Kai įvedate naują aplanką neužmirškite, kad jis bus naudojamas visuose įrenginiuose. Svarbu visur įvesti visiškai tokį pat aplanko vardą neužmirštant apie didžiąsias ir mažąsias raides.",
|
||||
"Yes": "Taip",
|
||||
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
|
||||
"days": "days",
|
||||
"full documentation": "pilna dokumentacija",
|
||||
"items": "įrašai",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\""
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
"Maximum Age": "Maksimal Levetid",
|
||||
"Metadata Only": "Kun metadata",
|
||||
"Minimum Free Disk Space": "Nødvendig ledig diskplass",
|
||||
"Move to top of queue": "Flytt til topp av kø",
|
||||
"Move to top of queue": "Flytt fremst i køen",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
|
||||
"Never": "Aldri",
|
||||
"New Device": "Ny Enhet",
|
||||
@@ -122,8 +122,8 @@
|
||||
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
|
||||
"RAM Utilization": "RAM-utnyttelse",
|
||||
"Random": "Tilfeldig",
|
||||
"Relayed via": "Relayed via",
|
||||
"Relays": "Relays",
|
||||
"Relayed via": "Relé via",
|
||||
"Relays": "Reléer",
|
||||
"Release Notes": "Utgivelsesnotat",
|
||||
"Remove": "Fjern",
|
||||
"Rescan": "Skann på nytt",
|
||||
@@ -212,6 +212,7 @@
|
||||
"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 en ny mappe blir lagt til, husk at Mappe-ID blir brukt til å binde sammen mapper mellom enheter. Det er forskjell på store og små bokstaver, så IDene må være identiske på alle enhetene.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du må beholde minst én versjon",
|
||||
"days": "dager",
|
||||
"full documentation": "all dokumentasjon",
|
||||
"items": "element",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\"."
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"Edit Folder": "Bewerk map",
|
||||
"Editing": "Bezig met bewerken",
|
||||
"Enable UPnP": "UPnP gebruiken",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen of \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen in of voer \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
|
||||
"Enter ignore patterns, one per line.": "Voer negeerpatronen in, één per regel.",
|
||||
"Error": "Fout",
|
||||
"External File Versioning": "Extern versiebeheer voor bestanden",
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Houd er bij het toevoegen van nieuwe mappen rekening mee dat het map-ID gebruikt wordt om mappen tussen apparaten te verbinden. Dit ID is hoofdlettergevoelig en moet identiek zijn op andere apparaten.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
|
||||
"days": "dagen",
|
||||
"full documentation": "volledige documentatie",
|
||||
"items": "objecten",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de map \"{{folder}}\" delen."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Hugs at når ei ny mappe vert lagt til, vert mappe-ID-en brukt til å binda saman mappene mellom einingane. Det er skilnad på store og små bokstavar, så ID-ane må vera identiske på alle einingane.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du må behalda minst ein versjon.",
|
||||
"days": "days",
|
||||
"full documentation": "all dokumentasjon",
|
||||
"items": "element",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønskjer å dela mappa \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Przy dodawaniu nowego folderu, pamiętaj, że ID użyte jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ciągu ma znaczenie musi zgadzać się na wszystkich urządzeniach.",
|
||||
"Yes": "Tak",
|
||||
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
|
||||
"days": "days",
|
||||
"full documentation": "pełna dokumentacja",
|
||||
"items": "pozycji",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\""
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"Device ID": "ID do dispositivo",
|
||||
"Device Identification": "Identificação do dispositivo",
|
||||
"Device Name": "Nome do dispositivo",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
|
||||
"Devices": "Dispositivos",
|
||||
"Disconnected": "Desconectado",
|
||||
"Documentation": "Documentação",
|
||||
@@ -49,7 +49,7 @@
|
||||
"Edit Folder": "Editar pasta",
|
||||
"Editing": "Editando",
|
||||
"Enable UPnP": "Habilitar UPnP",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática de endereço.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática do endereço.",
|
||||
"Enter ignore patterns, one per line.": "Insira os padrões de exclusão, um por linha.",
|
||||
"Error": "Erro",
|
||||
"External File Versioning": "Versionamento externo de arquivo",
|
||||
@@ -57,9 +57,9 @@
|
||||
"File Pull Order": "Ordem de retirada do arquivo",
|
||||
"File Versioning": "Versionamento de arquivos",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Os bits de permissão de um arquivo são ignorados durante as verificações. Use em sistemas de arquivo FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são motivos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando são substituídos ou removidos pelo Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são movidos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions após serem substituídos ou removidos pelo Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto dos dispositivos.",
|
||||
"Folder": "Pasta",
|
||||
"Folder ID": "ID da pasta",
|
||||
"Folder Master": "Pasta mestre",
|
||||
@@ -140,14 +140,14 @@
|
||||
"Select the folders to share with this device.": "Selecione as pastas a serem compartilhadas com este dispositivo.",
|
||||
"Settings": "Configurações",
|
||||
"Share": "Compartilhar",
|
||||
"Share Folder": "Compartilhar Pasta",
|
||||
"Share Folder": "Compartilhar pasta",
|
||||
"Share Folders With Device": "Compartilhar pastas com o dispositivo",
|
||||
"Share With Devices": "Compartilhar com os dispositivos",
|
||||
"Share this folder?": "Compartilhar esta pasta?",
|
||||
"Shared With": "Compartilhada com",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curto para a pasta. Deve ser igual em todos os dispositivos.",
|
||||
"Show ID": "Mostrar ID",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome pré-definido opcional.",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome opcional e pré-definido.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será atualizado para o nome que o dispositivo divulga, caso seja deixado em branco.",
|
||||
"Shutdown": "Desligar",
|
||||
"Shutdown Complete": "Desligamento completado",
|
||||
@@ -172,11 +172,11 @@
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi salva mas ainda não foi ativada. O Syncthing precisa ser reiniciado para a ativação da nova configuração.",
|
||||
"The device ID cannot be blank.": "O ID de dispositivo não pode ficar vazio.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a ser inserido aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífens são opcionais (ignorados).",
|
||||
"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.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
|
||||
"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.": "O relatório de uso é criptografado e enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
|
||||
"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.": "O ID de dispositivo inserido não parece ser válido. Ele deve ter entre 52 e 56 caracteres e ser composto de letras e números, com espaços e hífens opcionais.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "O primeiro argumento da linha de comando é o caminho da pasta e o segundo é o caminho relativo à pasta.",
|
||||
"The folder ID cannot be blank.": "O ID da pasta não pode ficar vazio.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos), composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos) e composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
|
||||
"The folder ID must be unique.": "O ID da pasta deve ser único.",
|
||||
"The folder path cannot be blank.": "O caminho da pasta não pode ficar vazio.",
|
||||
"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.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
|
||||
@@ -184,7 +184,7 @@
|
||||
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico. O campo não pode ficar vazio.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "O número máximo de dias em que uma versão é guardada. (Use 0 para manter para sempre).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "A porcentagem de espaço livre mínimo no disco deve ser um número positivo entre 0 e 100 (inclusive).",
|
||||
"The number of days must be a number and cannot be blank.": "O número de dias deve ser um número valido e não pode ficar em branco.",
|
||||
"The number of days must be a number and cannot be blank.": "O número de dias deve ser um número válido e não pode ficar em branco.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "O número de dias em que são mantidos os arquivos da lixeira. Zero significa para sempre.",
|
||||
"The number of old versions to keep, per file.": "O número de versões antigas a serem mantidas, por arquivo.",
|
||||
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numérico. O campo não pode ficar vazio.",
|
||||
@@ -209,9 +209,10 @@
|
||||
"Versions Path": "Caminho das versões",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "As versões são automaticamente apagadas se elas são mais antigas do que a idade máxima ou excederem o número de arquivos permitido em um intervalo.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando estiver adicionando um dispositivo, lembre-se de que este dispositivo deve ser adicionado do outro lado também.",
|
||||
"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.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e devem ser iguais em todos os dispositivos.",
|
||||
"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.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e deve ser o mesmo em todos os dispositivos.",
|
||||
"Yes": "Sim",
|
||||
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
|
||||
"days": "dias",
|
||||
"full documentation": "documentação completa",
|
||||
"items": "itens",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar as pastas entre dispositivos. É sensível às diferenças entre maiúsculas e minúsculas e tem que ter uma correspondência perfeita entre todos os dispositivos.",
|
||||
"Yes": "Sim",
|
||||
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
|
||||
"days": "dias",
|
||||
"full documentation": "documentação completa",
|
||||
"items": "itens",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "Cînd adăugaţi un fişier nou, nu uitaţi că ID-ul fişierului va rămîne acelaşi pe toate dispozitivele. Iar literele mari sînt diferite de literele mici. ",
|
||||
"Yes": "Da",
|
||||
"You must keep at least one version.": "Trebuie să păstrezi cel puţin o versiune.",
|
||||
"days": "days",
|
||||
"full documentation": "toată documentaţia",
|
||||
"items": "obiecte",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{Dispozitivul}} vrea să transmită mapa {{Mapa}}"
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
|
||||
"Yes": "Да",
|
||||
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
|
||||
"days": "Дней",
|
||||
"full documentation": "полная документация",
|
||||
"items": "элементы",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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 ny katalog, tänk på att katalog-ID:t knyter ihop katalogen mellan olika noder. De måste vara exakt desamma mellan noder och stora eller små bokstäver har betydelse.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
|
||||
"days": "days",
|
||||
"full documentation": "fullständig dokumentation",
|
||||
"items": "poster",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\"."
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"Edit Folder": "Редагувати директорію",
|
||||
"Editing": "Редагування",
|
||||
"Enable UPnP": "Увімкнути UPnP",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введіть розділені комою (\"tcp://ip:port\", \"tcp://host:port\") адреси або \"dynamic\" для автоматичного визначення адреси.",
|
||||
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
|
||||
"Error": "Помилка",
|
||||
"External File Versioning": "Зовнішне керування версіями",
|
||||
@@ -74,7 +74,7 @@
|
||||
"Global Discovery Server": "Сервер для глобального виявлення",
|
||||
"Global State": "Глобальний статус",
|
||||
"Help": "Допомога",
|
||||
"Home page": "Home page",
|
||||
"Home page": "Домашня сторінка",
|
||||
"Ignore": "Ігнорувати",
|
||||
"Ignore Patterns": "Ігнорувати шаблони",
|
||||
"Ignore Permissions": "Ігнорувати права доступу до файлів",
|
||||
@@ -93,7 +93,7 @@
|
||||
"Major Upgrade": "Мажорне оновлення",
|
||||
"Maximum Age": "Максимальний вік",
|
||||
"Metadata Only": "Тільки метадані",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
"Minimum Free Disk Space": "Мінімальний вільний простір на диску",
|
||||
"Move to top of queue": "Пересунути у початок черги",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Багаторівнева маска (пошук збігів в усіх піддиректоріях) ",
|
||||
"Never": "Ніколи",
|
||||
@@ -113,8 +113,8 @@
|
||||
"Override Changes": "Перезаписати зміни",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Шлях до директорії на локальному комп’ютері. Буде створений, якщо такий не існує. Символ тильди (~) може бути використаний як ярлик для",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
|
||||
"Pause": "Pause",
|
||||
"Paused": "Paused",
|
||||
"Pause": "Пауза",
|
||||
"Paused": "Призупинено",
|
||||
"Please consult the release notes before performing a major upgrade.": "Будь ласка перегляньте примітки до випуску перед мажорним оновленням. ",
|
||||
"Please wait": "Будь ласка, зачекайте",
|
||||
"Preview": "Попередній перегляд",
|
||||
@@ -122,17 +122,17 @@
|
||||
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
|
||||
"RAM Utilization": "Використання RAM",
|
||||
"Random": "Випадково",
|
||||
"Relayed via": "Relayed via",
|
||||
"Relays": "Relays",
|
||||
"Relayed via": "Ретранслювати через",
|
||||
"Relays": "Ретрансляція",
|
||||
"Release Notes": "Примітки до випуску",
|
||||
"Remove": "Remove",
|
||||
"Remove": "Видалити",
|
||||
"Rescan": "Пересканувати",
|
||||
"Rescan All": "Пересканувати усе",
|
||||
"Rescan Interval": "Інтервал для повторного сканування",
|
||||
"Restart": "Перезапуск",
|
||||
"Restart Needed": "Необхідний перезапуск",
|
||||
"Restarting": "Відбувається перезапуск",
|
||||
"Resume": "Resume",
|
||||
"Resume": "Продовжити",
|
||||
"Reused": "Використано вдруге",
|
||||
"Save": "Зберегти",
|
||||
"Scanning": "Сканування",
|
||||
@@ -157,7 +157,7 @@
|
||||
"Source Code": "Сирцевий код",
|
||||
"Staggered File Versioning": "Поступове версіонування",
|
||||
"Start Browser": "Запустити браузер",
|
||||
"Statistics": "Statistics",
|
||||
"Statistics": "Статистика",
|
||||
"Stopped": "Зупинено",
|
||||
"Support": "Підтримка",
|
||||
"Sync Protocol Listen Addresses": "Адреса панелі управління",
|
||||
@@ -183,13 +183,13 @@
|
||||
"The following items could not be synchronized.": "Наступні пункти не можуть бути синхронізовані.",
|
||||
"The maximum age must be a number and cannot be blank.": "Максимальний термін повинен бути числом та не може бути пустим.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальний термін, щоб зберігати версію (у днях, вствновіть в 0, щоби зберігати версії назавжди).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Відсоток мінімального вільного простору на диску має бути додатнім числом від 0 до 100 включно.",
|
||||
"The number of days must be a number and cannot be blank.": "Кількість днів має бути числом і не може бути порожнім.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Кількість днів зберігання файлів у кошику. Нуль означає назавжди.",
|
||||
"The number of old versions to keep, per file.": "Кількість старих версій, яку необхідно зберігати для кожного файлу.",
|
||||
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
|
||||
"The path cannot be blank.": "Шлях не може бути порожнім.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Швидкість має бути додатнім числом.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Вони будуть автоматично повторно синхронізовані, коли помилку буде усунено. ",
|
||||
"This is a major version upgrade.": "Це оновлення мажорної версії",
|
||||
@@ -212,6 +212,7 @@
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між вузлами. Назви є чутливими до регістра та повинні співпадати точно між усіма вузлами.",
|
||||
"Yes": "Так",
|
||||
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
|
||||
"days": "days",
|
||||
"full documentation": "повна документація",
|
||||
"items": "елементи",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\"."
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "若你添加了新文件夹,记住文件夹标识是用以在不同设备间建立联系的。在不同设备间拥有相同标识的文件夹将会被同步。且文件夹标识大小写敏感。",
|
||||
"Yes": "是",
|
||||
"You must keep at least one version.": "您必须保留至少一个版本。",
|
||||
"days": "days",
|
||||
"full documentation": "完整文档",
|
||||
"items": "条目",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将 “{{folder}}” 文件夹共享给您"
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"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.": "當新增一個資料夾時,請記住,資料夾識別碼是用來將裝置之間的資料夾綁定在一起的。它們有區分大小寫,且必須在所有裝置之間完全相同。",
|
||||
"Yes": "是",
|
||||
"You must keep at least one version.": "您必須保留至少一個版本。",
|
||||
"days": "days",
|
||||
"full documentation": "完整說明文件",
|
||||
"items": "個項目",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\"。"
|
||||
|
||||
+3
-3
@@ -70,7 +70,7 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<div class="container" id="content">
|
||||
|
||||
<!-- Panel: Restart Needed -->
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title"><span class="fa fa-exclamation-circle"></span><span translate>Notice</span></h3></div>
|
||||
<div class="panel-body">
|
||||
<p ng-repeat="err in errorList()"><small>{{err.time | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
|
||||
<p ng-repeat="err in errorList()"><small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.message)}}</p>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="clearErrors()">
|
||||
@@ -239,7 +239,7 @@
|
||||
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].needFiles > 0">
|
||||
<th><span class="fa fa-fw fa-cloud-download"></span> <span translate>Out of Sync</span></th>
|
||||
<th><span class="fa fa-fw fa-cloud-download"></span> <span translate>Out of Sync Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
|
||||
</td>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<div class="col-md-12">
|
||||
<ul class="list-unstyled three-columns" id="contributor-list">
|
||||
<li class="auto-generated">Aaron Bieber</li>
|
||||
<li class="auto-generated">Adam Piggott</li>
|
||||
<li class="auto-generated">Alexander Graf</li>
|
||||
<li class="auto-generated">Andrew Dunham</li>
|
||||
<li class="auto-generated">Antony Male</li>
|
||||
@@ -59,6 +60,8 @@
|
||||
<li class="auto-generated">Marc Laporte</li>
|
||||
<li class="auto-generated">Marc Pujol</li>
|
||||
<li class="auto-generated">Marcin Dziadus</li>
|
||||
<li class="auto-generated">Mateon1</li>
|
||||
<li class="auto-generated">Matt Burke</li>
|
||||
<li class="auto-generated">Michael Jephcote</li>
|
||||
<li class="auto-generated">Michael Tilli</li>
|
||||
<li class="auto-generated">Pascal Jungblut</li>
|
||||
|
||||
@@ -1043,13 +1043,16 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.errorList = function () {
|
||||
if (!$scope.errors) {
|
||||
return [];
|
||||
}
|
||||
return $scope.errors.filter(function (e) {
|
||||
return e.time > $scope.seenError;
|
||||
return e.when > $scope.seenError;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clearErrors = function () {
|
||||
$scope.seenError = $scope.errors[$scope.errors.length - 1].time;
|
||||
$scope.seenError = $scope.errors[$scope.errors.length - 1].when;
|
||||
$http.post(urlbase + '/system/error/clear');
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<datalist id="discovery-list" ng-if="!editingExisting">
|
||||
<option ng-repeat="(id, data) in discovery" value="{{id}}" />
|
||||
</datalist>
|
||||
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.deviceID}}</div>
|
||||
<div ng-if="editingExisting" class="well well-sm text-monospace" select-on-click>{{currentDevice.deviceID}}</div>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">The device ID to enter here can be found in the "Edit > Show ID" dialog on the other device. Spaces and dashes are optional (ignored).</span>
|
||||
<span translate ng-show="!editingExisting && (deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine)">When adding a new device, keep in mind that this device must be added on the other side too.</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<modal id="idqr" status="info" icon="qrcode" title="{{'Device Identification' | translate}} - {{deviceName(thisDevice())}}" large="yes" close="yes">
|
||||
<div class="well well-sm text-monospace text-center">{{myID}}</div>
|
||||
<div class="well well-sm text-monospace text-center" select-on-click>{{myID}}</div>
|
||||
<img ng-if="myID" class="center-block img-thumbnail" ng-src="qr/?text={{myID}}"/>
|
||||
</modal>
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
<div class="col-md-12">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<label for="folderID"><span translate>Folder ID</span></label>
|
||||
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
|
||||
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder></input>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span>
|
||||
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
|
||||
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.folderID.$error.pattern && folderEditor.folderID.$dirty">The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label translate>API Key</label>
|
||||
<div class="well well-sm text-monospace">{{tmpGUI.apiKey || "-"}}</div>
|
||||
<div class="well well-sm text-monospace" select-on-click>{{tmpGUI.apiKey || "-"}}</div>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(tmpGUI)">
|
||||
<span class="fa fa-repeat"></span> <span translate>Generate</span>
|
||||
</button>
|
||||
|
||||
+44
-40
File diff suppressed because one or more lines are too long
+35
-51
@@ -33,9 +33,7 @@ func NewBroadcast(port int) *Broadcast {
|
||||
FailureBackoff: 60 * time.Second,
|
||||
// Only log restarts in debug mode.
|
||||
Log: func(line string) {
|
||||
if debug {
|
||||
l.Debugln(line)
|
||||
}
|
||||
l.Debugln(line)
|
||||
},
|
||||
}),
|
||||
port: port,
|
||||
@@ -81,17 +79,13 @@ type broadcastWriter struct {
|
||||
}
|
||||
|
||||
func (w *broadcastWriter) Serve() {
|
||||
if debug {
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
}
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
|
||||
var err error
|
||||
w.conn, err = net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
return
|
||||
}
|
||||
@@ -100,9 +94,7 @@ func (w *broadcastWriter) Serve() {
|
||||
for bs := range w.inbox {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
continue
|
||||
}
|
||||
@@ -120,41 +112,43 @@ func (w *broadcastWriter) Serve() {
|
||||
dsts = append(dsts, net.IP{0xff, 0xff, 0xff, 0xff})
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugln("addresses:", dsts)
|
||||
}
|
||||
l.Debugln("addresses:", dsts)
|
||||
|
||||
success := 0
|
||||
for _, ip := range dsts {
|
||||
dst := &net.UDPAddr{IP: ip, Port: w.port}
|
||||
|
||||
w.conn.SetWriteDeadline(time.Now().Add(time.Second))
|
||||
_, err := w.conn.WriteTo(bs, dst)
|
||||
w.conn.SetWriteDeadline(time.Time{})
|
||||
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
// Write timeouts should not happen. We treat it as a fatal
|
||||
// error on the socket.
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
return
|
||||
} else if err, ok := err.(net.Error); ok && err.Temporary() {
|
||||
// A transient error. Lets hope for better luck in the future.
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
// Some other error that we don't expect. Bail and retry.
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
w.setError(err)
|
||||
return
|
||||
} else if debug {
|
||||
l.Debugf("sent %d bytes to %s", len(bs), dst)
|
||||
w.setError(nil)
|
||||
}
|
||||
|
||||
if err, ok := err.(net.Error); ok && err.Temporary() {
|
||||
// A transient error. Lets hope for better luck in the future.
|
||||
l.Debugln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Some other error that we don't expect. Bail and retry.
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debugf("sent %d bytes to %s", len(bs), dst)
|
||||
success++
|
||||
}
|
||||
|
||||
if success > 0 {
|
||||
w.setError(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,17 +169,13 @@ type broadcastReader struct {
|
||||
}
|
||||
|
||||
func (r *broadcastReader) Serve() {
|
||||
if debug {
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
}
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
|
||||
var err error
|
||||
r.conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
r.setError(err)
|
||||
return
|
||||
}
|
||||
@@ -195,27 +185,21 @@ func (r *broadcastReader) Serve() {
|
||||
for {
|
||||
n, addr, err := r.conn.ReadFrom(bs)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
r.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r.setError(nil)
|
||||
|
||||
if debug {
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
}
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
|
||||
c := make([]byte, n)
|
||||
copy(c, bs)
|
||||
select {
|
||||
case r.outbox <- recv{c, addr}:
|
||||
default:
|
||||
if debug {
|
||||
l.Debugln("dropping message")
|
||||
}
|
||||
l.Debugln("dropping message")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -10,10 +10,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "beacon") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
l = logger.DefaultLogger.NewFacility("beacon", "Multicast and broadcast discovery")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("beacon", strings.Contains(os.Getenv("STTRACE"), "beacon") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
+29
-56
@@ -35,9 +35,7 @@ func NewMulticast(addr string) *Multicast {
|
||||
FailureBackoff: 60 * time.Second,
|
||||
// Only log restarts in debug mode.
|
||||
Log: func(line string) {
|
||||
if debug {
|
||||
l.Debugln(line)
|
||||
}
|
||||
l.Debugln(line)
|
||||
},
|
||||
}),
|
||||
inbox: make(chan []byte),
|
||||
@@ -85,25 +83,19 @@ type multicastWriter struct {
|
||||
}
|
||||
|
||||
func (w *multicastWriter) Serve() {
|
||||
if debug {
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
}
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
|
||||
gaddr, err := net.ResolveUDPAddr("udp6", w.addr)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := net.ListenPacket("udp6", ":0")
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
return
|
||||
}
|
||||
@@ -117,34 +109,33 @@ func (w *multicastWriter) Serve() {
|
||||
for bs := range w.inbox {
|
||||
intfs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var success int
|
||||
|
||||
success := 0
|
||||
for _, intf := range intfs {
|
||||
wcm.IfIndex = intf.Index
|
||||
pconn.SetWriteDeadline(time.Now().Add(time.Second))
|
||||
_, err = pconn.WriteTo(bs, wcm, gaddr)
|
||||
pconn.SetWriteDeadline(time.Time{})
|
||||
if err != nil && debug {
|
||||
|
||||
if err != nil {
|
||||
l.Debugln(err, "on write to", gaddr, intf.Name)
|
||||
} else if debug {
|
||||
l.Debugf("sent %d bytes to %v on %s", len(bs), gaddr, intf.Name)
|
||||
success++
|
||||
w.setError(err)
|
||||
continue
|
||||
}
|
||||
|
||||
l.Debugf("sent %d bytes to %v on %s", len(bs), gaddr, intf.Name)
|
||||
|
||||
success++
|
||||
}
|
||||
|
||||
if success > 0 {
|
||||
w.setError(nil)
|
||||
} else {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
w.setError(err)
|
||||
}
|
||||
}
|
||||
@@ -166,34 +157,26 @@ type multicastReader struct {
|
||||
}
|
||||
|
||||
func (r *multicastReader) Serve() {
|
||||
if debug {
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
}
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
|
||||
gaddr, err := net.ResolveUDPAddr("udp6", r.addr)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
r.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := net.ListenPacket("udp6", r.addr)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
r.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
intfs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
r.setError(err)
|
||||
return
|
||||
}
|
||||
@@ -202,20 +185,16 @@ func (r *multicastReader) Serve() {
|
||||
joined := 0
|
||||
for _, intf := range intfs {
|
||||
err := pconn.JoinGroup(&intf, &net.UDPAddr{IP: gaddr.IP})
|
||||
if debug {
|
||||
if err != nil {
|
||||
l.Debugln("IPv6 join", intf.Name, "failed:", err)
|
||||
} else {
|
||||
l.Debugln("IPv6 join", intf.Name, "success")
|
||||
}
|
||||
if err != nil {
|
||||
l.Debugln("IPv6 join", intf.Name, "failed:", err)
|
||||
} else {
|
||||
l.Debugln("IPv6 join", intf.Name, "success")
|
||||
}
|
||||
joined++
|
||||
}
|
||||
|
||||
if joined == 0 {
|
||||
if debug {
|
||||
l.Debugln("no multicast interfaces available")
|
||||
}
|
||||
l.Debugln("no multicast interfaces available")
|
||||
r.setError(errors.New("no multicast interfaces available"))
|
||||
return
|
||||
}
|
||||
@@ -224,24 +203,18 @@ func (r *multicastReader) Serve() {
|
||||
for {
|
||||
n, _, addr, err := pconn.ReadFrom(bs)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
r.setError(err)
|
||||
continue
|
||||
}
|
||||
if debug {
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
}
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
|
||||
c := make([]byte, n)
|
||||
copy(c, bs)
|
||||
select {
|
||||
case r.outbox <- recv{c, addr}:
|
||||
default:
|
||||
if debug {
|
||||
l.Debugln("dropping message")
|
||||
}
|
||||
l.Debugln("dropping message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -108,7 +108,9 @@ type FolderConfiguration struct {
|
||||
Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
|
||||
Order PullOrder `xml:"order" json:"order"`
|
||||
IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
|
||||
ScanProgressIntervalS int `xml:"scanProgressInterval" json:"scanProgressInterval"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
|
||||
ScanProgressIntervalS int `xml:"scanProgressIntervalS" json:"scanProgressIntervalS"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
|
||||
PullerSleepS int `xml:"pullerSleepS" json:"pullerSleepS"`
|
||||
PullerPauseS int `xml:"pullerPauseS" json:"pullerPauseS"`
|
||||
|
||||
Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
var device1, device2, device3, device4 protocol.DeviceID
|
||||
|
||||
+6
-3
@@ -10,10 +10,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "config") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
l = logger.DefaultLogger.NewFacility("config", "Configuration loading and saving")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("config", strings.Contains(os.Getenv("STTRACE"), "config") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
+5
-13
@@ -9,9 +9,9 @@ package config
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
@@ -142,13 +142,9 @@ func (w *Wrapper) replaceLocked(to Configuration) CommitResponse {
|
||||
from := w.cfg
|
||||
|
||||
for _, sub := range w.subs {
|
||||
if debug {
|
||||
l.Debugln(sub, "verifying configuration")
|
||||
}
|
||||
l.Debugln(sub, "verifying configuration")
|
||||
if err := sub.VerifyConfiguration(from, to); err != nil {
|
||||
if debug {
|
||||
l.Debugln(sub, "rejected config:", err)
|
||||
}
|
||||
l.Debugln(sub, "rejected config:", err)
|
||||
return CommitResponse{
|
||||
ValidationError: err,
|
||||
}
|
||||
@@ -157,14 +153,10 @@ func (w *Wrapper) replaceLocked(to Configuration) CommitResponse {
|
||||
|
||||
allOk := true
|
||||
for _, sub := range w.subs {
|
||||
if debug {
|
||||
l.Debugln(sub, "committing configuration")
|
||||
}
|
||||
l.Debugln(sub, "committing configuration")
|
||||
ok := sub.CommitConfiguration(from, to)
|
||||
if !ok {
|
||||
if debug {
|
||||
l.Debugln(sub, "requires restart")
|
||||
}
|
||||
l.Debugln(sub, "requires restart")
|
||||
allOk = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package connections
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@@ -15,14 +15,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/relaysrv/client"
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/discover"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/relay"
|
||||
"github.com/syncthing/syncthing/lib/relay/client"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
@@ -35,17 +36,29 @@ var (
|
||||
listeners = make(map[string]ListenerFactory, 0)
|
||||
)
|
||||
|
||||
type Model interface {
|
||||
protocol.Model
|
||||
AddConnection(conn model.Connection)
|
||||
ConnectedTo(remoteID protocol.DeviceID) bool
|
||||
IsPaused(remoteID protocol.DeviceID) bool
|
||||
}
|
||||
|
||||
// The connection service listens on TLS and dials configured unconnected
|
||||
// devices. Successful connections are handed to the model.
|
||||
type connectionSvc struct {
|
||||
*suture.Supervisor
|
||||
cfg *config.Wrapper
|
||||
myID protocol.DeviceID
|
||||
model *model.Model
|
||||
tlsCfg *tls.Config
|
||||
discoverer discover.Finder
|
||||
conns chan model.IntermediateConnection
|
||||
relaySvc *relay.Svc
|
||||
cfg *config.Wrapper
|
||||
myID protocol.DeviceID
|
||||
model Model
|
||||
tlsCfg *tls.Config
|
||||
discoverer discover.Finder
|
||||
conns chan model.IntermediateConnection
|
||||
relaySvc *relay.Svc
|
||||
bepProtocolName string
|
||||
tlsDefaultCommonName string
|
||||
lans []*net.IPNet
|
||||
writeRateLimit *ratelimit.Bucket
|
||||
readRateLimit *ratelimit.Bucket
|
||||
|
||||
lastRelayCheck map[protocol.DeviceID]time.Time
|
||||
|
||||
@@ -54,16 +67,20 @@ type connectionSvc struct {
|
||||
relaysEnabled bool
|
||||
}
|
||||
|
||||
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Model, tlsCfg *tls.Config, discoverer discover.Finder, relaySvc *relay.Svc) *connectionSvc {
|
||||
func NewConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, relaySvc *relay.Svc,
|
||||
bepProtocolName string, tlsDefaultCommonName string, lans []*net.IPNet) suture.Service {
|
||||
svc := &connectionSvc{
|
||||
Supervisor: suture.NewSimple("connectionSvc"),
|
||||
cfg: cfg,
|
||||
myID: myID,
|
||||
model: mdl,
|
||||
tlsCfg: tlsCfg,
|
||||
discoverer: discoverer,
|
||||
relaySvc: relaySvc,
|
||||
conns: make(chan model.IntermediateConnection),
|
||||
Supervisor: suture.NewSimple("connectionSvc"),
|
||||
cfg: cfg,
|
||||
myID: myID,
|
||||
model: mdl,
|
||||
tlsCfg: tlsCfg,
|
||||
discoverer: discoverer,
|
||||
relaySvc: relaySvc,
|
||||
conns: make(chan model.IntermediateConnection),
|
||||
bepProtocolName: bepProtocolName,
|
||||
tlsDefaultCommonName: tlsDefaultCommonName,
|
||||
lans: lans,
|
||||
|
||||
connType: make(map[protocol.DeviceID]model.ConnectionType),
|
||||
relaysEnabled: cfg.Options().RelaysEnabled,
|
||||
@@ -71,6 +88,13 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
|
||||
}
|
||||
cfg.Subscribe(svc)
|
||||
|
||||
if svc.cfg.Options().MaxSendKbps > 0 {
|
||||
svc.writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*svc.cfg.Options().MaxSendKbps), int64(5*1000*svc.cfg.Options().MaxSendKbps))
|
||||
}
|
||||
if svc.cfg.Options().MaxRecvKbps > 0 {
|
||||
svc.readRateLimit = ratelimit.NewBucketWithRate(float64(1000*svc.cfg.Options().MaxRecvKbps), int64(5*1000*svc.cfg.Options().MaxRecvKbps))
|
||||
}
|
||||
|
||||
// There are several moving parts here; one routine per listening address
|
||||
// to handle incoming connections, one routine to periodically attempt
|
||||
// outgoing connections, one routine to the the common handling
|
||||
@@ -97,9 +121,7 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
|
||||
continue
|
||||
}
|
||||
|
||||
if debugNet {
|
||||
l.Debugln("listening on", uri.String())
|
||||
}
|
||||
l.Debugln("listening on", uri)
|
||||
|
||||
svc.Add(serviceFunc(func() {
|
||||
listener(uri, svc.tlsCfg, svc.conns)
|
||||
@@ -123,7 +145,7 @@ next:
|
||||
// of the TLS handshake. Unfortunately this can't be a hard error,
|
||||
// because there are implementations out there that don't support
|
||||
// protocol negotiation (iOS for one...).
|
||||
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != bepProtocolName {
|
||||
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != s.bepProtocolName {
|
||||
l.Infof("Peer %s did not negotiate bep/1.0", c.Conn.RemoteAddr())
|
||||
}
|
||||
|
||||
@@ -142,7 +164,7 @@ next:
|
||||
// The device ID should not be that of ourselves. It can happen
|
||||
// though, especially in the presence of NAT hairpinning, multiple
|
||||
// clients between the same NAT gateway, and global discovery.
|
||||
if remoteID == myID {
|
||||
if remoteID == s.myID {
|
||||
l.Infof("Connected to myself (%s) - should not happen", remoteID)
|
||||
c.Conn.Close()
|
||||
continue
|
||||
@@ -154,9 +176,7 @@ next:
|
||||
ct, ok := s.connType[remoteID]
|
||||
s.mut.RUnlock()
|
||||
if ok && !ct.IsDirect() && c.Type.IsDirect() {
|
||||
if debugNet {
|
||||
l.Debugln("Switching connections", remoteID)
|
||||
}
|
||||
l.Debugln("Switching connections", remoteID)
|
||||
s.model.Close(remoteID, fmt.Errorf("switching connections"))
|
||||
} else if s.model.ConnectedTo(remoteID) {
|
||||
// We should not already be connected to the other party. TODO: This
|
||||
@@ -181,7 +201,7 @@ next:
|
||||
// the certificate and used another name.
|
||||
certName := deviceCfg.CertName
|
||||
if certName == "" {
|
||||
certName = tlsDefaultCommonName
|
||||
certName = s.tlsDefaultCommonName
|
||||
}
|
||||
err := remoteCert.VerifyHostname(certName)
|
||||
if err != nil {
|
||||
@@ -199,22 +219,20 @@ next:
|
||||
limit := s.shouldLimit(c.Conn.RemoteAddr())
|
||||
|
||||
wr := io.Writer(c.Conn)
|
||||
if limit && writeRateLimit != nil {
|
||||
wr = &limitedWriter{c.Conn, writeRateLimit}
|
||||
if limit && s.writeRateLimit != nil {
|
||||
wr = NewWriteLimiter(c.Conn, s.writeRateLimit)
|
||||
}
|
||||
|
||||
rd := io.Reader(c.Conn)
|
||||
if limit && readRateLimit != nil {
|
||||
rd = &limitedReader{c.Conn, readRateLimit}
|
||||
if limit && s.readRateLimit != nil {
|
||||
rd = NewReadLimiter(c.Conn, s.readRateLimit)
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s-%s (%s)", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), c.Type)
|
||||
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression)
|
||||
|
||||
l.Infof("Established secure connection to %s at %s", remoteID, name)
|
||||
if debugNet {
|
||||
l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit)
|
||||
}
|
||||
l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit)
|
||||
|
||||
s.model.AddConnection(model.Connection{
|
||||
c.Conn,
|
||||
@@ -245,7 +263,7 @@ func (s *connectionSvc) connect() {
|
||||
for {
|
||||
nextDevice:
|
||||
for deviceID, deviceCfg := range s.cfg.Devices() {
|
||||
if deviceID == myID {
|
||||
if deviceID == s.myID {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -287,18 +305,14 @@ func (s *connectionSvc) connect() {
|
||||
|
||||
dialer, ok := dialers[uri.Scheme]
|
||||
if !ok {
|
||||
l.Infoln("Unknown address schema", uri.String())
|
||||
l.Infoln("Unknown address schema", uri)
|
||||
continue
|
||||
}
|
||||
|
||||
if debugNet {
|
||||
l.Debugln("dial", deviceCfg.DeviceID, uri.String())
|
||||
}
|
||||
l.Debugln("dial", deviceCfg.DeviceID, uri)
|
||||
conn, err := dialer(uri, s.tlsCfg)
|
||||
if err != nil {
|
||||
if debugNet {
|
||||
l.Debugln("dial failed", deviceCfg.DeviceID, uri.String(), err)
|
||||
}
|
||||
l.Debugln("dial failed", deviceCfg.DeviceID, uri, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -323,11 +337,9 @@ func (s *connectionSvc) connect() {
|
||||
|
||||
reconIntv := time.Duration(s.cfg.Options().RelayReconnectIntervalM) * time.Minute
|
||||
if last, ok := s.lastRelayCheck[deviceID]; ok && time.Since(last) < reconIntv {
|
||||
if debugNet {
|
||||
l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
|
||||
}
|
||||
l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
|
||||
continue nextDevice
|
||||
} else if debugNet {
|
||||
} else {
|
||||
l.Debugln("Trying relay connections to", deviceID, relays)
|
||||
}
|
||||
|
||||
@@ -342,21 +354,17 @@ func (s *connectionSvc) connect() {
|
||||
|
||||
inv, err := client.GetInvitationFromRelay(uri, deviceID, s.tlsCfg.Certificates)
|
||||
if err != nil {
|
||||
if debugNet {
|
||||
l.Debugf("Failed to get invitation for %s from %s: %v", deviceID, uri, err)
|
||||
}
|
||||
l.Debugf("Failed to get invitation for %s from %s: %v", deviceID, uri, err)
|
||||
continue
|
||||
} else if debugNet {
|
||||
} else {
|
||||
l.Debugln("Succesfully retrieved relay invitation", inv, "from", uri)
|
||||
}
|
||||
|
||||
conn, err := client.JoinSession(inv)
|
||||
if err != nil {
|
||||
if debugNet {
|
||||
l.Debugf("Failed to join relay session %s: %v", inv, err)
|
||||
}
|
||||
l.Debugf("Failed to join relay session %s: %v", inv, err)
|
||||
continue
|
||||
} else if debugNet {
|
||||
} else {
|
||||
l.Debugln("Sucessfully joined relay session", inv)
|
||||
}
|
||||
|
||||
@@ -412,7 +420,7 @@ func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
for _, lan := range lans {
|
||||
for _, lan := range s.lans {
|
||||
if lan.Contains(tcpaddr.IP) {
|
||||
return false
|
||||
}
|
||||
@@ -444,3 +452,10 @@ func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// serviceFunc wraps a function to create a suture.Service without stop
|
||||
// functionality.
|
||||
type serviceFunc func()
|
||||
|
||||
func (f serviceFunc) Serve() { f() }
|
||||
func (f serviceFunc) Stop() {}
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package connections
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@@ -33,17 +33,13 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
|
||||
|
||||
raddr, err := net.ResolveTCPAddr("tcp", uri.Host)
|
||||
if err != nil {
|
||||
if debugNet {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := net.DialTCP("tcp", nil, raddr)
|
||||
if err != nil {
|
||||
if debugNet {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Debugln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -81,9 +77,7 @@ func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- model.Intermedia
|
||||
continue
|
||||
}
|
||||
|
||||
if debugNet {
|
||||
l.Debugln("connect from", conn.RemoteAddr())
|
||||
}
|
||||
l.Debugln("connect from", conn.RemoteAddr())
|
||||
|
||||
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
|
||||
if err != nil {
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package connections
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("connections", "Connection handling")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("connections", strings.Contains(os.Getenv("STTRACE"), "connections") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package connections
|
||||
|
||||
import (
|
||||
"io"
|
||||
@@ -12,13 +12,20 @@ import (
|
||||
"github.com/juju/ratelimit"
|
||||
)
|
||||
|
||||
type limitedReader struct {
|
||||
r io.Reader
|
||||
type LimitedReader struct {
|
||||
reader io.Reader
|
||||
bucket *ratelimit.Bucket
|
||||
}
|
||||
|
||||
func (r *limitedReader) Read(buf []byte) (int, error) {
|
||||
n, err := r.r.Read(buf)
|
||||
func NewReadLimiter(r io.Reader, b *ratelimit.Bucket) *LimitedReader {
|
||||
return &LimitedReader{
|
||||
reader: r,
|
||||
bucket: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LimitedReader) Read(buf []byte) (int, error) {
|
||||
n, err := r.reader.Read(buf)
|
||||
if r.bucket != nil {
|
||||
r.bucket.Wait(int64(n))
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package connections
|
||||
|
||||
import (
|
||||
"io"
|
||||
@@ -12,14 +12,21 @@ import (
|
||||
"github.com/juju/ratelimit"
|
||||
)
|
||||
|
||||
type limitedWriter struct {
|
||||
w io.Writer
|
||||
type LimitedWriter struct {
|
||||
writer io.Writer
|
||||
bucket *ratelimit.Bucket
|
||||
}
|
||||
|
||||
func (w *limitedWriter) Write(buf []byte) (int, error) {
|
||||
func NewWriteLimiter(w io.Writer, b *ratelimit.Bucket) *LimitedWriter {
|
||||
return &LimitedWriter{
|
||||
writer: w,
|
||||
bucket: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LimitedWriter) Write(buf []byte) (int, error) {
|
||||
if w.bucket != nil {
|
||||
w.bucket.Wait(int64(len(buf)))
|
||||
}
|
||||
return w.w.Write(buf)
|
||||
return w.writer.Write(buf)
|
||||
}
|
||||
+1
-1
@@ -17,8 +17,8 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
|
||||
@@ -9,7 +9,7 @@ package db
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
|
||||
+6
-4
@@ -10,11 +10,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "files") || os.Getenv("STTRACE") == "all"
|
||||
debugDB = strings.Contains(os.Getenv("STTRACE"), "db") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
l = logger.DefaultLogger.NewFacility("db", "The database layer")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("db", strings.Contains(os.Getenv("STTRACE"), "db") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
+53
-151
@@ -15,7 +15,7 @@ import (
|
||||
"runtime"
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
@@ -178,20 +178,14 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
limit := deviceKey(folder, device, []byte{0xff, 0xff, 0xff, 0xff}) // after all folder/device files
|
||||
|
||||
batch := new(leveldb.Batch)
|
||||
if debugDB {
|
||||
l.Debugf("new batch %p", batch)
|
||||
}
|
||||
l.Debugf("new batch %p", batch)
|
||||
snap, err := db.GetSnapshot()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -220,15 +214,11 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
|
||||
cmp := bytes.Compare(newName, oldName)
|
||||
|
||||
if debugDB {
|
||||
l.Debugf("generic replace; folder=%q device=%v moreFs=%v moreDb=%v cmp=%d newName=%q oldName=%q", folder, protocol.DeviceIDFromBytes(device), moreFs, moreDb, cmp, newName, oldName)
|
||||
}
|
||||
l.Debugf("generic replace; folder=%q device=%v moreFs=%v moreDb=%v cmp=%d newName=%q oldName=%q", folder, protocol.DeviceIDFromBytes(device), moreFs, moreDb, cmp, newName, oldName)
|
||||
|
||||
switch {
|
||||
case moreFs && (!moreDb || cmp == -1):
|
||||
if debugDB {
|
||||
l.Debugln("generic replace; missing - insert")
|
||||
}
|
||||
l.Debugln("generic replace; missing - insert")
|
||||
// Database is missing this file. Insert it.
|
||||
if lv := ldbInsert(batch, folder, device, fs[fsi]); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
@@ -244,15 +234,11 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
// File exists on both sides - compare versions. We might get an
|
||||
// update with the same version and different flags if a device has
|
||||
// marked a file as invalid, so handle that too.
|
||||
if debugDB {
|
||||
l.Debugln("generic replace; exists - compare")
|
||||
}
|
||||
l.Debugln("generic replace; exists - compare")
|
||||
var ef FileInfoTruncated
|
||||
ef.UnmarshalXDR(dbi.Value())
|
||||
if !fs[fsi].Version.Equal(ef.Version) || fs[fsi].Flags != ef.Flags {
|
||||
if debugDB {
|
||||
l.Debugln("generic replace; differs - insert")
|
||||
}
|
||||
l.Debugln("generic replace; differs - insert")
|
||||
if lv := ldbInsert(batch, folder, device, fs[fsi]); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
@@ -261,7 +247,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
} else {
|
||||
ldbUpdateGlobal(snap, batch, folder, device, fs[fsi])
|
||||
}
|
||||
} else if debugDB {
|
||||
} else {
|
||||
l.Debugln("generic replace; equal - ignore")
|
||||
}
|
||||
|
||||
@@ -269,9 +255,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
moreDb = dbi.Next()
|
||||
|
||||
case moreDb && (!moreFs || cmp == 1):
|
||||
if debugDB {
|
||||
l.Debugln("generic replace; exists - remove")
|
||||
}
|
||||
l.Debugln("generic replace; exists - remove")
|
||||
if lv := deleteFn(snap, batch, folder, device, oldName, dbi); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
@@ -281,9 +265,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
// Write out and reuse the batch every few records, to avoid the batch
|
||||
// growing too large and thus allocating unnecessarily much memory.
|
||||
if batch.Len() > batchFlushSize {
|
||||
if debugDB {
|
||||
l.Debugf("db.Write %p", batch)
|
||||
}
|
||||
l.Debugf("db.Write %p", batch)
|
||||
|
||||
err = db.Write(batch, nil)
|
||||
if err != nil {
|
||||
@@ -294,9 +276,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
}
|
||||
}
|
||||
|
||||
if debugDB {
|
||||
l.Debugf("db.Write %p", batch)
|
||||
}
|
||||
l.Debugf("db.Write %p", batch)
|
||||
err = db.Write(batch, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -309,13 +289,9 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) i
|
||||
// TODO: Return the remaining maxLocalVer?
|
||||
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
|
||||
// Database has a file that we are missing. Remove it.
|
||||
if debugDB {
|
||||
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
|
||||
}
|
||||
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
|
||||
ldbRemoveFromGlobal(db, batch, folder, device, name)
|
||||
if debugDB {
|
||||
l.Debugf("batch.Delete %p %x", batch, dbi.Key())
|
||||
}
|
||||
l.Debugf("batch.Delete %p %x", batch, dbi.Key())
|
||||
batch.Delete(dbi.Key())
|
||||
return 0
|
||||
})
|
||||
@@ -325,20 +301,14 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
|
||||
runtime.GC()
|
||||
|
||||
batch := new(leveldb.Batch)
|
||||
if debugDB {
|
||||
l.Debugf("new batch %p", batch)
|
||||
}
|
||||
l.Debugf("new batch %p", batch)
|
||||
snap, err := db.GetSnapshot()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -347,9 +317,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
|
||||
for _, f := range fs {
|
||||
name := []byte(f.Name)
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
if lv := ldbInsert(batch, folder, device, f); lv > maxLocalVer {
|
||||
@@ -384,9 +352,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
|
||||
// Write out and reuse the batch every few records, to avoid the batch
|
||||
// growing too large and thus allocating unnecessarily much memory.
|
||||
if batch.Len() > batchFlushSize {
|
||||
if debugDB {
|
||||
l.Debugf("db.Write %p", batch)
|
||||
}
|
||||
l.Debugf("db.Write %p", batch)
|
||||
|
||||
err = db.Write(batch, nil)
|
||||
if err != nil {
|
||||
@@ -397,9 +363,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
|
||||
}
|
||||
}
|
||||
|
||||
if debugDB {
|
||||
l.Debugf("db.Write %p", batch)
|
||||
}
|
||||
l.Debugf("db.Write %p", batch)
|
||||
err = db.Write(batch, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -409,9 +373,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
|
||||
}
|
||||
|
||||
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) int64 {
|
||||
if debugDB {
|
||||
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
|
||||
}
|
||||
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
|
||||
|
||||
if file.LocalVersion == 0 {
|
||||
file.LocalVersion = clock(0)
|
||||
@@ -419,9 +381,7 @@ func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) in
|
||||
|
||||
name := []byte(file.Name)
|
||||
nk := deviceKey(folder, device, name)
|
||||
if debugDB {
|
||||
l.Debugf("batch.Put %p %x", batch, nk)
|
||||
}
|
||||
l.Debugf("batch.Put %p %x", batch, nk)
|
||||
batch.Put(nk, file.MustMarshalXDR())
|
||||
|
||||
return file.LocalVersion
|
||||
@@ -431,9 +391,7 @@ func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) in
|
||||
// file. If the device is already present in the list, the version is updated.
|
||||
// If the file does not have an entry in the global list, it is created.
|
||||
func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device []byte, file protocol.FileInfo) bool {
|
||||
if debugDB {
|
||||
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
|
||||
}
|
||||
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
|
||||
name := []byte(file.Name)
|
||||
gk := globalKey(folder, name)
|
||||
svl, err := db.Get(gk, nil)
|
||||
@@ -499,10 +457,8 @@ func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device []byte, file pr
|
||||
fl.versions = append(fl.versions, nv)
|
||||
|
||||
done:
|
||||
if debugDB {
|
||||
l.Debugf("batch.Put %p %x", batch, gk)
|
||||
l.Debugf("new global after update: %v", fl)
|
||||
}
|
||||
l.Debugf("batch.Put %p %x", batch, gk)
|
||||
l.Debugf("new global after update: %v", fl)
|
||||
batch.Put(gk, fl.MustMarshalXDR())
|
||||
|
||||
return true
|
||||
@@ -519,9 +475,7 @@ func insertVersion(vl []fileVersion, i int, v fileVersion) []fileVersion {
|
||||
// given file. If the version list is empty after this, the file entry is
|
||||
// removed entirely.
|
||||
func ldbRemoveFromGlobal(db dbReader, batch dbWriter, folder, device, file []byte) {
|
||||
if debugDB {
|
||||
l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file)
|
||||
}
|
||||
l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file)
|
||||
|
||||
gk := globalKey(folder, file)
|
||||
svl, err := db.Get(gk, nil)
|
||||
@@ -545,15 +499,11 @@ func ldbRemoveFromGlobal(db dbReader, batch dbWriter, folder, device, file []byt
|
||||
}
|
||||
|
||||
if len(fl.versions) == 0 {
|
||||
if debugDB {
|
||||
l.Debugf("batch.Delete %p %x", batch, gk)
|
||||
}
|
||||
l.Debugf("batch.Delete %p %x", batch, gk)
|
||||
batch.Delete(gk)
|
||||
} else {
|
||||
if debugDB {
|
||||
l.Debugf("batch.Put %p %x", batch, gk)
|
||||
l.Debugf("new global after remove: %v", fl)
|
||||
}
|
||||
l.Debugf("batch.Put %p %x", batch, gk)
|
||||
l.Debugf("new global after remove: %v", fl)
|
||||
batch.Put(gk, fl.MustMarshalXDR())
|
||||
}
|
||||
}
|
||||
@@ -565,13 +515,9 @@ func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterat
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -598,13 +544,9 @@ func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []b
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -659,19 +601,13 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf,
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, k)
|
||||
}
|
||||
l.Debugf("snap.Get %p %x", snap, k)
|
||||
bs, err := snap.Get(k, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
return nil, false
|
||||
@@ -691,9 +627,7 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf,
|
||||
}
|
||||
|
||||
k = deviceKey(folder, vl.versions[0].device, file)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, k)
|
||||
}
|
||||
l.Debugf("snap.Get %p %x", snap, k)
|
||||
bs, err = snap.Get(k, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -713,13 +647,9 @@ func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iter
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -739,9 +669,7 @@ func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iter
|
||||
}
|
||||
name := globalKeyName(dbi.Key())
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, vl.versions[0].device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err != nil {
|
||||
l.Debugf("folder: %q (%x)", folder, folder)
|
||||
@@ -802,13 +730,9 @@ func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterat
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -854,9 +778,7 @@ nextFile:
|
||||
continue nextFile
|
||||
}
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, vl.versions[i].device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err != nil {
|
||||
var id protocol.DeviceID
|
||||
@@ -886,9 +808,7 @@ nextFile:
|
||||
continue nextFile
|
||||
}
|
||||
|
||||
if debugDB {
|
||||
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v haveV=%d globalV=%d", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveVersion, vl.versions[0].version)
|
||||
}
|
||||
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v haveV=%d globalV=%d", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveVersion, vl.versions[0].version)
|
||||
|
||||
if cont := fn(gf); !cont {
|
||||
return
|
||||
@@ -908,13 +828,9 @@ func ldbListFolders(db *leveldb.DB) []string {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -945,13 +861,9 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -995,13 +907,9 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if debugDB {
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("created snapshot %p", snap)
|
||||
defer func() {
|
||||
if debugDB {
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
}
|
||||
l.Debugf("close snapshot %p", snap)
|
||||
snap.Release()
|
||||
}()
|
||||
|
||||
@@ -1011,9 +919,7 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
defer dbi.Release()
|
||||
|
||||
batch := new(leveldb.Batch)
|
||||
if debugDB {
|
||||
l.Debugf("new batch %p", batch)
|
||||
}
|
||||
l.Debugf("new batch %p", batch)
|
||||
|
||||
var fk []byte
|
||||
for dbi.Next() {
|
||||
@@ -1033,9 +939,7 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
var newVL versionList
|
||||
for _, version := range vl.versions {
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, version.device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
_, err := snap.Get(fk, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
continue
|
||||
@@ -1051,8 +955,6 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
batch.Put(dbi.Key(), newVL.MustMarshalXDR())
|
||||
}
|
||||
}
|
||||
if debugDB {
|
||||
l.Infoln("db check completed for %q", folder)
|
||||
}
|
||||
l.Debugf("db check completed for %q", folder)
|
||||
db.Write(batch, nil)
|
||||
}
|
||||
|
||||
+11
-31
@@ -13,8 +13,8 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
@@ -62,18 +62,14 @@ func NewFileSet(folder string, db *leveldb.DB) *FileSet {
|
||||
}
|
||||
return true
|
||||
})
|
||||
if debug {
|
||||
l.Debugf("loaded localVersion for %q: %#v", folder, s.localVersion)
|
||||
}
|
||||
l.Debugf("loaded localVersion for %q: %#v", folder, s.localVersion)
|
||||
clock(s.localVersion[protocol.LocalDeviceID])
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
if debug {
|
||||
l.Debugf("%s Replace(%v, [%d])", s.folder, device, len(fs))
|
||||
}
|
||||
l.Debugf("%s Replace(%v, [%d])", s.folder, device, len(fs))
|
||||
normalizeFilenames(fs)
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
@@ -89,9 +85,7 @@ func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
}
|
||||
|
||||
func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
if debug {
|
||||
l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
|
||||
}
|
||||
l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
|
||||
normalizeFilenames(fs)
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
@@ -114,51 +108,37 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
}
|
||||
|
||||
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithNeed(%v)", s.folder, device)
|
||||
}
|
||||
l.Debugf("%s WithNeed(%v)", s.folder, device)
|
||||
ldbWithNeed(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
|
||||
}
|
||||
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
|
||||
ldbWithNeed(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithHave(%v)", s.folder, device)
|
||||
}
|
||||
l.Debugf("%s WithHave(%v)", s.folder, device)
|
||||
ldbWithHave(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
|
||||
}
|
||||
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
|
||||
ldbWithHave(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *FileSet) WithGlobal(fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithGlobal()", s.folder)
|
||||
}
|
||||
l.Debugf("%s WithGlobal()", s.folder)
|
||||
ldbWithGlobal(s.db, []byte(s.folder), nil, false, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *FileSet) WithGlobalTruncated(fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithGlobalTruncated()", s.folder)
|
||||
}
|
||||
l.Debugf("%s WithGlobalTruncated()", s.folder)
|
||||
ldbWithGlobal(s.db, []byte(s.folder), nil, true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *FileSet) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithPrefixedGlobalTruncated()", s.folder, prefix)
|
||||
}
|
||||
l.Debugf("%s WithPrefixedGlobalTruncated()", s.folder, prefix)
|
||||
ldbWithGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -13,8 +13,8 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
)
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
package db
|
||||
|
||||
import "github.com/syncthing/protocol"
|
||||
import "github.com/syncthing/syncthing/lib/protocol"
|
||||
|
||||
type FileInfoTruncated struct {
|
||||
protocol.FileInfo
|
||||
|
||||
@@ -34,9 +34,7 @@ func NewVirtualMtimeRepo(ldb *leveldb.DB, folder string) *VirtualMtimeRepo {
|
||||
}
|
||||
|
||||
func (r *VirtualMtimeRepo) UpdateMtime(path string, diskMtime, actualMtime time.Time) {
|
||||
if debug {
|
||||
l.Debugf("virtual mtime: storing values for path:%s disk:%v actual:%v", path, diskMtime, actualMtime)
|
||||
}
|
||||
l.Debugf("virtual mtime: storing values for path:%s disk:%v actual:%v", path, diskMtime, actualMtime)
|
||||
|
||||
diskBytes, _ := diskMtime.MarshalBinary()
|
||||
actualBytes, _ := actualMtime.MarshalBinary()
|
||||
@@ -63,15 +61,11 @@ func (r *VirtualMtimeRepo) GetMtime(path string, diskMtime time.Time) time.Time
|
||||
panic(fmt.Sprintf("Can't unmarshal stored mtime at path %s: %v", path, err))
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("virtual mtime: return %v instead of %v for path: %s", mtime, diskMtime, path)
|
||||
}
|
||||
l.Debugf("virtual mtime: return %v instead of %v for path: %s", mtime, diskMtime, path)
|
||||
return mtime
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("virtual mtime: record exists, but mismatch inDisk: %v dbDisk: %v for path: %s", diskMtime, mtime, path)
|
||||
}
|
||||
l.Debugf("virtual mtime: record exists, but mismatch inDisk: %v dbDisk: %v for path: %s", diskMtime, mtime, path)
|
||||
return diskMtime
|
||||
}
|
||||
|
||||
|
||||
+91
-20
@@ -7,10 +7,11 @@
|
||||
package discover
|
||||
|
||||
import (
|
||||
"sort"
|
||||
stdsync "sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
@@ -33,6 +34,14 @@ type cachedFinder struct {
|
||||
Finder
|
||||
cacheTime time.Duration
|
||||
negCacheTime time.Duration
|
||||
priority int
|
||||
}
|
||||
|
||||
// A prioritizedAddress is what we use to sort addresses returned from
|
||||
// different sources with different priorities.
|
||||
type prioritizedAddress struct {
|
||||
priority int
|
||||
addr string
|
||||
}
|
||||
|
||||
func NewCachingMux() *CachingMux {
|
||||
@@ -43,9 +52,9 @@ func NewCachingMux() *CachingMux {
|
||||
}
|
||||
|
||||
// Add registers a new Finder, with associated cache timeouts.
|
||||
func (m *CachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration) {
|
||||
func (m *CachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration, priority int) {
|
||||
m.mut.Lock()
|
||||
m.finders = append(m.finders, cachedFinder{finder, cacheTime, negCacheTime})
|
||||
m.finders = append(m.finders, cachedFinder{finder, cacheTime, negCacheTime, priority})
|
||||
m.caches = append(m.caches, newCache())
|
||||
m.mut.Unlock()
|
||||
|
||||
@@ -57,6 +66,8 @@ func (m *CachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration) {
|
||||
// Lookup attempts to resolve the device ID using any of the added Finders,
|
||||
// while obeying the cache settings.
|
||||
func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays []Relay, err error) {
|
||||
var pdirect []prioritizedAddress
|
||||
|
||||
m.mut.Lock()
|
||||
for i, finder := range m.finders {
|
||||
if cacheEntry, ok := m.caches[i].Get(deviceID); ok {
|
||||
@@ -64,11 +75,11 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
|
||||
|
||||
if cacheEntry.found && time.Since(cacheEntry.when) < finder.cacheTime {
|
||||
// It's a positive, valid entry. Use it.
|
||||
if debug {
|
||||
l.Debugln("cached discovery entry for", deviceID, "at", finder.String())
|
||||
l.Debugln(" ", cacheEntry)
|
||||
l.Debugln("cached discovery entry for", deviceID, "at", finder)
|
||||
l.Debugln(" cache:", cacheEntry)
|
||||
for _, addr := range cacheEntry.Direct {
|
||||
pdirect = append(pdirect, prioritizedAddress{finder.priority, addr})
|
||||
}
|
||||
direct = append(direct, cacheEntry.Direct...)
|
||||
relays = append(relays, cacheEntry.Relays...)
|
||||
continue
|
||||
}
|
||||
@@ -76,9 +87,7 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
|
||||
if !cacheEntry.found && time.Since(cacheEntry.when) < finder.negCacheTime {
|
||||
// It's a negative, valid entry. We should not make another
|
||||
// attempt right now.
|
||||
if debug {
|
||||
l.Debugln("negative cache entry for", deviceID, "at", finder.String())
|
||||
}
|
||||
l.Debugln("negative cache entry for", deviceID, "at", finder)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -87,12 +96,12 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
|
||||
|
||||
// Perform the actual lookup and cache the result.
|
||||
if td, tr, err := finder.Lookup(deviceID); err == nil {
|
||||
if debug {
|
||||
l.Debugln("lookup for", deviceID, "at", finder.String())
|
||||
l.Debugln(" ", td)
|
||||
l.Debugln(" ", tr)
|
||||
l.Debugln("lookup for", deviceID, "at", finder)
|
||||
l.Debugln(" direct:", td)
|
||||
l.Debugln(" relays:", tr)
|
||||
for _, addr := range td {
|
||||
pdirect = append(pdirect, prioritizedAddress{finder.priority, addr})
|
||||
}
|
||||
direct = append(direct, td...)
|
||||
relays = append(relays, tr...)
|
||||
m.caches[i].Set(deviceID, CacheEntry{
|
||||
Direct: td,
|
||||
@@ -104,11 +113,11 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
|
||||
}
|
||||
m.mut.Unlock()
|
||||
|
||||
if debug {
|
||||
l.Debugln("lookup results for", deviceID)
|
||||
l.Debugln(" ", direct)
|
||||
l.Debugln(" ", relays)
|
||||
}
|
||||
direct = uniqueSortedAddrs(pdirect)
|
||||
relays = uniqueSortedRelays(relays)
|
||||
l.Debugln("lookup results for", deviceID)
|
||||
l.Debugln(" direct: ", direct)
|
||||
l.Debugln(" relays: ", relays)
|
||||
|
||||
return direct, relays, nil
|
||||
}
|
||||
@@ -196,3 +205,65 @@ func (c *cache) Cache() map[protocol.DeviceID]CacheEntry {
|
||||
c.mut.Unlock()
|
||||
return m
|
||||
}
|
||||
|
||||
func uniqueSortedAddrs(ss []prioritizedAddress) []string {
|
||||
// We sort the addresses by priority, then filter them based on seen
|
||||
// (first time seen is the on kept, so we retain priority).
|
||||
sort.Sort(prioritizedAddressList(ss))
|
||||
filtered := make([]string, 0, len(ss))
|
||||
seen := make(map[string]struct{}, len(ss))
|
||||
for _, s := range ss {
|
||||
if _, ok := seen[s.addr]; !ok {
|
||||
filtered = append(filtered, s.addr)
|
||||
seen[s.addr] = struct{}{}
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func uniqueSortedRelays(rs []Relay) []Relay {
|
||||
m := make(map[string]Relay, len(rs))
|
||||
for _, r := range rs {
|
||||
m[r.URL] = r
|
||||
}
|
||||
|
||||
var ur = make([]Relay, 0, len(m))
|
||||
for _, r := range m {
|
||||
ur = append(ur, r)
|
||||
}
|
||||
|
||||
sort.Sort(relayList(ur))
|
||||
|
||||
return ur
|
||||
}
|
||||
|
||||
type relayList []Relay
|
||||
|
||||
func (l relayList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l relayList) Swap(a, b int) {
|
||||
l[a], l[b] = l[b], l[a]
|
||||
}
|
||||
|
||||
func (l relayList) Less(a, b int) bool {
|
||||
return l[a].URL < l[b].URL
|
||||
}
|
||||
|
||||
type prioritizedAddressList []prioritizedAddress
|
||||
|
||||
func (l prioritizedAddressList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l prioritizedAddressList) Swap(a, b int) {
|
||||
l[a], l[b] = l[b], l[a]
|
||||
}
|
||||
|
||||
func (l prioritizedAddressList) Less(a, b int) bool {
|
||||
if l[a].priority != l[b].priority {
|
||||
return l[a].priority < l[b].priority
|
||||
}
|
||||
return l[a].addr < l[b].addr
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
func TestCacheUnique(t *testing.T) {
|
||||
direct0 := []string{"tcp://192.0.2.44:22000", "tcp://192.0.2.42:22000"} // prio 0
|
||||
direct1 := []string{"tcp://192.0.2.43:22000", "tcp://192.0.2.42:22000"} // prio 1
|
||||
|
||||
// what we expect from just direct0
|
||||
direct0Sorted := []string{"tcp://192.0.2.42:22000", "tcp://192.0.2.44:22000"}
|
||||
|
||||
// what we expect from direct0+direct1
|
||||
totalSorted := []string{
|
||||
// first prio 0, sorted
|
||||
"tcp://192.0.2.42:22000", "tcp://192.0.2.44:22000",
|
||||
// then prio 1
|
||||
"tcp://192.0.2.43:22000",
|
||||
// no duplicate .42
|
||||
}
|
||||
|
||||
relays := []Relay{{URL: "relay://192.0.2.44:443"}, {URL: "tcp://192.0.2.45:443"}}
|
||||
|
||||
c := NewCachingMux()
|
||||
c.ServeBackground()
|
||||
defer c.Stop()
|
||||
|
||||
// Add a fake discovery service and verify we get it's answers through the
|
||||
// cache.
|
||||
|
||||
f1 := &fakeDiscovery{direct0, relays}
|
||||
c.Add(f1, time.Minute, 0, 0)
|
||||
|
||||
dir, rel, err := c.Lookup(protocol.LocalDeviceID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(dir, direct0Sorted) {
|
||||
t.Errorf("Incorrect direct; %+v != %+v", dir, direct0Sorted)
|
||||
}
|
||||
if !reflect.DeepEqual(rel, relays) {
|
||||
t.Errorf("Incorrect relays; %+v != %+v", rel, relays)
|
||||
}
|
||||
|
||||
// Add one more that answers in the same way and check that we don't
|
||||
// duplicate or otherwise mess up the responses now.
|
||||
|
||||
f2 := &fakeDiscovery{direct1, relays}
|
||||
c.Add(f2, time.Minute, 0, 1)
|
||||
|
||||
dir, rel, err = c.Lookup(protocol.LocalDeviceID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(dir, totalSorted) {
|
||||
t.Errorf("Incorrect direct; %+v != %+v", dir, totalSorted)
|
||||
}
|
||||
if !reflect.DeepEqual(rel, relays) {
|
||||
t.Errorf("Incorrect relays; %+v != %+v", rel, relays)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDiscovery struct {
|
||||
direct []string
|
||||
relays []Relay
|
||||
}
|
||||
|
||||
func (f *fakeDiscovery) Lookup(deviceID protocol.DeviceID) (direct []string, relays []Relay, err error) {
|
||||
return f.direct, f.relays, nil
|
||||
}
|
||||
|
||||
func (f *fakeDiscovery) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeDiscovery) String() string {
|
||||
return "fake"
|
||||
}
|
||||
|
||||
func (f *fakeDiscovery) Cache() map[protocol.DeviceID]CacheEntry {
|
||||
return nil
|
||||
}
|
||||
@@ -10,10 +10,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "discover") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
l = logger.DefaultLogger.NewFacility("discover", "Remote device discovery")
|
||||
)
|
||||
|
||||
func init() {
|
||||
l.SetDebug("discover", strings.Contains(os.Getenv("STTRACE"), "discover") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package discover
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
|
||||
+10
-28
@@ -18,8 +18,8 @@ import (
|
||||
stdsync "sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
type globalClient struct {
|
||||
@@ -124,16 +124,12 @@ func (c *globalClient) Lookup(device protocol.DeviceID) (direct []string, relays
|
||||
|
||||
resp, err := c.queryClient.Get(qURL.String())
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln("globalClient.Lookup", qURL.String(), err)
|
||||
}
|
||||
l.Debugln("globalClient.Lookup", qURL, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
if debug {
|
||||
l.Debugln("globalClient.Lookup", qURL.String(), resp.Status)
|
||||
}
|
||||
l.Debugln("globalClient.Lookup", qURL, resp.Status)
|
||||
return nil, nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
@@ -198,9 +194,7 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
|
||||
|
||||
if len(ann.Direct)+len(ann.Relays) == 0 {
|
||||
c.setError(errors.New("nothing to announce"))
|
||||
if debug {
|
||||
l.Debugln("Nothing to announce")
|
||||
}
|
||||
l.Debugln("Nothing to announce")
|
||||
timer.Reset(announceErrorRetryInterval)
|
||||
return
|
||||
}
|
||||
@@ -208,37 +202,27 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
|
||||
// The marshal doesn't fail, I promise.
|
||||
postData, _ := json.Marshal(ann)
|
||||
|
||||
if debug {
|
||||
l.Debugf("Announcement: %s", postData)
|
||||
}
|
||||
l.Debugf("Announcement: %s", postData)
|
||||
|
||||
resp, err := c.announceClient.Post(c.server, "application/json", bytes.NewReader(postData))
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln("announce POST:", err)
|
||||
}
|
||||
l.Debugln("announce POST:", err)
|
||||
c.setError(err)
|
||||
timer.Reset(announceErrorRetryInterval)
|
||||
return
|
||||
}
|
||||
if debug {
|
||||
l.Debugln("announce POST:", resp.Status)
|
||||
}
|
||||
l.Debugln("announce POST:", resp.Status)
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
if debug {
|
||||
l.Debugln("announce POST:", resp.Status)
|
||||
}
|
||||
l.Debugln("announce POST:", resp.Status)
|
||||
c.setError(errors.New(resp.Status))
|
||||
|
||||
if h := resp.Header.Get("Retry-After"); h != "" {
|
||||
// The server has a recommendation on when we should
|
||||
// retry. Follow it.
|
||||
if secs, err := strconv.Atoi(h); err == nil && secs > 0 {
|
||||
if debug {
|
||||
l.Debugln("announce Retry-After:", secs, err)
|
||||
}
|
||||
l.Debugln("announce Retry-After:", secs, err)
|
||||
timer.Reset(time.Duration(secs) * time.Second)
|
||||
return
|
||||
}
|
||||
@@ -254,9 +238,7 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
|
||||
// The server has a recommendation on when we should
|
||||
// reannounce. Follow it.
|
||||
if secs, err := strconv.Atoi(h); err == nil && secs > 0 {
|
||||
if debug {
|
||||
l.Debugln("announce Reannounce-After:", secs, err)
|
||||
}
|
||||
l.Debugln("announce Reannounce-After:", secs, err)
|
||||
timer.Reset(time.Duration(secs) * time.Second)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
)
|
||||
|
||||
|
||||
+14
-13
@@ -17,9 +17,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/lib/beacon"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
@@ -117,7 +117,12 @@ func (c *localClient) Error() error {
|
||||
}
|
||||
|
||||
func (c *localClient) announcementPkt() Announce {
|
||||
addrs := c.addrList.AllAddresses()
|
||||
var addrs []Address
|
||||
for _, addr := range c.addrList.AllAddresses() {
|
||||
addrs = append(addrs, Address{
|
||||
URL: addr,
|
||||
})
|
||||
}
|
||||
|
||||
var relays []Relay
|
||||
for _, relay := range c.relayStat.Relays() {
|
||||
@@ -161,15 +166,11 @@ func (c *localClient) recvAnnouncements(b beacon.Interface) {
|
||||
var pkt Announce
|
||||
err := pkt.UnmarshalXDR(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
if debug {
|
||||
l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
|
||||
}
|
||||
l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
|
||||
continue
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.This.ID))
|
||||
}
|
||||
l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.This.ID))
|
||||
|
||||
var newDevice bool
|
||||
if bytes.Compare(pkt.This.ID, c.myID[:]) != 0 {
|
||||
@@ -198,7 +199,7 @@ func (c *localClient) registerDevice(src net.Addr, device Device) bool {
|
||||
|
||||
var validAddresses []string
|
||||
for _, addr := range device.Addresses {
|
||||
u, err := url.Parse(addr)
|
||||
u, err := url.Parse(addr.URL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -213,10 +214,10 @@ func (c *localClient) registerDevice(src net.Addr, device Device) bool {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
u.Host = fmt.Sprintf("%s:%d", host, tcpAddr.Port)
|
||||
u.Host = net.JoinHostPort(host, strconv.Itoa(tcpAddr.Port))
|
||||
validAddresses = append(validAddresses, u.String())
|
||||
} else {
|
||||
validAddresses = append(validAddresses, addr)
|
||||
validAddresses = append(validAddresses, addr.URL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,9 +243,9 @@ func addrToAddr(addr *net.TCPAddr) string {
|
||||
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
|
||||
return fmt.Sprintf(":%c", addr.Port)
|
||||
} else if bs := addr.IP.To4(); bs != nil {
|
||||
return fmt.Sprintf("%s:%c", bs.String(), addr.Port)
|
||||
return net.JoinHostPort(bs.String(), strconv.Itoa(addr.Port))
|
||||
} else if bs := addr.IP.To16(); bs != nil {
|
||||
return fmt.Sprintf("[%s]:%c", bs.String(), addr.Port)
|
||||
return net.JoinHostPort(bs.String(), strconv.Itoa(addr.Port))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -11,27 +11,25 @@ package discover
|
||||
|
||||
const (
|
||||
AnnouncementMagic = 0x9D79BC40
|
||||
QueryMagic = 0x2CA856F6
|
||||
)
|
||||
|
||||
type Query struct {
|
||||
Magic uint32
|
||||
DeviceID []byte // max:32
|
||||
}
|
||||
|
||||
type Announce struct {
|
||||
Magic uint32
|
||||
This Device
|
||||
Extra []Device // max:16
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID []byte // max:32
|
||||
Addresses []Address // max:16
|
||||
Relays []Relay // max:16
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
URL string // max:2083
|
||||
}
|
||||
|
||||
type Relay struct {
|
||||
URL string `json:"url"` // max:2083
|
||||
Latency int32 `json:"latency"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID []byte // max:32
|
||||
Addresses []string // max:16
|
||||
Relays []Relay // max:16
|
||||
}
|
||||
|
||||
+151
-155
@@ -13,80 +13,6 @@ import (
|
||||
|
||||
/*
|
||||
|
||||
Query Structure:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Magic |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Device ID |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Device ID (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Query {
|
||||
unsigned int Magic;
|
||||
opaque DeviceID<32>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Query) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o Query) MarshalXDR() ([]byte, error) {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Query) MustMarshalXDR() []byte {
|
||||
bs, err := o.MarshalXDR()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
func (o Query) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o Query) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteUint32(o.Magic)
|
||||
if l := len(o.DeviceID); l > 32 {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("DeviceID", l, 32)
|
||||
}
|
||||
xw.WriteBytes(o.DeviceID)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Query) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Query) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Query) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.Magic = xr.ReadUint32()
|
||||
o.DeviceID = xr.ReadBytesMax(32)
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Announce Structure:
|
||||
|
||||
0 1 2 3
|
||||
@@ -187,80 +113,6 @@ func (o *Announce) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
|
||||
/*
|
||||
|
||||
Relay Structure:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of URL |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ URL (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Latency |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Relay {
|
||||
string URL<256>;
|
||||
int Latency;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Relay) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o Relay) MarshalXDR() ([]byte, error) {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Relay) MustMarshalXDR() []byte {
|
||||
bs, err := o.MarshalXDR()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
func (o Relay) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o Relay) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
if l := len(o.URL); l > 256 {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("URL", l, 256)
|
||||
}
|
||||
xw.WriteString(o.URL)
|
||||
xw.WriteUint32(uint32(o.Latency))
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Relay) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Relay) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.URL = xr.ReadStringMax(256)
|
||||
o.Latency = int32(xr.ReadUint32())
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Device Structure:
|
||||
|
||||
0 1 2 3
|
||||
@@ -274,10 +126,8 @@ Device Structure:
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Addresses |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Addresses |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Addresses (variable length) \
|
||||
\ Zero or more Address Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Relays |
|
||||
@@ -290,7 +140,7 @@ Device Structure:
|
||||
|
||||
struct Device {
|
||||
opaque ID<32>;
|
||||
string Addresses<16>;
|
||||
Address Addresses<16>;
|
||||
Relay Relays<16>;
|
||||
}
|
||||
|
||||
@@ -330,7 +180,10 @@ func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
}
|
||||
xw.WriteUint32(uint32(len(o.Addresses)))
|
||||
for i := range o.Addresses {
|
||||
xw.WriteString(o.Addresses[i])
|
||||
_, err := o.Addresses[i].EncodeXDRInto(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
}
|
||||
if l := len(o.Relays); l > 16 {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("Relays", l, 16)
|
||||
@@ -365,9 +218,9 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
if _AddressesSize > 16 {
|
||||
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
|
||||
}
|
||||
o.Addresses = make([]string, _AddressesSize)
|
||||
o.Addresses = make([]Address, _AddressesSize)
|
||||
for i := range o.Addresses {
|
||||
o.Addresses[i] = xr.ReadString()
|
||||
(&o.Addresses[i]).DecodeXDRFrom(xr)
|
||||
}
|
||||
_RelaysSize := int(xr.ReadUint32())
|
||||
if _RelaysSize < 0 {
|
||||
@@ -382,3 +235,146 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
}
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Address Structure:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of URL |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ URL (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Address {
|
||||
string URL<2083>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Address) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o Address) MarshalXDR() ([]byte, error) {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Address) MustMarshalXDR() []byte {
|
||||
bs, err := o.MarshalXDR()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
func (o Address) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o Address) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
if l := len(o.URL); l > 2083 {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("URL", l, 2083)
|
||||
}
|
||||
xw.WriteString(o.URL)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Address) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Address) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Address) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.URL = xr.ReadStringMax(2083)
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Relay Structure:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of URL |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ URL (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Latency |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Relay {
|
||||
string URL<2083>;
|
||||
int Latency;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Relay) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o Relay) MarshalXDR() ([]byte, error) {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Relay) MustMarshalXDR() []byte {
|
||||
bs, err := o.MarshalXDR()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
func (o Relay) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o Relay) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
if l := len(o.URL); l > 2083 {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("URL", l, 2083)
|
||||
}
|
||||
xw.WriteString(o.URL)
|
||||
xw.WriteUint32(uint32(o.Latency))
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Relay) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Relay) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.URL = xr.ReadStringMax(2083)
|
||||
o.Latency = int32(xr.ReadUint32())
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
+6
-3
@@ -10,10 +10,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "events") || os.Getenv("STTRACE") == "all"
|
||||
dl = logger.DefaultLogger
|
||||
dl = logger.DefaultLogger.NewFacility("events", "Event generation and logging")
|
||||
)
|
||||
|
||||
func init() {
|
||||
dl.SetDebug("events", strings.Contains(os.Getenv("STTRACE"), "events") || os.Getenv("STTRACE") == "all")
|
||||
}
|
||||
|
||||
+15
-19
@@ -105,7 +105,7 @@ func (t EventType) MarshalText() ([]byte, error) {
|
||||
const BufferSize = 64
|
||||
|
||||
type Logger struct {
|
||||
subs map[int]*Subscription
|
||||
subs []*Subscription
|
||||
nextID int
|
||||
mutex sync.Mutex
|
||||
}
|
||||
@@ -119,7 +119,6 @@ type Event struct {
|
||||
|
||||
type Subscription struct {
|
||||
mask EventType
|
||||
id int
|
||||
events chan Event
|
||||
timeout *time.Timer
|
||||
}
|
||||
@@ -133,23 +132,20 @@ var (
|
||||
|
||||
func NewLogger() *Logger {
|
||||
return &Logger{
|
||||
subs: make(map[int]*Subscription),
|
||||
mutex: sync.NewMutex(),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Log(t EventType, data interface{}) {
|
||||
l.mutex.Lock()
|
||||
if debug {
|
||||
dl.Debugln("log", l.nextID, t.String(), data)
|
||||
}
|
||||
dl.Debugln("log", l.nextID, t, data)
|
||||
l.nextID++
|
||||
e := Event{
|
||||
ID: l.nextID,
|
||||
Time: time.Now(),
|
||||
Type: t,
|
||||
Data: data,
|
||||
}
|
||||
l.nextID++
|
||||
for _, s := range l.subs {
|
||||
if s.mask&t != 0 {
|
||||
select {
|
||||
@@ -164,27 +160,29 @@ func (l *Logger) Log(t EventType, data interface{}) {
|
||||
|
||||
func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
l.mutex.Lock()
|
||||
if debug {
|
||||
dl.Debugln("subscribe", mask)
|
||||
}
|
||||
dl.Debugln("subscribe", mask)
|
||||
s := &Subscription{
|
||||
mask: mask,
|
||||
id: l.nextID,
|
||||
events: make(chan Event, BufferSize),
|
||||
timeout: time.NewTimer(0),
|
||||
}
|
||||
l.nextID++
|
||||
l.subs[s.id] = s
|
||||
l.subs = append(l.subs, s)
|
||||
l.mutex.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
l.mutex.Lock()
|
||||
if debug {
|
||||
dl.Debugln("unsubscribe")
|
||||
dl.Debugln("unsubscribe")
|
||||
for i, ss := range l.subs {
|
||||
if s == ss {
|
||||
last := len(l.subs) - 1
|
||||
l.subs[i] = l.subs[last]
|
||||
l.subs[last] = nil
|
||||
l.subs = l.subs[:last]
|
||||
break
|
||||
}
|
||||
}
|
||||
delete(l.subs, s.id)
|
||||
close(s.events)
|
||||
l.mutex.Unlock()
|
||||
}
|
||||
@@ -193,9 +191,7 @@ func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
// out of the event channel is closed. Poll should not be called concurrently
|
||||
// from multiple goroutines for a single subscription.
|
||||
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
if debug {
|
||||
dl.Debugln("poll", timeout)
|
||||
}
|
||||
dl.Debugln("poll", timeout)
|
||||
|
||||
if !s.timeout.Reset(timeout) {
|
||||
select {
|
||||
|
||||
@@ -134,6 +134,7 @@ func TestIDs(t *testing.T) {
|
||||
s := l.Subscribe(events.AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
l.Log(events.DeviceConnected, "foo")
|
||||
_ = l.Subscribe(events.AllEvents)
|
||||
l.Log(events.DeviceConnected, "bar")
|
||||
|
||||
ev, err := s.Poll(timeout)
|
||||
@@ -152,8 +153,8 @@ func TestIDs(t *testing.T) {
|
||||
if ev.Data.(string) != "bar" {
|
||||
t.Fatal("Incorrect event:", ev)
|
||||
}
|
||||
if !(ev.ID > id) {
|
||||
t.Fatalf("ID not incremented (%d !> %d)", ev.ID, id)
|
||||
if ev.ID != id+1 {
|
||||
t.Fatalf("ID not incremented (%d != %d)", ev.ID, id+1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -217,28 +216,28 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
|
||||
// Pattern is rooted in the current dir only
|
||||
exp, err := fnmatch.Convert(line[1:], flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid pattern %q in ignore file", line)
|
||||
return fmt.Errorf("invalid pattern %q in ignore file", line)
|
||||
}
|
||||
patterns = append(patterns, Pattern{exp, include})
|
||||
} else if strings.HasPrefix(line, "**/") {
|
||||
// Add the pattern as is, and without **/ so it matches in current dir
|
||||
exp, err := fnmatch.Convert(line, flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid pattern %q in ignore file", line)
|
||||
return fmt.Errorf("invalid pattern %q in ignore file", line)
|
||||
}
|
||||
patterns = append(patterns, Pattern{exp, include})
|
||||
|
||||
exp, err = fnmatch.Convert(line[3:], flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid pattern %q in ignore file", line)
|
||||
return fmt.Errorf("invalid pattern %q in ignore file", line)
|
||||
}
|
||||
patterns = append(patterns, Pattern{exp, include})
|
||||
} else if strings.HasPrefix(line, "#include ") {
|
||||
includeFile := filepath.Join(filepath.Dir(currentFile), line[len("#include "):])
|
||||
includeRel := line[len("#include "):]
|
||||
includeFile := filepath.Join(filepath.Dir(currentFile), includeRel)
|
||||
includes, err := loadIgnoreFile(includeFile, seen)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
return fmt.Errorf("include of %q: %v", includeRel, err)
|
||||
}
|
||||
patterns = append(patterns, includes...)
|
||||
} else {
|
||||
@@ -246,13 +245,13 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
|
||||
// current directory and subdirs.
|
||||
exp, err := fnmatch.Convert(line, flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid pattern %q in ignore file", line)
|
||||
return fmt.Errorf("invalid pattern %q in ignore file", line)
|
||||
}
|
||||
patterns = append(patterns, Pattern{exp, include})
|
||||
|
||||
exp, err = fnmatch.Convert("**/"+line, flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid pattern %q in ignore file", line)
|
||||
return fmt.Errorf("invalid pattern %q in ignore file", line)
|
||||
}
|
||||
patterns = append(patterns, Pattern{exp, include})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user