mirror of
https://github.com/syncthing/syncthing.git
synced 2025-12-24 06:28:10 -05:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a54d7a32d1 | ||
|
|
ca823bd591 | ||
|
|
eabd972667 | ||
|
|
395e524e2d | ||
|
|
48b1a2b264 | ||
|
|
58953d799c | ||
|
|
f0f8bf7784 | ||
|
|
bf3834e367 | ||
|
|
8d1eff7e41 | ||
|
|
0cff66fcbc | ||
|
|
d23e8be39f | ||
|
|
43a5be1c4b | ||
|
|
b50039a920 | ||
|
|
d4e81fff8a | ||
|
|
3a557a43cd | ||
|
|
bc53782f88 | ||
|
|
e4ab9d3312 | ||
|
|
e31a116e6e | ||
|
|
24f41e169a | ||
|
|
675f289aef | ||
|
|
e7ae851900 | ||
|
|
73fa7a6e5b | ||
|
|
1a6d023ba8 | ||
|
|
9207535028 | ||
|
|
24967e99a7 | ||
|
|
50d8c43e7c | ||
|
|
90d9b2de2b | ||
|
|
04f05f102d | ||
|
|
289a02e994 | ||
|
|
84fe285659 | ||
|
|
445637ebec | ||
|
|
3f3d2c814b | ||
|
|
189e44488e | ||
|
|
27ff20faa3 | ||
|
|
b1564e53e4 | ||
|
|
3a75b63776 | ||
|
|
8e238c8e48 | ||
|
|
3d5af675db | ||
|
|
9da3273eb8 | ||
|
|
bd37f6da17 | ||
|
|
6940d79f5b | ||
|
|
0f80318ef6 | ||
|
|
d6622b1f68 | ||
|
|
43bcb3d5a5 | ||
|
|
e2e8f6e940 | ||
|
|
88b0ce892d | ||
|
|
55cd4b3d9b | ||
|
|
f24676ba5a | ||
|
|
722b3fce6a | ||
|
|
8a05492622 | ||
|
|
63c4e7f6d0 | ||
|
|
fafd30f804 | ||
|
|
ad5a046843 | ||
|
|
4b8853bfde | ||
|
|
648fcf2c45 | ||
|
|
ca3ae64bbf | ||
|
|
e2204d0071 | ||
|
|
d5ff2c41dc | ||
|
|
7a40c42e8b | ||
|
|
905c3594b0 | ||
|
|
5fd333e4f7 | ||
|
|
225c0dda80 | ||
|
|
5fd2cab102 |
5
.codecov.yml
Normal file
5
.codecov.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
coverage:
|
||||
range: "40...100"
|
||||
|
||||
ignore:
|
||||
- "**.pb.go"
|
||||
7
AUTHORS
7
AUTHORS
@@ -70,11 +70,13 @@ Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
||||
Elliot Huffman <thelich2@gmail.com>
|
||||
Emil Hessman (ceh) <emil@hessman.se>
|
||||
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
|
||||
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
||||
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
||||
Felix Ableitner (Nutomic) <me@nutomic.com>
|
||||
Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
|
||||
Francois-Xavier Gsell (zukoo) <fxgsell@gmail.com>
|
||||
Frank Isemann (fti7) <frank@isemann.name>
|
||||
georgespatton <georgespatton@users.noreply.github.com>
|
||||
Gilli Sigurdsson (gillisig) <gilli@vx.is>
|
||||
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
||||
Han Boetes <han@boetes.org>
|
||||
@@ -83,7 +85,7 @@ Heiko Zuerker (Smiley73) <heiko@zuerker.org>
|
||||
Hugo Locurcio <hugo.locurcio@hugo.pro>
|
||||
Iain Barnett <iainspeed@gmail.com>
|
||||
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
|
||||
Iskander (Alex) Sharipov <quasilyte@gmail.com>
|
||||
Iskander Sharipov (Alex) <quasilyte@gmail.com>
|
||||
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
|
||||
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
|
||||
Jake Peterson (acogdev) <jake@acogdev.com>
|
||||
@@ -99,6 +101,7 @@ Jochen Voss (seehuhn) <voss@seehuhn.de>
|
||||
Johan Andersson <j@i19.se>
|
||||
Johan Vromans (sciurius) <jvromans@squirrel.nl>
|
||||
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
||||
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
||||
Jonathan Cross <jcross@gmail.com>
|
||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||
@@ -126,6 +129,7 @@ Mark Pulford (mpx) <mark@kyne.com.au>
|
||||
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
||||
Matic Potočnik <hairyfotr@gmail.com>
|
||||
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matteo Ruina <matteo.ruina@gmail.com>
|
||||
Maurizio Tomasi <ziotom78@gmail.com>
|
||||
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
||||
@@ -143,6 +147,7 @@ Nicolas Braud-Santoni <nicolas@braud-santoni.eu>
|
||||
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
|
||||
Nils Jakobi (thunderstorm99) <jakobi.nils@gmail.com>
|
||||
NoLooseEnds <jon.koslung@gmail.com>
|
||||
otbutz <tbutz@optitool.de>
|
||||
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
|
||||
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.11 AS builder
|
||||
FROM golang:1.12 AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
# Docker Container for Syncthing
|
||||
|
||||
Use the Dockerfile in this repo, or pull the `syncthing/syncthing` image
|
||||
from Docker Hub. Use volumes to have the synchronized files available on the
|
||||
host.
|
||||
from Docker Hub.
|
||||
|
||||
The exposed volumes are by default:
|
||||
|
||||
/var/syncthing/config - the configuration and index directory into the Container
|
||||
/var/syncthing - the default sync folder into the Container
|
||||
|
||||
You can add more folders and map them as you prefer.
|
||||
Use the `/var/syncthing` volume to have the synchronized files available on the
|
||||
host. You can add more folders and map them as you prefer.
|
||||
|
||||
Note that Syncthing runs as UID 1000 and GID 1000 by default. These may be
|
||||
altered with the ``PUID`` and ``PGID`` environment variables.
|
||||
|
||||
Example usage:
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
$ docker pull syncthing/syncthing
|
||||
$ docker run -p 8384:8384 -p 22000:22000 \
|
||||
-v /wherever/st-cfg:/var/syncthing/config \
|
||||
-v /wherever/st-sync:/var/syncthing \
|
||||
syncthing/syncthing:latest
|
||||
```
|
||||
|
||||
Note that local device discovery will not work with the above command resulting
|
||||
in poor local transfer rates if local device addresses are not manually
|
||||
configured.
|
||||
Note that local device discovery will not work with the above command, resulting in poor local transfer rates if local device addresses are not manually configured.
|
||||
|
||||
To allow local discovery, the docker host network can be used instead:
|
||||
|
||||
```
|
||||
$ docker pull syncthing/syncthing
|
||||
$ docker run --network=host \
|
||||
-v /wherever/st-cfg:/var/syncthing/config \
|
||||
-v /wherever/st-sync:/var/syncthing \
|
||||
syncthing/syncthing:latest
|
||||
```
|
||||
|
||||
22
build.go
22
build.go
@@ -48,6 +48,7 @@ var (
|
||||
pkgdir string
|
||||
cc string
|
||||
debugBinary bool
|
||||
coverage bool
|
||||
timeout = "120s"
|
||||
gogoProtoVersion = "v1.2.0"
|
||||
)
|
||||
@@ -330,24 +331,27 @@ func parseFlags() {
|
||||
flag.StringVar(&pkgdir, "pkgdir", "", "Set -pkgdir parameter for `go build`")
|
||||
flag.StringVar(&cc, "cc", os.Getenv("CC"), "Set CC environment variable for `go build`")
|
||||
flag.BoolVar(&debugBinary, "debug-binary", debugBinary, "Create unoptimized binary to use with delve, set -gcflags='-N -l' and omit -ldflags")
|
||||
flag.BoolVar(&coverage, "coverage", coverage, "Write coverage profile of tests to coverage.txt")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func test(pkgs ...string) {
|
||||
lazyRebuildAssets()
|
||||
|
||||
useRace := runtime.GOARCH == "amd64"
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "linux", "freebsd": // , "windows": # See https://github.com/golang/go/issues/27089
|
||||
default:
|
||||
useRace = false
|
||||
args := []string{"test", "-short", "-timeout", timeout, "-tags", "purego"}
|
||||
|
||||
if runtime.GOARCH == "amd64" {
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "linux", "freebsd": // , "windows": # See https://github.com/golang/go/issues/27089
|
||||
args = append(args, "-race")
|
||||
}
|
||||
}
|
||||
|
||||
if useRace {
|
||||
runPrint(goCmd, append([]string{"test", "-short", "-race", "-timeout", timeout, "-tags", "purego"}, pkgs...)...)
|
||||
} else {
|
||||
runPrint(goCmd, append([]string{"test", "-short", "-timeout", timeout, "-tags", "purego"}, pkgs...)...)
|
||||
if coverage {
|
||||
args = append(args, "-covermode", "atomic", "-coverprofile", "coverage.txt")
|
||||
}
|
||||
|
||||
runPrint(goCmd, append(args, pkgs...)...)
|
||||
}
|
||||
|
||||
func bench(pkgs ...string) {
|
||||
|
||||
@@ -14,15 +14,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("main", "Main package")
|
||||
httpl = logger.DefaultLogger.NewFacility("http", "REST API")
|
||||
l = logger.DefaultLogger.NewFacility("main", "Main package")
|
||||
)
|
||||
|
||||
func shouldDebugHTTP() bool {
|
||||
return l.ShouldDebug("http")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/api"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/connections"
|
||||
@@ -47,7 +47,9 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/sha256"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
"github.com/syncthing/syncthing/lib/ur"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
@@ -62,16 +64,12 @@ const (
|
||||
const (
|
||||
bepProtocolName = "bep/1.0"
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
defaultEventTimeout = time.Minute
|
||||
maxSystemErrors = 5
|
||||
initialSystemLog = 10
|
||||
maxSystemLog = 250
|
||||
)
|
||||
|
||||
var (
|
||||
myID protocol.DeviceID
|
||||
stop = make(chan int)
|
||||
)
|
||||
var myID protocol.DeviceID
|
||||
|
||||
const (
|
||||
usage = "syncthing [options]"
|
||||
@@ -166,34 +164,35 @@ var (
|
||||
)
|
||||
|
||||
type RuntimeOptions struct {
|
||||
confDir string
|
||||
resetDatabase bool
|
||||
resetDeltaIdxs bool
|
||||
showVersion bool
|
||||
showPaths bool
|
||||
showDeviceId bool
|
||||
doUpgrade bool
|
||||
doUpgradeCheck bool
|
||||
upgradeTo string
|
||||
noBrowser bool
|
||||
browserOnly bool
|
||||
hideConsole bool
|
||||
logFile string
|
||||
auditEnabled bool
|
||||
auditFile string
|
||||
verbose bool
|
||||
paused bool
|
||||
unpaused bool
|
||||
guiAddress string
|
||||
guiAPIKey string
|
||||
generateDir string
|
||||
noRestart bool
|
||||
profiler string
|
||||
assetDir string
|
||||
cpuProfile bool
|
||||
stRestarting bool
|
||||
logFlags int
|
||||
showHelp bool
|
||||
confDir string
|
||||
resetDatabase bool
|
||||
resetDeltaIdxs bool
|
||||
showVersion bool
|
||||
showPaths bool
|
||||
showDeviceId bool
|
||||
doUpgrade bool
|
||||
doUpgradeCheck bool
|
||||
upgradeTo string
|
||||
noBrowser bool
|
||||
browserOnly bool
|
||||
hideConsole bool
|
||||
logFile string
|
||||
auditEnabled bool
|
||||
auditFile string
|
||||
verbose bool
|
||||
paused bool
|
||||
unpaused bool
|
||||
guiAddress string
|
||||
guiAPIKey string
|
||||
generateDir string
|
||||
noRestart bool
|
||||
profiler string
|
||||
assetDir string
|
||||
cpuProfile bool
|
||||
stRestarting bool
|
||||
logFlags int
|
||||
showHelp bool
|
||||
allowNewerConfig bool
|
||||
}
|
||||
|
||||
func defaultRuntimeOptions() RuntimeOptions {
|
||||
@@ -247,6 +246,7 @@ func parseCommandLineOptions() RuntimeOptions {
|
||||
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
|
||||
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout). Cannot be used together with -no-restart/STNORESTART environment variable.")
|
||||
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
|
||||
flag.BoolVar(&options.allowNewerConfig, "allow-newer-config", false, "Allow loading newer than current config version")
|
||||
if runtime.GOOS == "windows" {
|
||||
// Allow user to hide the console window
|
||||
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
|
||||
@@ -264,6 +264,33 @@ func parseCommandLineOptions() RuntimeOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// exiter implements api.Controller
|
||||
type exiter struct {
|
||||
stop chan int
|
||||
}
|
||||
|
||||
func (e *exiter) Restart() {
|
||||
l.Infoln("Restarting")
|
||||
e.stop <- exitRestarting
|
||||
}
|
||||
|
||||
func (e *exiter) Shutdown() {
|
||||
l.Infoln("Shutting down")
|
||||
e.stop <- exitSuccess
|
||||
}
|
||||
|
||||
func (e *exiter) ExitUpgrading() {
|
||||
l.Infoln("Shutting down after upgrade")
|
||||
e.stop <- exitUpgrading
|
||||
}
|
||||
|
||||
// waitForExit must be called synchronously.
|
||||
func (e *exiter) waitForExit() int {
|
||||
return <-e.stop
|
||||
}
|
||||
|
||||
var exit = &exiter{make(chan int)}
|
||||
|
||||
func main() {
|
||||
options := parseCommandLineOptions()
|
||||
l.SetFlags(options.logFlags)
|
||||
@@ -282,7 +309,8 @@ func main() {
|
||||
// to complain if they set -logfile explicitly, not if it's set to its
|
||||
// default location
|
||||
if options.noRestart && (options.logFile != "" && options.logFile != "-") {
|
||||
l.Fatalln("-logfile may not be used with -no-restart or STNORESTART")
|
||||
l.Warnln("-logfile may not be used with -no-restart or STNORESTART")
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if options.hideConsole {
|
||||
@@ -295,11 +323,13 @@ func main() {
|
||||
var err error
|
||||
options.confDir, err = filepath.Abs(options.confDir)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
l.Warnln("Failed to make options path absolute:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
}
|
||||
if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil {
|
||||
l.Fatalln(err)
|
||||
l.Warnln(err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +366,8 @@ func main() {
|
||||
locations.Get(locations.KeyFile),
|
||||
)
|
||||
if err != nil {
|
||||
l.Fatalln("Error reading device ID:", err)
|
||||
l.Warnln("Error reading device ID:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
myID = protocol.NewDeviceID(cert.Certificate[0])
|
||||
@@ -345,22 +376,32 @@ func main() {
|
||||
}
|
||||
|
||||
if options.browserOnly {
|
||||
openGUI()
|
||||
if err := openGUI(); err != nil {
|
||||
l.Warnln("Failed to open web UI:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if options.generateDir != "" {
|
||||
generate(options.generateDir)
|
||||
if err := generate(options.generateDir); err != nil {
|
||||
l.Warnln("Failed to generate config and keys:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure that our home directory exists.
|
||||
ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700)
|
||||
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
|
||||
l.Warnln("Failure on home directory:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if options.upgradeTo != "" {
|
||||
err := upgrade.ToURL(options.upgradeTo)
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err) // exits 1
|
||||
l.Warnln("Error while Upgrading:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
l.Infoln("Upgraded from", options.upgradeTo)
|
||||
return
|
||||
@@ -379,7 +420,8 @@ func main() {
|
||||
|
||||
if options.resetDatabase {
|
||||
if err := resetDB(); err != nil {
|
||||
l.Fatalln("Resetting database:", err)
|
||||
l.Warnln("Resetting database:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -391,23 +433,30 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func openGUI() {
|
||||
cfg, _ := loadOrDefaultConfig()
|
||||
func openGUI() error {
|
||||
cfg, err := loadOrDefaultConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.GUI().Enabled {
|
||||
if err := openURL(cfg.GUI().URL()); err != nil {
|
||||
l.Fatalln("Open URL:", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
l.Warnln("Browser: GUI is currently disabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate(generateDir string) {
|
||||
func generate(generateDir string) error {
|
||||
dir, err := fs.ExpandTilde(generateDir)
|
||||
if err != nil {
|
||||
l.Fatalln("generate:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureDir(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
ensureDir(dir, 0700)
|
||||
|
||||
certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
@@ -417,11 +466,11 @@ func generate(generateDir string) {
|
||||
} else {
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName)
|
||||
if err != nil {
|
||||
l.Fatalln("Create certificate:", err)
|
||||
return errors.Wrap(err, "create certificate")
|
||||
}
|
||||
myID = protocol.NewDeviceID(cert.Certificate[0])
|
||||
if err != nil {
|
||||
l.Fatalln("Load certificate:", err)
|
||||
return errors.Wrap(err, "load certificate")
|
||||
}
|
||||
if err == nil {
|
||||
l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0]))
|
||||
@@ -431,13 +480,17 @@ func generate(generateDir string) {
|
||||
cfgFile := filepath.Join(dir, "config.xml")
|
||||
if _, err := os.Stat(cfgFile); err == nil {
|
||||
l.Warnln("Config exists; will not overwrite.")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
cfg, err := defaultConfig(cfgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cfg = defaultConfig(cfgFile)
|
||||
err = cfg.Save()
|
||||
if err != nil {
|
||||
l.Warnln("Failed to save config", err)
|
||||
return errors.Wrap(err, "save config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func debugFacilities() string {
|
||||
@@ -467,7 +520,8 @@ func checkUpgrade() upgrade.Release {
|
||||
opts := cfg.Options()
|
||||
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err)
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
|
||||
@@ -486,14 +540,16 @@ func performUpgrade(release upgrade.Release) {
|
||||
if err == nil {
|
||||
err = upgrade.To(release)
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err)
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
l.Infof("Upgraded to %q", release.Tag)
|
||||
} else {
|
||||
l.Infoln("Attempting upgrade through running Syncthing...")
|
||||
err = upgradeViaRest()
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err)
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
l.Infoln("Syncthing upgrading")
|
||||
os.Exit(exitUpgrading)
|
||||
@@ -567,8 +623,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
// Event subscription for the API; must start early to catch the early
|
||||
// events. The LocalChangeDetected event might overwhelm the event
|
||||
// receiver in some situations so we will not subscribe to it here.
|
||||
defaultSub := events.NewBufferedSubscription(events.Default.Subscribe(defaultEventMask), eventSubBufferSize)
|
||||
diskSub := events.NewBufferedSubscription(events.Default.Subscribe(diskEventMask), eventSubBufferSize)
|
||||
defaultSub := events.NewBufferedSubscription(events.Default.Subscribe(api.DefaultEventMask), api.EventSubBufferSize)
|
||||
diskSub := events.NewBufferedSubscription(events.Default.Subscribe(api.DiskEventMask), api.EventSubBufferSize)
|
||||
|
||||
if len(os.Getenv("GOMAXPROCS")) == 0 {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
@@ -592,7 +648,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
tlsDefaultCommonName,
|
||||
)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
l.Infoln("Failed to generate certificate:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,10 +671,15 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
"myID": myID.String(),
|
||||
})
|
||||
|
||||
cfg := loadConfigAtStartup()
|
||||
cfg, err := loadConfigAtStartup(runtimeOptions.allowNewerConfig)
|
||||
if err != nil {
|
||||
l.Warnln("Failed to initialize config:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if err := checkShortIDs(cfg); err != nil {
|
||||
l.Fatalln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one of the following:\n ", err)
|
||||
l.Warnln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one of the following:\n ", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if len(runtimeOptions.profiler) > 0 {
|
||||
@@ -626,21 +688,24 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
err := http.ListenAndServe(runtimeOptions.profiler, nil)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
l.Warnln(err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
perf := cpuBench(3, 150*time.Millisecond, true)
|
||||
perf := ur.CpuBench(3, 150*time.Millisecond, true)
|
||||
l.Infof("Hashing performance is %.02f MB/s", perf)
|
||||
|
||||
dbFile := locations.Get(locations.Database)
|
||||
ldb, err := db.Open(dbFile)
|
||||
if err != nil {
|
||||
l.Fatalln("Error opening database:", err)
|
||||
l.Warnln("Error opening database:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
if err := db.UpdateSchema(ldb); err != nil {
|
||||
l.Fatalln("Database schema:", err)
|
||||
l.Warnln("Database schema:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if runtimeOptions.resetDeltaIdxs {
|
||||
@@ -769,25 +834,15 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(mainService, cfg, m, defaultSub, diskSub, cachedDiscovery, connectionsService, errors, systemLog, runtimeOptions)
|
||||
|
||||
if runtimeOptions.cpuProfile {
|
||||
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
|
||||
if err != nil {
|
||||
l.Fatalln("Creating profile:", err)
|
||||
l.Warnln("Creating profile:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
l.Fatalln("Starting profile:", err)
|
||||
}
|
||||
}
|
||||
|
||||
myDev, _ := cfg.Device(myID)
|
||||
l.Infof(`My name is "%v"`, myDev.Name)
|
||||
for _, device := range cfg.Devices() {
|
||||
if device.DeviceID != myID {
|
||||
l.Infof(`Device %s is "%v" at %v`, device.DeviceID, device.Name, device.Addresses)
|
||||
l.Warnln("Starting profile:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,8 +850,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
|
||||
if opts := cfg.Options(); build.IsCandidate {
|
||||
l.Infoln("Anonymous usage reporting is always enabled for candidate releases.")
|
||||
if opts.URAccepted != usageReportVersion {
|
||||
opts.URAccepted = usageReportVersion
|
||||
if opts.URAccepted != ur.Version {
|
||||
opts.URAccepted = ur.Version
|
||||
cfg.SetOptions(opts)
|
||||
cfg.Save()
|
||||
// Unique ID will be set and config saved below if necessary.
|
||||
@@ -810,9 +865,21 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
cfg.Save()
|
||||
}
|
||||
|
||||
usageReportingSvc := newUsageReportingService(cfg, m, connectionsService)
|
||||
usageReportingSvc := ur.New(cfg, m, connectionsService, noUpgradeFromEnv)
|
||||
mainService.Add(usageReportingSvc)
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(mainService, cfg, m, defaultSub, diskSub, cachedDiscovery, connectionsService, usageReportingSvc, errors, systemLog, runtimeOptions)
|
||||
|
||||
myDev, _ := cfg.Device(myID)
|
||||
l.Infof(`My name is "%v"`, myDev.Name)
|
||||
for _, device := range cfg.Devices() {
|
||||
if device.DeviceID != myID {
|
||||
l.Infof(`Device %s is "%v" at %v`, device.DeviceID, device.Name, device.Addresses)
|
||||
}
|
||||
}
|
||||
|
||||
if opts := cfg.Options(); opts.RestartOnWakeup {
|
||||
go standbyMonitor()
|
||||
}
|
||||
@@ -858,7 +925,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
code := <-stop
|
||||
code := exit.waitForExit()
|
||||
|
||||
mainService.Stop()
|
||||
|
||||
@@ -879,7 +946,7 @@ func setupSignalHandling() {
|
||||
signal.Notify(restartSign, sigHup)
|
||||
go func() {
|
||||
<-restartSign
|
||||
stop <- exitRestarting
|
||||
exit.Restart()
|
||||
}()
|
||||
|
||||
// Exit with "success" code (no restart) on INT/TERM
|
||||
@@ -889,45 +956,57 @@ func setupSignalHandling() {
|
||||
signal.Notify(stopSign, os.Interrupt, sigTerm)
|
||||
go func() {
|
||||
<-stopSign
|
||||
stop <- exitSuccess
|
||||
exit.Shutdown()
|
||||
}()
|
||||
}
|
||||
|
||||
func loadOrDefaultConfig() (*config.Wrapper, error) {
|
||||
func loadOrDefaultConfig() (config.Wrapper, error) {
|
||||
cfgFile := locations.Get(locations.ConfigFile)
|
||||
cfg, err := config.Load(cfgFile, myID)
|
||||
|
||||
if err != nil {
|
||||
cfg = defaultConfig(cfgFile)
|
||||
cfg, err = defaultConfig(cfgFile)
|
||||
}
|
||||
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func loadConfigAtStartup() *config.Wrapper {
|
||||
func loadConfigAtStartup(allowNewerConfig bool) (config.Wrapper, error) {
|
||||
cfgFile := locations.Get(locations.ConfigFile)
|
||||
cfg, err := config.Load(cfgFile, myID)
|
||||
if os.IsNotExist(err) {
|
||||
cfg = defaultConfig(cfgFile)
|
||||
cfg.Save()
|
||||
l.Infof("Default config saved. Edit %s to taste or use the GUI\n", cfg.ConfigPath())
|
||||
cfg, err = defaultConfig(cfgFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate default config")
|
||||
}
|
||||
err = cfg.Save()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to save default config")
|
||||
}
|
||||
l.Infof("Default config saved. Edit %s to taste (with Syncthing stopped) or use the GUI", cfg.ConfigPath())
|
||||
} else if err == io.EOF {
|
||||
l.Fatalln("Failed to load config: unexpected end of file. Truncated or empty configuration?")
|
||||
return nil, errors.New("Failed to load config: unexpected end of file. Truncated or empty configuration?")
|
||||
} else if err != nil {
|
||||
l.Fatalln("Failed to load config:", err)
|
||||
return nil, errors.Wrap(err, "failed to load config")
|
||||
}
|
||||
|
||||
if cfg.RawCopy().OriginalVersion != config.CurrentVersion {
|
||||
if cfg.RawCopy().OriginalVersion == config.CurrentVersion+1101 {
|
||||
l.Infof("Now, THAT's what we call a config from the future! Don't worry. As long as you hit that wire with the connecting hook at precisely eighty-eight miles per hour the instant the lightning strikes the tower... everything will be fine.")
|
||||
}
|
||||
if cfg.RawCopy().OriginalVersion > config.CurrentVersion && !allowNewerConfig {
|
||||
return nil, fmt.Errorf("Config file version (%d) is newer than supported version (%d). If this is expected, use -allow-newer-config to override.", cfg.RawCopy().OriginalVersion, config.CurrentVersion)
|
||||
}
|
||||
err = archiveAndSaveConfig(cfg)
|
||||
if err != nil {
|
||||
l.Fatalln("Config archive:", err)
|
||||
return nil, errors.Wrap(err, "config archive")
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func archiveAndSaveConfig(cfg *config.Wrapper) error {
|
||||
func archiveAndSaveConfig(cfg config.Wrapper) error {
|
||||
// Copy the existing config to an archive copy
|
||||
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion)
|
||||
l.Infoln("Archiving a copy of old config file format at:", archivePath)
|
||||
@@ -976,7 +1055,8 @@ func startAuditing(mainService *suture.Supervisor, auditFile string) {
|
||||
}
|
||||
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
|
||||
if err != nil {
|
||||
l.Fatalln("Audit:", err)
|
||||
l.Warnln("Audit:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
auditDest = auditFile
|
||||
}
|
||||
@@ -991,7 +1071,7 @@ func startAuditing(mainService *suture.Supervisor, auditFile string) {
|
||||
l.Infoln("Audit log in", auditDest)
|
||||
}
|
||||
|
||||
func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) {
|
||||
func setupGUI(mainService *suture.Supervisor, cfg config.Wrapper, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, urService *ur.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) {
|
||||
guiCfg := cfg.GUI()
|
||||
|
||||
if !guiCfg.Enabled {
|
||||
@@ -1005,50 +1085,49 @@ func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Mode
|
||||
cpu := newCPUService()
|
||||
mainService.Add(cpu)
|
||||
|
||||
api := newAPIService(myID, cfg, locations.Get(locations.HTTPSCertFile), locations.Get(locations.HTTPSKeyFile), runtimeOptions.assetDir, m, defaultSub, diskSub, discoverer, connectionsService, errors, systemLog, cpu)
|
||||
cfg.Subscribe(api)
|
||||
mainService.Add(api)
|
||||
summaryService := model.NewFolderSummaryService(cfg, m, myID)
|
||||
mainService.Add(summaryService)
|
||||
|
||||
apiSvc := api.New(myID, cfg, runtimeOptions.assetDir, tlsDefaultCommonName, m, defaultSub, diskSub, discoverer, connectionsService, urService, summaryService, errors, systemLog, cpu, exit, noUpgradeFromEnv)
|
||||
mainService.Add(apiSvc)
|
||||
|
||||
if err := apiSvc.WaitForStart(); err != nil {
|
||||
l.Warnln("Failed starting API:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
|
||||
if cfg.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
|
||||
// Can potentially block if the utility we are invoking doesn't
|
||||
// fork, and just execs, hence keep it in its own routine.
|
||||
<-api.startedOnce
|
||||
go func() { _ = openURL(guiCfg.URL()) }()
|
||||
}
|
||||
}
|
||||
|
||||
func defaultConfig(cfgFile string) *config.Wrapper {
|
||||
newCfg := config.NewWithFreePorts(myID)
|
||||
func defaultConfig(cfgFile string) (config.Wrapper, error) {
|
||||
newCfg, err := config.NewWithFreePorts(myID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if noDefaultFolder {
|
||||
l.Infoln("We will skip creation of a default folder on first start since the proper envvar is set")
|
||||
return config.Wrap(cfgFile, newCfg)
|
||||
return config.Wrap(cfgFile, newCfg), nil
|
||||
}
|
||||
|
||||
newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
|
||||
l.Infoln("Default folder created and/or linked to new config")
|
||||
return config.Wrap(cfgFile, newCfg)
|
||||
return config.Wrap(cfgFile, newCfg), nil
|
||||
}
|
||||
|
||||
func resetDB() error {
|
||||
return os.RemoveAll(locations.Get(locations.Database))
|
||||
}
|
||||
|
||||
func restart() {
|
||||
l.Infoln("Restarting")
|
||||
stop <- exitRestarting
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
l.Infoln("Shutting down")
|
||||
stop <- exitSuccess
|
||||
}
|
||||
|
||||
func ensureDir(dir string, mode fs.FileMode) {
|
||||
func ensureDir(dir string, mode fs.FileMode) error {
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
err := fs.MkdirAll(".", mode)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if fi, err := fs.Stat("."); err == nil {
|
||||
@@ -1064,6 +1143,7 @@ func ensureDir(dir string, mode fs.FileMode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func standbyMonitor() {
|
||||
@@ -1079,14 +1159,14 @@ func standbyMonitor() {
|
||||
// things a moment to stabilize.
|
||||
time.Sleep(restartDelay)
|
||||
|
||||
restart()
|
||||
exit.Restart()
|
||||
return
|
||||
}
|
||||
now = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func autoUpgrade(cfg *config.Wrapper) {
|
||||
func autoUpgrade(cfg config.Wrapper) {
|
||||
timer := time.NewTimer(0)
|
||||
sub := events.Default.Subscribe(events.DeviceConnected)
|
||||
for {
|
||||
@@ -1137,7 +1217,7 @@ func autoUpgrade(cfg *config.Wrapper) {
|
||||
events.Default.Unsubscribe(sub)
|
||||
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
|
||||
time.Sleep(time.Minute)
|
||||
stop <- exitUpgrading
|
||||
exit.ExitUpgrading()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1188,7 +1268,7 @@ func cleanConfigDirectory() {
|
||||
// checkShortIDs verifies that the configuration won't result in duplicate
|
||||
// short ID:s; that is, that the devices in the cluster all have unique
|
||||
// initial 64 bits.
|
||||
func checkShortIDs(cfg *config.Wrapper) error {
|
||||
func checkShortIDs(cfg config.Wrapper) error {
|
||||
exists := make(map[protocol.ShortID]protocol.DeviceID)
|
||||
for deviceID := range cfg.Devices() {
|
||||
shortID := deviceID.Short()
|
||||
@@ -1210,7 +1290,7 @@ func showPaths(options RuntimeOptions) {
|
||||
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
|
||||
}
|
||||
|
||||
func setPauseState(cfg *config.Wrapper, paused bool) {
|
||||
func setPauseState(cfg config.Wrapper, paused bool) {
|
||||
raw := cfg.RawCopy()
|
||||
for i := range raw.Devices {
|
||||
raw.Devices[i].Paused = paused
|
||||
@@ -1219,6 +1299,7 @@ func setPauseState(cfg *config.Wrapper, paused bool) {
|
||||
raw.Folders[i].Paused = paused
|
||||
}
|
||||
if _, err := cfg.Replace(raw); err != nil {
|
||||
l.Fatalln("Cannot adjust paused state:", err)
|
||||
l.Warnln("Cannot adjust paused state:", err)
|
||||
os.Exit(exitError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ func monitorMain(runtimeOptions RuntimeOptions) {
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
l.Fatalln("stderr:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
l.Fatalln("stdout:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
l.Infoln("Starting syncthing")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stdoutMut.Lock()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Name=Syncthing Web UI
|
||||
GenericName=File synchronization UI
|
||||
Comment="Opens Syncthing's Web UI in the default browser (Syncthing must already be started)."
|
||||
Comment=Opens Syncthing's Web UI in the default browser (Syncthing must already be started).
|
||||
Exec=/usr/bin/syncthing -browser-only
|
||||
Icon=syncthing
|
||||
Terminal=false
|
||||
|
||||
3
go.mod
3
go.mod
@@ -36,9 +36,8 @@ require (
|
||||
github.com/thejerf/suture v3.0.2+incompatible
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
||||
golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 // indirect
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -85,6 +85,8 @@ github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gN
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
|
||||
golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d h1:GrqEEc3+MtHKTsZrdIGVoYDgLpbSRzW1EF+nLu0PcHE=
|
||||
golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
@@ -92,6 +94,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984 h1:ulYJn/BqO4fMRe1xAQzWjokgjsQLPpb21GltxXHI3fQ=
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b h1:3X+R0qq1+64izd8es+EttB6qcY+JDlVmAhpRXl7gpzU=
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Копиран от оригинала",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Всички правата запазени © 2014-2016 Сътрудници:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Всички правата запазени © 2014-2017. Сътрудници:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Създаване на шаблони за игнориране, презаписване на съществуващ файл в {{path}}.",
|
||||
"Danger!": "Опасност!",
|
||||
"Debugging Facilities": "Дебъг функционалност",
|
||||
@@ -228,7 +229,7 @@
|
||||
"Reduced by ignore patterns": "Намалено посредством шаблон за игнориране",
|
||||
"Release Notes": "Бележки по обновяването",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Кандидат версиите съдържат най-новата функционалност и поправки. Те са близки до традиционните дву-седмични Synchthing обновления.",
|
||||
"Remote Devices": "Чужди устройства",
|
||||
"Remote Devices": "Устройства",
|
||||
"Remove": "Премахни",
|
||||
"Remove Device": "Премахване на устройство",
|
||||
"Remove Folder": "Премахване на папка",
|
||||
@@ -300,7 +301,7 @@
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing има проблем при обработването на заявката. Моля, презаредете браузъра или рестартирайте Syncthing ако проблемът продължи.",
|
||||
"Take me back": "Take me back",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Администраторския панел на Syncthing е разрешава дистанционен достъп без да изисква парола.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Администраторският панел на Syncthing разрешава дистанционен достъп без да изисква парола.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Обобщение на събраните статистически ще намерите на долния URL адрес.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурацията е запазена, но не е активирана. Syncthing трябва да рестартира, за да се активира новата конфигурация.",
|
||||
"The device ID cannot be blank.": "Полето идентификатор на устройство не може да бъде празно.",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Работи от",
|
||||
"Usage reporting is always enabled for candidate releases.": "Докладът за ползването е винаги включен за кандидат нови версии.",
|
||||
"Use HTTPS for GUI": "Използвай HTTPS за потребителския интерфейс",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Версия",
|
||||
"Versions": "Версии",
|
||||
"Versions Path": "Път до версиите",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Предупреждение: Ако използвате външна програма за наблюдение като {{syncthingInotify}}, трябва да я деактивирате.",
|
||||
"Watch for Changes": "Мониторинг за промени",
|
||||
"Watching for Changes": "Мониторинг за промени",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Когато добавяте ново устройство имайте предвид, че това устройство също трябва да бъде добавено от другата страна.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когато добавяте нов идентификатор на папка имайте предвид, че той се използва за свързване на папките между отделните устройства. Идентификатора разграничава главни/малки букви.",
|
||||
"Yes": "Да",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 els següents Col·laboradors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 els següents Col·laboradors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creant patrons a ignorar, sobreescriguent un fitxer que ja existeix a {{path}}.",
|
||||
"Danger!": "Perill!",
|
||||
"Debugging Facilities": "Utilitats de Depuració",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Temps de funcionament",
|
||||
"Usage reporting is always enabled for candidate releases.": "Els informes d'ús sempre estan activats per a les versions candidates.",
|
||||
"Use HTTPS for GUI": "Utilitzar HTTPS per a l'Interfície Gràfica d'Usuari (GUI)",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Versió",
|
||||
"Versions": "Versions",
|
||||
"Versions Path": "Ruta de les versions",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "AVÍS: Si estàs utilitzant un observador extern com {{syncthingInotify}}, deus assegurar-te de que està desactivat.",
|
||||
"Watch for Changes": "Vigilar els Canvis",
|
||||
"Watching for Changes": "Vigilant els Canvis",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quant s'afig un nou dispositiu, hi ha que tindre en compte que aquest dispositiu deu ser afegit també en l'altre costat.",
|
||||
"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í",
|
||||
|
||||
@@ -51,11 +51,12 @@
|
||||
"Connection Error": "Chyba připojení",
|
||||
"Connection Type": "Typ připojení",
|
||||
"Connections": "Připojení",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Syncthing nyní umožňuje nepřetržité sledování změn. To zachytí změny na disku na spustí sken pouze pro změněné cesty. Výhody jsou rychlejší propagace změn a méně plných skenů.",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Syncthing nyní umožňuje nepřetržité sledování změn. To zachytí změny na disku a spustí sken pouze pro změněné cesty. Výhody jsou rychlejší propagace změn a méně plných skenů.",
|
||||
"Copied from elsewhere": "Zkopírováno odjinud",
|
||||
"Copied from original": "Zkopírováno z originálu",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 následující přispěvatelé:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 následující přispěvatelé:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Vytváření ignorovaných vzorů, přepisování existujícího souboru v {{path}}.",
|
||||
"Danger!": "Pozor!",
|
||||
"Debugging Facilities": "Nástroje pro ladění",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Celkový čas běhu",
|
||||
"Usage reporting is always enabled for candidate releases.": "Hlášení o používání je pro kandidáty na vydání vždy zapnuto.",
|
||||
"Use HTTPS for GUI": "Použít HTTPS pro grafické rozhraní",
|
||||
"Use notifications from the filesystem to detect changed items.": "Použít oznamování soubor. systému pro nalezení změněných položek.",
|
||||
"Variable Size Blocks": "Bloky variabilní velikosti",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Bloky variabilní velikosti (také \"velké bloky\") jsou účinnější pro velké soubory.",
|
||||
"Version": "Verze",
|
||||
"Versions": "Verze",
|
||||
"Versions Path": "Cesta k verzím",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Pozor: Pokud používáte externí sledování změn jako {{syncthingInotify}}, měly byste se ujistit, že je toto sledování vypnuto.",
|
||||
"Watch for Changes": "Sledovat změny",
|
||||
"Watching for Changes": "Sledování změn",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Sledování změn odhalí většinu změn bez periodického skenování.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového zařízení mějte na paměti, že je ho třeba také zadat na druhé straně.",
|
||||
"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říč zařízeními. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi zařízeními.",
|
||||
"Yes": "Ano",
|
||||
|
||||
@@ -56,12 +56,13 @@
|
||||
"Copied from original": "Kopieret fra originalen",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 de følgende bidragsydere:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 de følgende bidragsydere:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 de følgende bidragsydere:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Opretter ignoreringsmønstre; overskriver en eksisterende fil på {{path}}.",
|
||||
"Danger!": "Fare!",
|
||||
"Debugging Facilities": "Faciliteter til fejlretning",
|
||||
"Default Folder Path": "Standardmappesti",
|
||||
"Deleted": "Slettet",
|
||||
"Deselect All": "Deselect All",
|
||||
"Deselect All": "Fravælg alle",
|
||||
"Device": "Enhed",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enheden “{{name}}” ({{device}} på {{address}}) vil gerne forbinde. Tilføj denne enhed?",
|
||||
"Device ID": "Enheds-ID",
|
||||
@@ -74,11 +75,11 @@
|
||||
"Disabled periodic scanning and disabled watching for changes": "Deaktiverede periodisk skanning og deaktiverede overvågning af ændringer",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Deaktiverede periodisk skanning og aktiverede overvågning af ændringer",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Deaktiverede periodisk skanning fra og lykkedes ikke med at opsætte overvågning af ændringer; prøver igen hvert minut:",
|
||||
"Discard": "Discard",
|
||||
"Discard": "Behold ikke",
|
||||
"Disconnected": "Ikke tilsluttet",
|
||||
"Discovered": "Opdaget",
|
||||
"Discovery": "Opslag",
|
||||
"Discovery Failures": "Opdagelses Fejl ",
|
||||
"Discovery Failures": "Fejl ved opdagelse",
|
||||
"Do not restore": "Genskab ikke",
|
||||
"Do not restore all": "Genskab ikke alle",
|
||||
"Do you want to enable watching for changes for all your folders?": "Vil du aktivere løbende overvågning af ændringer for alle dine mapper?",
|
||||
@@ -97,7 +98,7 @@
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Indtast et ikke-negativt tal (fx “2,35”) og vælg en enhed. Procentsatser er ud fra total diskstørrelse.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Indtast et ikke-priviligeret portnummer (1024–65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Angiv kommaseparerede adresser (“tcp://ip:port”, “tcp://host:port”) eller “dynamic” for at benytte automatisk opdagelse af adressen.",
|
||||
"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.": "Angiv en kommaadskilt adresseliste (\"tcp://ip:port\", \"tcp://host:port\") eller \"dynamic\" for automatisk at opdage adressen.",
|
||||
"Enter ignore patterns, one per line.": "Indtast ignoreringsmønstre, ét per linje.",
|
||||
"Error": "Fejl",
|
||||
"External File Versioning": "Ekstern filversionering",
|
||||
@@ -143,9 +144,9 @@
|
||||
"Ignore": "Ignorér",
|
||||
"Ignore Patterns": "Ignoreringsmønstre",
|
||||
"Ignore Permissions": "Ignorér rettigheder",
|
||||
"Ignored Devices": "Ignored Devices",
|
||||
"Ignored Folders": "Ignored Folders",
|
||||
"Ignored at": "Ignored at",
|
||||
"Ignored Devices": "Ignorerede enheder",
|
||||
"Ignored Folders": "Ignorerede mapper",
|
||||
"Ignored at": "Ignoreret på",
|
||||
"Incoming Rate Limit (KiB/s)": "Indgående hastighedsbegrænsning (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Ukorrekt opsætning kan skade dine data og gøre Syncthing ude af stand til at fungere.",
|
||||
"Introduced By": "Introduceret af",
|
||||
@@ -159,14 +160,14 @@
|
||||
"Later": "Senere",
|
||||
"Latest Change": "Seneste ændring",
|
||||
"Learn more": "Lær mere",
|
||||
"Limit": "Limit",
|
||||
"Limit": "Grænse",
|
||||
"Listeners": "Lyttere",
|
||||
"Loading data...": "Indlæser data…",
|
||||
"Loading...": "Indlæser…",
|
||||
"Loading data...": "Indlæser data ...",
|
||||
"Loading...": "Indlæser ...",
|
||||
"Local Discovery": "Lokal opslag",
|
||||
"Local State": "Lokal tilstand",
|
||||
"Local State (Total)": "Lokal tilstand (total)",
|
||||
"Locally Changed Items": "Locally Changed Items",
|
||||
"Locally Changed Items": "Lokalt ændrede filer",
|
||||
"Log": "Logbog",
|
||||
"Log tailing paused. Click here to continue.": "Logfølgning på pause. Klik her for at fortsætte.",
|
||||
"Log tailing paused. Scroll to bottom continue.": "Logfølgning på pause. Rul til bunden for at fortsætte.",
|
||||
@@ -208,7 +209,7 @@
|
||||
"Pause": "Pause",
|
||||
"Pause All": "Sæt alt på pause",
|
||||
"Paused": "På pause",
|
||||
"Pending changes": "Pending changes",
|
||||
"Pending changes": "Ventende ændringer",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Periodisk skanning med et givent interval og deaktiveret overvågning af ændringer",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Periodisk skanning med et givent interval og aktiveret overvågning af ændringer",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning med et givent interval og lykkedes ikke med at opsætte overvågning af ændringer; prøver igen hvert minut:",
|
||||
@@ -252,7 +253,7 @@
|
||||
"Scanning": "Skanner",
|
||||
"See external versioner help for supported templated command line parameters.": "Se hjælp til ekstern versionering for understøttede kommandolinjeparametre.",
|
||||
"See external versioning help for supported templated command line parameters.": "Se hjælp til ekstern versionering for understøttede kommandolinjeparametre.",
|
||||
"Select All": "Select All",
|
||||
"Select All": "Vælg alle",
|
||||
"Select a version": "Vælg en version",
|
||||
"Select latest version": "Vælg seneste version",
|
||||
"Select oldest version": "Vælg ældste version",
|
||||
@@ -289,7 +290,7 @@
|
||||
"Statistics": "Statistikker",
|
||||
"Stopped": "Stoppet",
|
||||
"Support": "Støt",
|
||||
"Support Bundle": "Support Bundle",
|
||||
"Support Bundle": "Støttepakke",
|
||||
"Sync Protocol Listen Addresses": "Lytteadresser for synkroniseringsprotokol",
|
||||
"Syncing": "Synkroniserer",
|
||||
"Syncthing has been shut down.": "Syncthing er lukket ned.",
|
||||
@@ -298,7 +299,7 @@
|
||||
"Syncthing is upgrading.": "Syncthing opgraderer.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ud til at være stoppet eller oplever problemer med din internetforbindelse. Prøver igen…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Det ser ud til, at Syncthing har problemer med at udføre opgaven. Prøv at genindlæse siden eller genstarte Synching, hvis problemet vedbliver.",
|
||||
"Take me back": "Take me back",
|
||||
"Take me back": "Tag mig tilbage",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing-administationsfladen er sat op til at kunne fjernstyres uden adgangskode.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Den indsamlede statistik er offentligt tilgængelig på den nedenstående URL.",
|
||||
@@ -313,7 +314,7 @@
|
||||
"The folder path cannot be blank.": "Mappestien må ikke være tom.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De følgende intervaller er brugt: Inden for den første time bliver en version gemt hvert 30. sekund, inden for den første dag bliver en version gemt hver time, inden for de første 30 dage bliver en version gemt hver dag, og indtil den maksimale alder bliver en version gemt hver uge.",
|
||||
"The following items could not be synchronized.": "Følgende filer kunne ikke synkroniseres.",
|
||||
"The following items were changed locally.": "The following items were changed locally.",
|
||||
"The following items were changed locally.": "De følgende filer er ændret lokalt.",
|
||||
"The maximum age must be a number and cannot be blank.": "Maksimal alder skal være et tal og feltet må ikke være tomt.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den maksimale tid, en version skal gemmes (i dage; sæt lig med 0 for at beholde gamle versioner for altid).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Procentsatsen for mindst ledig diskplads skal være et ikke-negativt tal mellem 0 og 100 (inklusive).",
|
||||
@@ -336,40 +337,44 @@
|
||||
"Unavailable": "Ikke tilgængelig",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Ikke tilgængelig / deaktiveret af administrator eller vedligeholder",
|
||||
"Undecided (will prompt)": "Ubestemt (du bliver spurgt)",
|
||||
"Unignore": "Unignore",
|
||||
"Unignore": "Fjern ignorering",
|
||||
"Unknown": "Ukendt",
|
||||
"Unshared": "Ikke delt",
|
||||
"Unused": "Ubrugt",
|
||||
"Up to Date": "Fuldt opdateret",
|
||||
"Updated": "Opdateret",
|
||||
"Upgrade": "Upgradér",
|
||||
"Upgrade": "Opgradér",
|
||||
"Upgrade To {%version%}": "Opgradér til {{version}}",
|
||||
"Upgrading": "Opgraderer",
|
||||
"Upload Rate": "Uploadhastighed",
|
||||
"Uptime": "Oppetid",
|
||||
"Usage reporting is always enabled for candidate releases.": "Forbrugsraportering er altid aktiveret for udgivelseskandidater.",
|
||||
"Use HTTPS for GUI": "Anvend HTTPS til GUI-adgang",
|
||||
"Use notifications from the filesystem to detect changed items.": "Benyt notifikationer fra filsystemet til at finde filændringer.",
|
||||
"Variable Size Blocks": "Skiftende blokstørrelse",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Skiftende blokstørrelse (også \"store blokke\") er mere effektivt for større filer.",
|
||||
"Version": "Version",
|
||||
"Versions": "Versioner",
|
||||
"Versions Path": "Versionssti",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner slettes automatisk, hvis de er ældre end den givne maksimum alder eller overstiger det tilladte antal filer i et interval.",
|
||||
"Waiting to scan": "Waiting to scan",
|
||||
"Waiting to scan": "Venter på at skanne",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Advarsel: Denne sti er en forældermappe til den eksisterende mappe “{{otherFolder}}”.",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel: Denne sti er en forældermappe til den eksisterende mappe “{{otherFolderLabel}}” ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel: Denne sti er en undermappe til den eksisterende mappe “{{otherFolder}}”.",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel: Denne sti er en undermappe til den eksisterende mappe “{{otherFolderLabel}}” ({{otherFolder}}).",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advarsel: Hvis du bruger en ekstern overvågning så som {{syncthingInotify}}, bør du være sikker på, at den er deaktiveret.",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advarsel: Hvis du bruger en ekstern overvågning såsom {{syncthingInotify}}, bør du være sikker på, at den er deaktiveret.",
|
||||
"Watch for Changes": "Overvåg ændringer",
|
||||
"Watching for Changes": "Overvågning af ændringer",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Overvågning af ændringer finder ændringer uden at skanne fra tid til anden.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når der tilføjes en ny enhed, vær da opmærksom på, at denne enhed også skal tilføjes i den anden ende.",
|
||||
"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 der tilføjes en ny enhed, vær da opmærksom på at samme mappe-ID bruges til at forbinde mapper på de forskellige enheder. Der er forskel på store og små bogstaver, og ID skal være fuldstændig identisk på alle enheder.",
|
||||
"Yes": "Ja",
|
||||
"You can also select one of these nearby devices:": "Du kan også vælge en af disse enheder i nærheden:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Du kan altid ændre dit valg under indstillinger.",
|
||||
"You can read more about the two release channels at the link below.": "Du kan læse mere om de to udgivelseskanaler på linket herunder.",
|
||||
"You have no ignored devices.": "You have no ignored devices.",
|
||||
"You have no ignored folders.": "You have no ignored folders.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "You have unsaved changes. Do you really want to discard them?",
|
||||
"You have no ignored devices.": "Du har ingen ignorerede enheder.",
|
||||
"You have no ignored folders.": "Du har ingen ignorerede mapper.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Du har ændringer, som ikke er gemt. Er du sikker på, at du ikke vil beholde dem?",
|
||||
"You must keep at least one version.": "Du skal beholde mindst én version.",
|
||||
"days": "dage",
|
||||
"directories": "mapper",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Vom Original kopiert",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 der folgenden Unterstützer:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 der folgenden Unterstützer:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Erstelle Ignoriermuster, welche die existierende Datei {{path}} überschreiben.",
|
||||
"Danger!": "Achtung!",
|
||||
"Debugging Facilities": "Debugging-Möglichkeiten",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Betriebszeit",
|
||||
"Usage reporting is always enabled for candidate releases.": "Nutzungsbericht ist für Veröffentlichungskandidaten immer aktiviert.",
|
||||
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche verwenden",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Version",
|
||||
"Versions": "Versionen",
|
||||
"Versions Path": "Versionierungspfad",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Achtung: Wenn Sie einen externen Beobachter wie {{syncthingInotify}} benutzen, sollten sie sicher sein das dieser deaktiviert ist.",
|
||||
"Watch for Changes": "Auf Änderungen achten",
|
||||
"Watching for Changes": "Auf Änderungen achten",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "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 Ordners, dass die Ordnerkennung dazu verwendet wird, Ordner zwischen Geräten zu verbinden. Die Kennung muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"Yes": "Ja",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 για τους παρακάτω συνεισφέροντες:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 για τους παρακάτω συνεισφέροντες:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Δημιουργία προτύπων αγνόησης, αντικατάσταση του υπάρχοντος αρχείου στο {{path}}.",
|
||||
"Danger!": "Προσοχή!",
|
||||
"Debugging Facilities": "Εργαλεία αποσφαλμάτωσης",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Χρόνος απρόσκοπτης λειτουργίας",
|
||||
"Usage reporting is always enabled for candidate releases.": "Η αποστολή αναφορών χρήσης είναι πάντα ενεργοποιημένη στις υποψήφιες εκδόσεις.",
|
||||
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Έκδοση",
|
||||
"Versions": "Εκδόσεις",
|
||||
"Versions Path": "Φάκελος τήρησης εκδόσεων",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Προσοχή: αν χρησιμοποιείτε ένα εξωτερικό εργαλείο επιτήρησης, όπως το {{syncthingInotify}}, σιγουρευτείτε ότι έχει απενεργοποιηθεί.",
|
||||
"Watch for Changes": "Επιτήρηση αλλαγών",
|
||||
"Watching for Changes": "Εκτελείται η επιτήρηση αλλαγών",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Θυμήσου πως όταν προσθέτεις μια νέα συσκευή, ετούτη η συσκευή θα πρέπει να προστεθεί και στην άλλη πλευρά.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
|
||||
"Yes": "Ναι",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
|
||||
"Danger!": "Danger!",
|
||||
"Debugging Facilities": "Debugging Facilities",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Uptime",
|
||||
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
|
||||
"Use HTTPS for GUI": "Use HTTPS for GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Version",
|
||||
"Versions": "Versions",
|
||||
"Versions Path": "Versions Path",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
|
||||
"Watch for Changes": "Watch for Changes",
|
||||
"Watching for Changes": "Watching for Changes",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "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",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
|
||||
"Danger!": "Danger!",
|
||||
"Debugging Facilities": "Debugging Facilities",
|
||||
@@ -294,6 +295,7 @@
|
||||
"Syncing": "Syncing",
|
||||
"Syncthing has been shut down.": "Syncthing has been shut down.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing is Free and Open Source Software licensed as MPL v2.0.",
|
||||
"Syncthing is restarting.": "Syncthing is restarting.",
|
||||
"Syncthing is upgrading.": "Syncthing is upgrading.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
|
||||
|
||||
386
gui/default/assets/lang/lang-eo.json
Normal file
386
gui/default/assets/lang/lang-eo.json
Normal file
@@ -0,0 +1,386 @@
|
||||
{
|
||||
"A device with that ID is already added.": "Aparato kun samtia ID estis jam aldonita.",
|
||||
"A negative number of days doesn't make sense.": "Negativa numero de tagoj ne havas sencon.",
|
||||
"A new major version may not be compatible with previous versions.": "Nova ĉefa versio eble ne kongruanta kun antaŭaj versioj.",
|
||||
"API Key": "API Ŝlosilo",
|
||||
"About": "Pri",
|
||||
"Action": "Ago",
|
||||
"Actions": "Agoj",
|
||||
"Add": "Aldoni",
|
||||
"Add Device": "Aldoni Aparaton",
|
||||
"Add Folder": "Aldoni Dosierujon",
|
||||
"Add Remote Device": "Aldoni Foran Aparaton",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Aldoni aparatojn de la enkondukanto ĝis nia aparatlisto, por reciproke komunigitaj dosierujoj.",
|
||||
"Add new folder?": "Aldoni novan dosierujon?",
|
||||
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Aldone, plena reskana intervalo estos pliigita (60-oble, t.e. nova defaŭlto estas 1h). Vi povas ankaŭ agordi ĝin permane por ĉiu dosierujo poste post elekto de Ne.",
|
||||
"Address": "Adreso",
|
||||
"Addresses": "Adresoj",
|
||||
"Advanced": "Altnivela",
|
||||
"Advanced Configuration": "Altnivela Agordo",
|
||||
"Advanced settings": "Altnivelaj agordoj",
|
||||
"All Data": "Ĉiuj Datumoj",
|
||||
"Allow Anonymous Usage Reporting?": "Permesi Anoniman Raporton de Uzado?",
|
||||
"Allowed Networks": "Permesitaj Retoj",
|
||||
"Alphabetic": "Alfabeta",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la komunigita dosierujo.",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la komunigita dosierujo. Se la vojo al la apliko elhavas blankoj, ĝi devas esti inter citiloj.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la sinkronigita dosierujo.",
|
||||
"Anonymous Usage Reporting": "Anonima Raporto de Uzado",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formato de anonima raporto de uzado ŝanĝis. Ĉu vi ŝatus transiri al la nova formato?",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Ajnaj aparatoj agorditaj sur enkondukanta aparato estos ankaŭ aldonita al ĉi tiu aparato.",
|
||||
"Are you sure you want to remove device {%name%}?": "Ĉu vi certas, ke vi volas forigi aparaton {{name}}?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Ĉu vi certas, ke vi volas forigi dosierujon {{label}}?",
|
||||
"Are you sure you want to restore {%count%} files?": "Ĉu vi certas, ke vi volas restarigi {{count}} dosierojn?",
|
||||
"Auto Accept": "Akcepti Aŭtomate",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Aŭtomata ĝisdatigo nun proponas la elekton inter stabilaj eldonoj kaj kandidataj eldonoj.",
|
||||
"Automatic upgrades": "Aŭtomataj ĝisdatigoj",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Aŭtomataj ĝisdatigoj ĉiam ŝaltitaj por kandidataj eldonoj.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Aŭtomate krei aŭ komunigi dosierujojn, kiujn ĉi tiu aparato anoncas, ĉe la defaŭlta vojo.",
|
||||
"Available debug logging facilities:": "Disponeblaj elpurigadaj protokoliloj:",
|
||||
"Be careful!": "Atentu!",
|
||||
"Bugs": "Cimoj",
|
||||
"CPU Utilization": "Ĉefprocesoro Uzo",
|
||||
"Changelog": "Ŝanĝoprotokolo",
|
||||
"Clean out after": "Purigi poste",
|
||||
"Click to see discovery failures": "Alklaku por vidi malsukcesajn malkovrojn",
|
||||
"Close": "Fermi",
|
||||
"Command": "Komando",
|
||||
"Comment, when used at the start of a line": "Komento, kiam uzita ĉe la komenco de lineo",
|
||||
"Compression": "Densigo",
|
||||
"Configured": "Agordita",
|
||||
"Connection Error": "Eraro de Konekto",
|
||||
"Connection Type": "Tipo de Konekto",
|
||||
"Connections": "Konektoj",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Daŭra rigardado je ŝanĝoj estas nun havebla ene Syncthing. Ĉi tio detektos ŝangoj sur disko kaj skanos nur modifitajn vojojn. La avantaĝo estas en pli rapifa propagiĝo de ŝanĝoj kaj bezono je malpli plenaj skanoj.",
|
||||
"Copied from elsewhere": "Kopiita el aliloke",
|
||||
"Copied from original": "Kopiita el la originalo",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Kopirajto © 2014-2016 por la sekvantaj Kontribuantoj:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Kopirajto © 2014-2017 por la sekvantaj Kontribuantoj:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Kopirajto © 2014-2019 por la sekvantaj Kontribuantoj:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Kreante ignorantajn ŝablonojn, anstataŭige ekzistantan dosieron ĉe {{path}}.",
|
||||
"Danger!": "Danĝero!",
|
||||
"Debugging Facilities": "Elpurigadiloj",
|
||||
"Default Folder Path": "Defaŭlta Dosieruja Vojo",
|
||||
"Deleted": "Forigita",
|
||||
"Deselect All": "Malelekti Ĉiujn",
|
||||
"Device": "Aparato",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Aparato \"{{name}}\" ({{device}} ĉe {{address}}) volas konekti. Aldoni la novan aparaton?",
|
||||
"Device ID": "Aparato ID",
|
||||
"Device Identification": "Identigo de Aparato",
|
||||
"Device Name": "Nomo de Aparato",
|
||||
"Device rate limits": "Limoj de rapideco de aparato",
|
||||
"Device that last modified the item": "Aparato kiu laste modifis la eron",
|
||||
"Devices": "Aparatoj",
|
||||
"Disabled": "Malebligita",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Malebligita perioda skanado kaj malebligita rigardado je ŝanĝoj",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Malebligita perioda skanado kaj ebligita rigardado je ŝanĝoj",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Malebligita perioda skanado kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
|
||||
"Discard": "Forĵeti",
|
||||
"Disconnected": "Malkonektita",
|
||||
"Discovered": "Malkovrita",
|
||||
"Discovery": "Malkovro",
|
||||
"Discovery Failures": "Malsukcesoj de Malkovro",
|
||||
"Do not restore": "Ne restarigu",
|
||||
"Do not restore all": "Ne restarigu ĉion",
|
||||
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi rigardado je ŝanĝoj por ĉiuj viaj dosierujoj?",
|
||||
"Documentation": "Dokumentado",
|
||||
"Download Rate": "Elŝutrapideco",
|
||||
"Downloaded": "Elŝutita",
|
||||
"Downloading": "Elŝutado",
|
||||
"Edit": "Redakti",
|
||||
"Edit Device": "Redakti Aparaton",
|
||||
"Edit Folder": "Redakti Dosierujon",
|
||||
"Editing": "Redaktado",
|
||||
"Editing {%path%}.": "Redaktado de {{path}}.",
|
||||
"Enable NAT traversal": "Ŝaltu trairan NAT",
|
||||
"Enable Relaying": "Ŝaltu Relajsadon",
|
||||
"Enabled": "Ebligita",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enigu ne negativan nombron (ekz. \"2.35\") kaj elektu uniton. Procentoj estas kiel parto de tuta grandeco de disko.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enigu ne privilegiitan numeron de pordo (1024- 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigu adresojn dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\") aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigu adresojn dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\") aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
|
||||
"Enter ignore patterns, one per line.": "Enigu ignorantajn ŝablonojn, unu po linio.",
|
||||
"Error": "Eraro",
|
||||
"External File Versioning": "Ekstera Versionado de Dosiero",
|
||||
"Failed Items": "Malsukcesaj Eroj",
|
||||
"Failed to load ignore patterns": "Malsukcesis ŝarĝi ignorantajn ŝablonojn",
|
||||
"Failed to setup, retrying": "Malsukcesis agordi, provante denove",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Malsukceso por konekti al IPv6 serviloj atendante se ekzistas neniu IPv6 konektebleco.",
|
||||
"File Pull Order": "Ordo por Tiri Dosieron",
|
||||
"File Versioning": "Versionado de Dosieroj",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Permesoj bitaj de dosieroj estas ignorita dum la serĉado por ŝanĝoj. Uzi en FAT dosiersistemoj.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al .stversions dosierujo kiam anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al .stversions dosierujo kiam anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al date stampitaj versioj en .stversions dosierujo kiam ili estas anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al datostampitaj versioj en .stversions dosierujo kiam ili estas anstataŭigitaj aŭ forigitaj en 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.": "Dosieroj estas protektataj kontraŭ ŝanĝoj faritaj en aliaj aparatoj, sed ŝanĝoj faritaj en ĉi tiu aparato estos senditaj al cetera parto de la grupo.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Dosieroj estas sinkronigitaj de la grupo, sed ajnaj ŝanĝoj faritaj loke ne estis senditaj al aliaj aparatoj.",
|
||||
"Filesystem Notifications": "Dosiersistemaj Sciigoj",
|
||||
"Filesystem Watcher Errors": "Eraroj de Rigardanto de Dosiersistemo",
|
||||
"Filter by date": "Filtri per daton",
|
||||
"Filter by name": "Filtri per nomon",
|
||||
"Folder": "Dosierujo",
|
||||
"Folder ID": "Dosieruja ID",
|
||||
"Folder Label": "Dosieruja Etikedo",
|
||||
"Folder Path": "Dosieruja Vojo",
|
||||
"Folder Type": "Dosieruja Tipo",
|
||||
"Folders": "Dosierujoj",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Por la sekvantaj dosierujoj eraro okazis dum komencado de rigardado je ŝanĝoj. Provante denove ĉiuminute, do eraroj eble foriros baldaŭ. Se ili persistas, provu ripari subkuŝantan problemon kaj petu helpon, se vi ne povas.",
|
||||
"Full Rescan Interval (s)": "Plena Reskana Intervalo (s)",
|
||||
"GUI": "Grafika Interfaco",
|
||||
"GUI Authentication Password": "Pasvorta Aŭtentigo en Grafika Interfaco",
|
||||
"GUI Authentication User": "Uzanta Aŭtentigo en Grafika Interfaco",
|
||||
"GUI Listen Address": "Adreso de Aŭskultado en Grafika Interfaco",
|
||||
"GUI Listen Addresses": "Adresoj de Aŭskultado en Grafika Interfaco",
|
||||
"GUI Theme": "Etoso de Grafika Interfaco",
|
||||
"General": "Ĝenerala",
|
||||
"Generate": "Generi",
|
||||
"Global Changes": "Mallokaj Ŝanĝoj",
|
||||
"Global Discovery": "Malloka Malkovro",
|
||||
"Global Discovery Servers": "Serviloj de Malloka Malkovro",
|
||||
"Global State": "Malloka Stato",
|
||||
"Help": "Helpo",
|
||||
"Home page": "Hejma paĝo",
|
||||
"Ignore": "Ignoru",
|
||||
"Ignore Patterns": "Ignorantaj Ŝablonoj",
|
||||
"Ignore Permissions": "Ignori Permesojn",
|
||||
"Ignored Devices": "Ignoritaj Aparatoj",
|
||||
"Ignored Folders": "Ignoritaj Dosierujoj",
|
||||
"Ignored at": "Ignorita ĉe",
|
||||
"Incoming Rate Limit (KiB/s)": "Alvenanta Rapideco Limo (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Erara agordo povas difekti viajn dosierujajn enhavojn kaj senefikigi Syncthing-n.",
|
||||
"Introduced By": "Enkondukita Per",
|
||||
"Introducer": "Enkondukanto",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversigo de la donita kondiĉo (t.e. ne ekskludi)",
|
||||
"Keep Versions": "Konservi Versiojn",
|
||||
"Largest First": "Plej Granda Unue",
|
||||
"Last File Received": "Lasta Dosiero Ricevita",
|
||||
"Last Scan": "Lasta Skano",
|
||||
"Last seen": "Lasta vidita",
|
||||
"Later": "Poste",
|
||||
"Latest Change": "Lasta Ŝanĝo",
|
||||
"Learn more": "Lerni pli",
|
||||
"Limit": "Limo",
|
||||
"Listeners": "Aŭskultantoj",
|
||||
"Loading data...": "Ŝarĝas datumojn...",
|
||||
"Loading...": "Ŝarĝas...",
|
||||
"Local Discovery": "Loka Malkovro",
|
||||
"Local State": "Loka Stato",
|
||||
"Local State (Total)": "Loka Stato (Tuta)",
|
||||
"Locally Changed Items": "Loke Ŝanĝitaj Eroj",
|
||||
"Log": "Protokolo",
|
||||
"Log tailing paused. Click here to continue.": "Vostado de protokolo paŭzis. Alklaku ĉi tie por daŭrigi.",
|
||||
"Log tailing paused. Scroll to bottom continue.": "Vostado de protokolo paŭzis. Rulumu malsupre por daŭrigi.",
|
||||
"Logs": "Protokoloj",
|
||||
"Major Upgrade": "Ĉefa Ĝisdatigo",
|
||||
"Mass actions": "Amasa agoj",
|
||||
"Master": "Ĉefa",
|
||||
"Maximum Age": "Maksimuma Aĝo",
|
||||
"Metadata Only": "Nur Metadatumoj",
|
||||
"Minimum Free Disk Space": "Minimuma Libera Diskospaco",
|
||||
"Mod. Device": "Mod. Aparato",
|
||||
"Mod. Time": "Mod. Tempo",
|
||||
"Move to top of queue": "Movi al la supro de atendovico",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multnivela ĵokero (egalas multoblajn dosierujaj niveloj)",
|
||||
"Never": "Neniam",
|
||||
"New Device": "Nova Aparato",
|
||||
"New Folder": "Nova Dosierujo",
|
||||
"Newest First": "Plejnova Unue",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Sen Dosiera Versionado",
|
||||
"No files will be deleted as a result of this operation.": "Neniuj dosieroj estos forigitaj rezulte de ĉi tiu ago.",
|
||||
"No upgrades": "Sen ĝisdatigoj",
|
||||
"Normal": "Normala",
|
||||
"Notice": "Avizo",
|
||||
"OK": "Bone",
|
||||
"Off": "Malŝata",
|
||||
"Oldest First": "Malnova Unue",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Laŭvola priskriba etikedo por la dosierujo. Povas esti malsama en ĉiu aparato.",
|
||||
"Options": "Opcioj",
|
||||
"Out of Sync": "Elsinkronigita",
|
||||
"Out of Sync Items": "Elsinkronigitaj Eroj",
|
||||
"Outgoing Rate Limit (KiB/s)": " Eliranta Rapideco Limo (KiB/s)",
|
||||
"Override Changes": "Transpasi Ŝanĝojn",
|
||||
"Path": "Vojo",
|
||||
"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": "Vojo de la dosierujo en la loka komputilo. Kreiĝos se ne ekzistas. La tilda signo (~) povas esti uzata kiel mallongigilo por",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "La vojo, kie novaj aŭtomate akceptitaj dosierujoj estos kreita, kaj ankaŭ la defaŭlta sugestita vojo aldoninte novajn dosierujojn per uzinterfaco. Tildo (~) malvolvas al {{tilde}}.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Vojo kies versioj devus esti stokitaj (lasu malplena por la defaŭlta .stversions dosierujo en la komunigita dosierujo).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Vojo kies vesioj estas konservitaj (lasi malplena por la defaŭlta .stversions dosierujo en la dosierujo).",
|
||||
"Pause": "Paŭzu",
|
||||
"Pause All": "Paŭzu Ĉion",
|
||||
"Paused": "Paŭzita",
|
||||
"Pending changes": "Pritraktataj ŝanĝoj",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Perioda skanado ĉe donita intervalo kaj malebligita rigardado je ŝanĝoj",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Perioda skanado ĉe donita intervalo kaj ebligita rigardado je ŝanĝoj",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Perioda skanado ĉe donita intervalo kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
|
||||
"Permissions": "Permesoj",
|
||||
"Please consult the release notes before performing a major upgrade.": "Bonvolu konsulti la notojn de eldono antaŭ elfari ĉefan ĝisdatigon.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bonvolu agordi GUI Authentication Uzanto kaj Pasvorto en la agordoj dialogo.",
|
||||
"Please wait": "Bonvolu atendi",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefikso indikanta, ke la dosiero povas esti forigita, se ĝi malhelpas forigi dosierujon",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefikso indikanta, ke la ŝablono devus esti egalita usklecoblinde.",
|
||||
"Preview": "Antaŭrigardo",
|
||||
"Preview Usage Report": "Antaŭrigardo Uzada Raporto",
|
||||
"Quick guide to supported patterns": "Rapida gvidilo pri subtenata ŝablonoj",
|
||||
"RAM Utilization": "Labormemoro Uzo",
|
||||
"Random": "Hazarda",
|
||||
"Receive Only": "Nur Ricevi",
|
||||
"Recent Changes": "Lastatempaj Ŝanĝoj",
|
||||
"Reduced by ignore patterns": "Malpliigita per ignorantaj ŝablonoj",
|
||||
"Release Notes": "Notoj de Eldono",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidataj eldonoj enhavas la lastajn trajtojn kaj korektojn. Ili estas similaj al la tradiciaj dusemajnaj Syncthing eldonoj.",
|
||||
"Remote Devices": "Foraj Aparatoj",
|
||||
"Remove": "Forigu",
|
||||
"Remove Device": "Forigi Aparaton",
|
||||
"Remove Folder": "Forigi Dosierujon",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Nepra identigilo por la dosierujo. Devas esti la sama en ĉiuj aparatoj de la grupo.",
|
||||
"Rescan": "Reskanu",
|
||||
"Rescan All": "Reskanu Ĉion",
|
||||
"Rescan Interval": "Reskana Intervalo",
|
||||
"Rescans": "Reskanoj",
|
||||
"Restart": "Restartu",
|
||||
"Restart Needed": "Restarto Bezonata",
|
||||
"Restarting": "Restartado",
|
||||
"Restore": "Restarigi",
|
||||
"Restore Versions": "Restarigi Versiojn",
|
||||
"Resume": "Daŭrigu",
|
||||
"Resume All": "Daŭrigu Ĉion",
|
||||
"Reused": "Reuzita",
|
||||
"Revert Local Changes": "Reverti Lokajn Ŝangojn",
|
||||
"Running": "Kurante",
|
||||
"Save": "Konservu",
|
||||
"Scan Time Remaining": "Restanta Tempo de Skano",
|
||||
"Scanning": "Skanado",
|
||||
"See external versioner help for supported templated command line parameters.": "Vidu informlibron de ekstera versionilo por subtenata ŝablona parametroj de komandlinio.",
|
||||
"See external versioning help for supported templated command line parameters.": "Vidu informlibron de ekstera versionado por subtenata ŝablona parametroj de komandlinio.",
|
||||
"Select All": "Elekti Ĉiujn",
|
||||
"Select a version": "Elekti version",
|
||||
"Select latest version": "Elekti plej novan version",
|
||||
"Select oldest version": "Elekti plej malnovan version",
|
||||
"Select the devices to share this folder with.": "Elekti la aparatojn por komunigi ĉi tiun dosierujon.",
|
||||
"Select the folders to share with this device.": "Elekti la dosierujojn por komunigi kun ĉi tiu aparato.",
|
||||
"Send & Receive": "Sendi kaj Ricevi",
|
||||
"Send Only": "Nur Sendi",
|
||||
"Settings": "Agordoj",
|
||||
"Share": "Komunigi",
|
||||
"Share Folder": "Komunigu Dosierujon",
|
||||
"Share Folders With Device": "Dosierujoj Komunigitaj Kun Aparato",
|
||||
"Share With Devices": "Komunigu Kun Aparatoj",
|
||||
"Share this folder?": "Komunigi ĉi tiun dosierujon?",
|
||||
"Shared With": "Komunigita Kun",
|
||||
"Sharing": "Komunigo",
|
||||
"Show ID": "Montru ID",
|
||||
"Show QR": "Montru QR",
|
||||
"Show diff with previous version": "Montri diferenco kun antaŭa versio",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Montrita anstataŭ ID de Aparato en la statuso de la grupo. Estos anoncita al aliaj aparatoj kiel laŭvola defaŭlta nomo.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Montri anstataŭ ID de Aparato en la statuso de la grupo. Estos ĝisdatigita al la nomo de la aparato sciigante se ĝi estas lasita malplena.",
|
||||
"Shutdown": "Sistemfermo",
|
||||
"Shutdown Complete": "Sistemfermo Tuta",
|
||||
"Simple File Versioning": "Simpla Versionado de Dosieroj",
|
||||
"Single level wildcard (matches within a directory only)": "Ununivela ĵokero (egalas nur ene de dosierujo)",
|
||||
"Size": "Grandeco",
|
||||
"Smallest First": "Plej Malgranda Unue",
|
||||
"Some items could not be restored:": "Iuj eroj ne povis esti restarigitaj:",
|
||||
"Source Code": "Fontkodo",
|
||||
"Stable releases and release candidates": "Stabilaj eldonoj kaj kandidataj eldonoj",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabilaj eldonoj prokrastas je ĉirkaŭ du semjanoj. Dum tiu tempo ili estos testataj kiel kandidataj eldonoj.",
|
||||
"Stable releases only": "Nur stabilaj eldonoj",
|
||||
"Staggered File Versioning": "Gradigita Dosiera Versionado",
|
||||
"Start Browser": "Startu Retumilon",
|
||||
"Statistics": "Statistikoj",
|
||||
"Stopped": "Haltita",
|
||||
"Support": "Subteno",
|
||||
"Support Bundle": "Pakaĵo por subteno",
|
||||
"Sync Protocol Listen Addresses": "Aŭskultado Adresoj de Sinkprotokolo",
|
||||
"Syncing": "Sinkronigas",
|
||||
"Syncthing has been shut down.": "Syncthing estis malŝaltita.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inkluzivas la jenajn programarojn aŭ porciojn ĝiajn:",
|
||||
"Syncthing is restarting.": "Syncthing estas restartanta.",
|
||||
"Syncthing is upgrading.": "Syncthing estas ĝisdatigita.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ŝajnas nefunkcii, aŭ estas problemo kun via retkonekto. Reprovado...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ŝajnas renkonti problemon kun la traktado de via peto. Bonvolu refreŝigi la paĝon aŭ restarti Syncthing se la problemo daŭras.",
|
||||
"Take me back": "Prenu min reen",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "La adreso de grafika interfaco estas superregita per startigaj agordoj. Ŝanĝoj ĉi tie ne efektiviĝas dum la superrego estas aktuala.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "La administra interfaco de Syncthing estas agordita por permesi foran atingon sen pasvorto.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "La agregita statistikoj estas publike disponebla ĉe la URL malsupre.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La agordo estis registrita sed ne aktivigita. Syncthing devas restarti por aktivigi la novan agordon.",
|
||||
"The device ID cannot be blank.": "La aparato ID ne povas esti malplena.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "La aparato ID por eniri ĉi tie estas trovebla per \"Agoj > Montru ID\" dialogo en la alia aparato. Interspacoj kaj streketoj estas opcio (ignorigita).",
|
||||
"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.": "La ĉifrita raporto de uzado estas sendata ĉiutage. Ĝi estas uzata por sekvi komunajn platformojn, dosierujajn grandojn kaj aplikaĵajn versiojn. Se la raporto datumaro ŝanĝis, vi estos avertata per ĉi tiu dialogo denove.",
|
||||
"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.": "La enigita aparato ID ne ŝajnas valida. Ĝi devas esti signoĉeno el 52 aŭ 56 karaktroj longa enhavanta leterojn kaj nombrojn, kun interspacoj kaj streketoj opciaj.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "La unua komandlinia parametro estas la vojo de la dosierujo kaj la dua parametro estas la relativa vojo en la dosierujo.",
|
||||
"The folder ID cannot be blank.": "La dosierujo ID ne povas esti malplena.",
|
||||
"The folder ID must be unique.": "La dosierujo ID devas esti unika.",
|
||||
"The folder path cannot be blank.": "La vojo de dosierujo ne povas esti malplena.",
|
||||
"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.": "La jenaj intervaloj estas uzataj: dum la unua horo version restas dum ĉiuj 30 sekundoj, dum la unua tago versio restas konservita dum ĉiu horo, dum la unuaj 30 tagoj versio estas konservita dum ĉiu tago, ĝis la maksimume aĝa versio restas konservita dum ĉiu semajno.",
|
||||
"The following items could not be synchronized.": "La sekvantaj eroj ne povas esti sinkronigitaj.",
|
||||
"The following items were changed locally.": "La sekvantaj eroj estis ŝanĝitaj loke.",
|
||||
"The maximum age must be a number and cannot be blank.": "La maksimuma aĝo devas esti nombro kaj ne povas esti malplena.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La maksimuma tempo por konservi version (en tagoj, agordi je 0 por konservi versiojn eterne).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "La minimuma procento de libera diskospaco devas esti pozitiva nombro inter 0 kaj 100 (inkluziva).",
|
||||
"The number of days must be a number and cannot be blank.": "La nombro da tagoj devas esti nombro kaj ne povas esti malplena.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "La nombro da tagoj por konservi dosierojn en la rubujo. Nulo signifas eterne.",
|
||||
"The number of old versions to keep, per file.": "La nombro da malnovaj versioj por konservi, po ĉiu dosiero.",
|
||||
"The number of versions must be a number and cannot be blank.": "La nombro da versioj devas esti nombro kaj ne povas esti malplena.",
|
||||
"The path cannot be blank.": "La vojo ne povas esti malplena.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "La rapideca limo devas esti pozitiva nombro (0: senlimo)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "La intervalo de reskano devas esti pozitiva nombro da sekundoj.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Ili estas reprovitaj aŭtomate kaj estos sinkronigitaj kiam la eraro estas solvita.",
|
||||
"This Device": "Ĉi Tiu Aparato",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Ĉi tio povas facile doni al kodumuloj atingon por legi kaj ŝanĝi ajnajn dosierojn en via komputilo.",
|
||||
"This is a major version upgrade.": "Ĉi tio estas ĉefversio ĝisdatigita.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Ĉi tiu agordo regas la libera spaco postulita sur la hejma (t.e. indeksa datumbaza) disko.",
|
||||
"Time": "Tempo",
|
||||
"Time the item was last modified": "Tempo de lasta modifo de la ero",
|
||||
"Trash Can File Versioning": "Rubuja Dosiera Versionado",
|
||||
"Type": "Tipo",
|
||||
"Unavailable": "Ne disponebla",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Ne disponebla/Malebligita de administranto aŭ subtenanto",
|
||||
"Undecided (will prompt)": "Hezitema (demandos)",
|
||||
"Unignore": "Malignoru",
|
||||
"Unknown": "Nekonata",
|
||||
"Unshared": "Nekomunigita",
|
||||
"Unused": "Neuzita",
|
||||
"Up to Date": "Ĝisdata",
|
||||
"Updated": "Ĝisdatigita",
|
||||
"Upgrade": "Altgradigo",
|
||||
"Upgrade To {%version%}": "Altgradigi Al {{version}}",
|
||||
"Upgrading": "Altgradigata",
|
||||
"Upload Rate": "Alŝutrapideco",
|
||||
"Uptime": "Daŭro de funkciado",
|
||||
"Usage reporting is always enabled for candidate releases.": "Uzada raportado ĉiam ŝaltita por kandidataj eldonoj.",
|
||||
"Use HTTPS for GUI": "Uzi HTTPS por grafika interfaco.",
|
||||
"Use notifications from the filesystem to detect changed items.": "Uzi sciigoj de la dosiersistemo por detekti ŝanĝitajn erojn.",
|
||||
"Variable Size Blocks": "Blokoj de Variaj Grandecoj",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Blokoj de Variaj Grandecoj (ankaŭ \"grandaj blokoj\") estas pli efika por grandaj dosieroj.",
|
||||
"Version": "Versio",
|
||||
"Versions": "Versioj",
|
||||
"Versions Path": "Vojo de Versioj",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioj estas aŭtomate forigita se ili estas pli malnovaj ol la maksimuma aĝo aŭ superas la nombron da dosieroj permesita en intervalo.",
|
||||
"Waiting to scan": "Atendante scanadon",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas parenta dosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas parenta dosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Averto: se vi uzas ekstera rigardanto simila al {{syncthingInotify}}, vi devas certiĝi ĝi estas senaktivita.",
|
||||
"Watch for Changes": "Rigardi Ŝanĝojn",
|
||||
"Watching for Changes": "Rigardado je Ŝanĝoj",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Rigardado je ŝanĝoj malkovras plejparton de la ŝanĝoj sen perioda skanado.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Dum la aldonado de nova aparato, memoru ke ĉi tiu aparato devas esti aldonita en la alia flanko ankaŭ.",
|
||||
"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.": "Dum la aldonado de nova dosierujo, memoru ke la Dosieruja ID estas uzita por ligi la dosierujojn kune inter aparatoj. Ili estas literfakodistingaj kaj devas kongrui precize inter ĉiuj aparatoj.",
|
||||
"Yes": "Jes",
|
||||
"You can also select one of these nearby devices:": "Vi povas ankaŭ elekti unu el ĉi tiuj proksimaj aparatoj:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Vi povas ŝanĝi vian elekton iam ajn en la Agorda dialogo.",
|
||||
"You can read more about the two release channels at the link below.": "Vi povas legi plu pri la du eldonkanaloj per la malsupra ligilo.",
|
||||
"You have no ignored devices.": "Vi havas neniujn ignoritajn aparatojn.",
|
||||
"You have no ignored folders.": "Vi havas neniujn ignoritajn dosierujojn.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Vi havas ne konservitaj ŝanĝoj. Ĉu vi vere volas forĵeti ilin?",
|
||||
"You must keep at least one version.": "Vi devas konservi almenaŭ unu version.",
|
||||
"days": "tagoj",
|
||||
"directories": "dosierujoj",
|
||||
"files": "dosieroj",
|
||||
"full documentation": "tuta dokumentado",
|
||||
"items": "eroj",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas komunigi dosierujon \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas komunigi dosierujon \"{{folderlabel}}\" ({{folder}})."
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copiado del original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes Colaboradores:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 Los siguientes colaboradores:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Crear patrones a ignorar, sobreescribiendo un fichero existente en {{path}}.",
|
||||
"Danger!": "¡Peligro!",
|
||||
"Debugging Facilities": "Ayudas a la depuración",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Tiempo de funcionamiento",
|
||||
"Usage reporting is always enabled for candidate releases.": "El informe de uso está siempre habilitado en las versiones candidatas.",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para la Interfaz Gráfica de Usuario (GUI)",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Versión",
|
||||
"Versions": "Versiones",
|
||||
"Versions Path": "Ruta de las versiones",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advertencia: Si estás usando un vigilante externo como {{syncthingInotify}}, deberías asegurarte de que está desactivado.",
|
||||
"Watch for Changes": "Vigilar los cambios",
|
||||
"Watching for Changes": "Vigilando los cambios",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
|
||||
"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",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copiado del original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes Colaboradores:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 Los siguientes colaboradores:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Crear patrones a ignorar, sobreescribiendo un fichero existente en {{path}}.",
|
||||
"Danger!": "¡Peligro!",
|
||||
"Debugging Facilities": "Servicios de depuración",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Tiempo de funcionamiento",
|
||||
"Usage reporting is always enabled for candidate releases.": "El informe de uso está siempre habilitado en las versiones candidatas.",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para la Interfaz Gráfica de Usuario (GUI)",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Versión",
|
||||
"Versions": "Versiones",
|
||||
"Versions Path": "Ruta de las versiones",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advertencia: Si estás utilizando un observador externo como {{syncthingInotify}}, debes asegurarte de que está desactivado.",
|
||||
"Watch for Changes": "Vigila los cambios",
|
||||
"Watching for Changes": "Vigilando los cambios",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
|
||||
"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",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Kopioitu alkuperäisestä lähteestä",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 seuraavat avustajat",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 seuraavat avustajat:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Luodaan ohituslausekkeet. Ylikirjoitetaan tiedosto: {{path}}.",
|
||||
"Danger!": "Vaara!",
|
||||
"Debugging Facilities": "Debug -luokat",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Päälläoloaika",
|
||||
"Usage reporting is always enabled for candidate releases.": "Käytön raportointi on aina käytössä testiversioissa.",
|
||||
"Use HTTPS for GUI": "Käytä HTTPS:ää GUI:n kanssa",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Versio",
|
||||
"Versions": "Versiot",
|
||||
"Versions Path": "Versioiden polku",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Varoitus: jos käytät ulkopuolista tiedostojärjestelmän muutosten valvojaa, kuten {{syncthingInotify}} varmista, että se ei ole aktiivinen.",
|
||||
"Watch for Changes": "Seuraa muutoksia",
|
||||
"Watching for Changes": "Seuraa muutoksia",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lisättäessä laitetta, muista että tämä laite tulee myös lisätä toiseen laitteeseen.",
|
||||
"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ä",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copié depuis l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016, les contributeurs sont:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017, les contributeurs sont:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 les Contributeurs suivants :",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Création de masques d'exclusion, remplacement du fichier existant : {{path}}.",
|
||||
"Danger!": "Attention !",
|
||||
"Debugging Facilities": "Outils de débogage",
|
||||
@@ -138,7 +139,7 @@
|
||||
"Global Discovery": "Découverte globale",
|
||||
"Global Discovery Servers": "Serveurs de découverte globale",
|
||||
"Global State": "État global",
|
||||
"Help": "Aide",
|
||||
"Help": "Aide (anglais)",
|
||||
"Home page": "Page d'accueil",
|
||||
"Ignore": "Refuser",
|
||||
"Ignore Patterns": "Exclusions...",
|
||||
@@ -298,7 +299,7 @@
|
||||
"Syncthing is upgrading.": "Syncthing se met à jour.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être arrêté, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble avoir un problème pour traiter votre demande. Rafraîchissez la page (F5 sur PC) ou redémarrez Syncthing si le problème persiste.",
|
||||
"Take me back": "Reprends moi",
|
||||
"Take me back": "Vérifier ...",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "L'adresse de l'interface graphique est remplacée par une ou des options de lancement. Les modifications apportées ici ne seront pas effectives tant que ces options seront utilisées.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "L'interface d'administration de Syncthing est paramétrée pour autoriser les accès à distance sans mot de passe !!!",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Les statistiques agrégées sont disponibles publiquement à l'adresse ci-dessous.",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Durée de fonctionnement",
|
||||
"Usage reporting is always enabled for candidate releases.": "L'envoi des statistiques d'utilisation est obligatoirement actif pour les versions préliminaires.",
|
||||
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Utiliser les notifications du système de fichiers pour détecter les éléments modifiés.",
|
||||
"Variable Size Blocks": "Blocs de taille variable",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Les blocs de taille variable (dont les \"gros blocs\") sont plus efficaces pour les gros fichiers.",
|
||||
"Version": "Version",
|
||||
"Versions": "Restauration...",
|
||||
"Versions Path": "Emplacement des versions",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Attention : si vous utilisez un système externe de surveillance tel que {{syncthingInotify}}, vous devez vous assurer qu'il est désactivé.",
|
||||
"Watch for Changes": "Écouter les changements",
|
||||
"Watching for Changes": "Surveillance des changements",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "La surveillance des changements découvre la plupart des changements sans réanalyses périodiques.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lorsque vous ajoutez un appareil, gardez à l'esprit que le votre doit aussi être ajouté de l'autre coté.",
|
||||
"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 partage est ajouté, gardez à l'esprit que son ID est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur tous les appareils participant à ce partage.",
|
||||
"Yes": "Oui",
|
||||
@@ -369,7 +374,7 @@
|
||||
"You can read more about the two release channels at the link below.": "Vous pouvez en savoir plus sur les deux canaux de distribution via le lien ci-dessous.",
|
||||
"You have no ignored devices.": "Aucun appareil refusé.",
|
||||
"You have no ignored folders.": "Aucun partage refusé.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Vous avez des réglages non enregistrés. Voulez-vous vraiment les ignorer ?",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Vous avez des réglages non enregistrés. Voulez-vous vraiment les rejeter ?",
|
||||
"You must keep at least one version.": "Vous devez garder au minimum une version.",
|
||||
"days": "Jours",
|
||||
"directories": "répertoires",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Oernommen fan orizjineel",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 de folgende bydragers:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 de folgende Bydragers:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Meitsje negear-patroanen dy in besteande triem oerskriuwe yn {{path}}.",
|
||||
"Danger!": "Gefaar!",
|
||||
"Debugging Facilities": "Debug-foarsjennings",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Rintiid",
|
||||
"Usage reporting is always enabled for candidate releases.": "Brûkersrapportaazje stiet altyd oan foar ferzje kandidaten.",
|
||||
"Use HTTPS for GUI": "Brûk HTTPS foar GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Ferzje",
|
||||
"Versions": "Ferzjes",
|
||||
"Versions Path": "Ferzjes-paad",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warskôging: As jo in eksterne sjogger lykas {{syncthingInotify}} brûke, bin der dan wiis fan dat dizze út stiet.",
|
||||
"Watch for Changes": "Sjoch foar Feroarings",
|
||||
"Watching for Changes": "Sjocht foar Feroarings",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Hâld by it taheakjen fan in nij apparaat yn de holle dat it apparaat oan de oare kant ek taheakke wurde moat. ",
|
||||
"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.": "Hâld by it taheakjen fan in nije map yn de holle dat de map-ID brûkt wurd om de mappen tusken apparaten mei-inoar te ferbinen. Se binne haadlettergefoelich en moatte oer alle apparaten eksakt oerienkomme.",
|
||||
"Yes": "Ja",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Eredetiről másolva",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Szerzői jog © 2014-2016 az alábbi közreműködők:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Szerzői jog © 2014-2017 az alábbi közreműködők:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Szerzői jog © 2014-2019 az alábbi közreműködők:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Mellőzési minták létrehozása, egy létező fájl felülírása itt: {{path}}.",
|
||||
"Danger!": "Veszély!",
|
||||
"Debugging Facilities": "Hibakeresési képességek",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Üzemidő",
|
||||
"Usage reporting is always enabled for candidate releases.": "Az előzetes kiadásokban a használati jelentés mindig engedélyezett.",
|
||||
"Use HTTPS for GUI": "HTTPS használata a grafikus felülethez",
|
||||
"Use notifications from the filesystem to detect changed items.": "A fájlrendszer által szolgáltatott értesítések alkalmazása a megváltozott elemek keresésére.",
|
||||
"Variable Size Blocks": "Változó méretű blokkok",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "A változó mérető blokkok (ún. „nagy blokkok”) sokkal hatékonyabbak nagy fájloknál.",
|
||||
"Version": "Verzió",
|
||||
"Versions": "Verziók",
|
||||
"Versions Path": "Verziók útvonala",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Figyelem: Ha eddig külső megfigyelő volt erre a célra alkalmazva, mint pl. a(z) {{syncthingInotify}}, akkor előbb meg kell győződni arról, hogy az ki lett kapcsolva.",
|
||||
"Watch for Changes": "Változásfigyelés",
|
||||
"Watching for Changes": "Változások figyelése",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "A változások keresése felfedezi a legtöbb változást periodikus átnézés nélkül.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Új eszköz hozzáadásakor nem szabad elfeledkezni arról, hogy a másik oldalon ezt az eszközt is hozzá kell adni.",
|
||||
"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.": "Új eszköz hozzáadásakor észben kell tartani, hogy a mappaazonosító arra való, hogy összekösse a mappákat az eszközökön. Az azonosító kisbetű-nagybetű érzékeny és pontosan egyeznie kell az eszközökön.",
|
||||
"Yes": "Igen",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copiato dall'originale",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 i seguenti Collaboratori:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 i seguenti Collaboratori:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creazione di schemi di esclusione, sovrascrivendo un file esistente in {{path}}.",
|
||||
"Danger!": "Pericolo!",
|
||||
"Debugging Facilities": "Servizi di Debug",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Tempo di Funzionamento",
|
||||
"Usage reporting is always enabled for candidate releases.": "Le segnalazioni di utilizzo sono sempre abilitate le versioni candidate al rilascio.",
|
||||
"Use HTTPS for GUI": "Utilizza HTTPS per l'interfaccia grafica",
|
||||
"Use notifications from the filesystem to detect changed items.": "Usa le notifiche dal filesystem per rilevare gli elementi modificati.",
|
||||
"Variable Size Blocks": "Blocchi di dimensioni variabile",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "I blocchi di dimensioni variabile (anche \"blocchi di grandi dimensioni\") sono più efficienti per file di grandi dimensioni.",
|
||||
"Version": "Versione",
|
||||
"Versions": "Versioni",
|
||||
"Versions Path": "Percorso Cartella Versioni",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Attenzione: se stai usando uno strumento esterno di monitoraggio come {{syncthingInotify}}, dovresti assicurarti che sia disattivato.",
|
||||
"Watch for Changes": "Monitorare i cambiamenti",
|
||||
"Watching for Changes": "Monitoraggio dei cambiamenti",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Guardando le modifiche si scopre la maggior parte delle modifiche senza scansione periodica.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando si aggiunge un nuovo dispositivo, tenere presente che il dispositivo deve essere aggiunto anche dall'altra parte.",
|
||||
"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ì",
|
||||
|
||||
@@ -56,10 +56,11 @@
|
||||
"Copied from original": "元ファイルからコピー済",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "無視パターンを作成中。既存のファイルが {{path}} にある場合は上書きされます。",
|
||||
"Danger!": "危険!",
|
||||
"Debugging Facilities": "Debugging Facilities",
|
||||
"Default Folder Path": "Default Folder Path",
|
||||
"Debugging Facilities": "デバッグ機能",
|
||||
"Default Folder Path": "デフォルトのフォルダーパス",
|
||||
"Deleted": "削除",
|
||||
"Deselect All": "すべて選択解除",
|
||||
"Device": "デバイス",
|
||||
@@ -202,7 +203,7 @@
|
||||
"Override Changes": "他のデバイスの変更を上書きする",
|
||||
"Path": "パス",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "ローカルコンピュータ上のフォルダーパス。フォルダーが存在しない場合は作成されます。チルダ (~) で以下のフォルダーを短縮入力できます:",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "新しい自動承諾フォルダーが作成されるパス、およびUIを使用して新しいフォルダーを追加するときにデフォルトでサジェストされるパスです。チルダ文字(~)の場合は {{tilde}} に展開されます。",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "古いバージョンを保存するパス (空欄の場合、デフォルトで共有フォルダー内の .stversions ディレクトリ)",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "古いバージョンを保存するパス (空欄の場合、デフォルトでフォルダー内の .stversions フォルダー)",
|
||||
"Pause": "一時停止",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "稼働時間",
|
||||
"Usage reporting is always enabled for candidate releases.": "リリース候補版では常に使用状況レポートが送信されます。",
|
||||
"Use HTTPS for GUI": "GUIにHTTPSを使用する",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "バージョン",
|
||||
"Versions": "Versions",
|
||||
"Versions Path": "古いバージョンを保存するパス",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
|
||||
"Watch for Changes": "変更の監視",
|
||||
"Watching for Changes": "変更の監視",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "新しいデバイスを追加する際は、相手側デバイスにもこのデバイスを追加してください。",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "新しいフォルダーを追加する際、フォルダーIDはデバイス間でフォルダーの対応づけに使われることに注意してください。フォルダーIDは大文字と小文字が区別され、共有するすべてのデバイスの間で完全に一致しなくてはなりません。",
|
||||
"Yes": "はい",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "원본에서 복사됨",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "무시 패턴 만들기, {{path}}에 존재하는 파일을 덮어쓰기 합니다",
|
||||
"Danger!": "경고!",
|
||||
"Debugging Facilities": "디버깅 기능",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "가동 시간",
|
||||
"Usage reporting is always enabled for candidate releases.": "출시 후보 버전에서는 사용 보고서가 항상 활성화 됩니다.",
|
||||
"Use HTTPS for GUI": "GUI에서 HTTPS 프로토콜 사용",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "버전",
|
||||
"Versions": "버전",
|
||||
"Versions Path": "버전 저장 경로",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "경고 : {{syncthingInotify}} 와 같은 외부 감시 도구를 사용하는 경우 비활성화되어 있는지 확인해야 합니다.",
|
||||
"Watch for Changes": "변경 사항 감시",
|
||||
"Watching for Changes": "변경 사항 감시",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "새 장치를 추가할 시 추가한 기기 쪽에서도 이 장치를 추가해야 합니다.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
|
||||
"Yes": "예",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Nukopijuota iš originalo",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Autorių teisės © 2014-2016 šių bendraautorių:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Autorių teisės © 2014-2017 šių bendraautorių:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Autorių teisės © 2014-2019 šių bendraautorių:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Kuriami nepaisomi šablonai, perrašomas esamas failas, esantis {{path}}.",
|
||||
"Danger!": "Pavojus!",
|
||||
"Debugging Facilities": "Derinimo priemonės",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Veiksnumo laikas",
|
||||
"Usage reporting is always enabled for candidate releases.": "Naudojimo ataskaita kandidatinėms versijoms visada yra įjungta.",
|
||||
"Use HTTPS for GUI": "Valdymo skydeliui naudoti saugų ryšį ",
|
||||
"Use notifications from the filesystem to detect changed items.": "Naudoti pranešimus iš failų sistemos, norint aptikti pakeistus elementus.",
|
||||
"Variable Size Blocks": "Kintamųjų dydžių blokai",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Kintamųjų dydžių blokai (taip pat \"dideli blokai\") yra efektyvesni dideliems failams.",
|
||||
"Version": "Versija",
|
||||
"Versions": "Versijos",
|
||||
"Versions Path": "Kelias iki versijos",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Įspėjimas: Jeigu naudojate išorinį stebėtoją, tokį kaip {{syncthingInotify}}, tuomet turite įsitikinti, kad jis yra išjungtas.",
|
||||
"Watch for Changes": "Stebėti pakeitimus",
|
||||
"Watching for Changes": "Stebimi pakeitimai",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Pakeitimų stebėjimas aptinka daugumą pakeitimų be periodinio nuskaitymo.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Pridėdami įrenginį, turėkite omeny, kad šis įrenginys taip pat turi būti pridėtas kitoje pusėje.",
|
||||
"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",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Kopiert fra original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Opphavsrett © 2014-2016 for følgende bidragsytere:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Opphavsrett © 2014-2017 for følgende bidragsytere:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Oppretter ignoreringsmønster, overskriver eksisterende fil i {{path}}.",
|
||||
"Danger!": "Fare!",
|
||||
"Debugging Facilities": "Feilrettingsverktøy",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Oppetid",
|
||||
"Usage reporting is always enabled for candidate releases.": "Bruksrapportering er alltid påslått for utgivelseskandidater",
|
||||
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Versjon",
|
||||
"Versions": "Versjoner",
|
||||
"Versions Path": "Plassering av versjoner",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advarsel: Hvis du bruker en ekstern viser, som {{syncthingInotify}}, burde du forsikre deg om at den avslått.",
|
||||
"Watch for Changes": "Hold oppsyn med endringer",
|
||||
"Watching for Changes": "Holder oppsyn med endringer",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når du legger til en ny enhet, husk at enheten må legges til på andre siden også.",
|
||||
"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",
|
||||
|
||||
@@ -52,10 +52,11 @@
|
||||
"Connection Type": "Soort verbinding",
|
||||
"Connections": "Verbindingen",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Continu opvolgen van wijzigingen is nu beschikbaar in Syncthing. Dit zal wijzigingen op een schijf detecteren en alleen een scan uitvoeren op de gewijzigde paden. De voordelen zijn dat wijzigingen sneller doorgevoerd worden en dat minder volledige scans nodig zijn.",
|
||||
"Copied from elsewhere": "Gekopieerd van ergens anders",
|
||||
"Copied from original": "Gekopieerd van het origineel",
|
||||
"Copied from elsewhere": "Gekopieerd van elders",
|
||||
"Copied from original": "Gekopieerd van origineel",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Auteursrecht © 2014-2016 voor de volgende bijdragers:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Auteursrecht © 2014-2017 voor de volgende bijdragers:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Auteursrecht © 2014-2019 voor de volgende bijdragers:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Negeerpatronen worden aangemaakt, bestaand bestand wordt overschreven op {{path}}.",
|
||||
"Danger!": "Let op!",
|
||||
"Debugging Facilities": "Debugmogelijkheden",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Bedrijfstijd",
|
||||
"Usage reporting is always enabled for candidate releases.": "Gebruiksrapportering is altijd ingeschakeld voor de kandidaat-releases.",
|
||||
"Use HTTPS for GUI": "HTTPS gebruiken voor GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Meldingen van het bestandssysteem gebruiken om gewijzigde items te detecteren.",
|
||||
"Variable Size Blocks": "Blokken met variabele grootte",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Blokken met variabele grootte (ook \"grote blokken\") zijn efficiënter voor grote bestanden.",
|
||||
"Version": "Versie",
|
||||
"Versions": "Versies",
|
||||
"Versions Path": "Pad voor versies",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Waarschuwing: als u een externe opvolger gebruikt zoals {{syncthingInotify}}, moet u zich ervan verzekeren dat die uitgeschakeld is.",
|
||||
"Watch for Changes": "Wijzigingen opvolgen",
|
||||
"Watching for Changes": "Wijzigingen opvolgen",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Opvolgen van wijzigingen ontdekt de meeste wijzigingen zonder periodiek scannen.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Houd er bij het toevoegen van een nieuw apparaat rekening mee dat dit apparaat ook aan de andere kant moet toegevoegd worden.",
|
||||
"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 een nieuwe map rekening mee dat de map-ID gebruikt wordt om mappen aan elkaar te koppelen tussen apparaten. Ze zijn hoofdlettergevoelig en moeten exact overeenkomen op alle apparaten.",
|
||||
"Yes": "Ja",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Skopiowane z oryginału",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016: ",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Prawa autorskie © 2014-2017 dla następujących autorów:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Ustawienie wzorów ignorowania, nadpisze istniejący plik w {{path}}.",
|
||||
"Danger!": "Niebezpieczne!",
|
||||
"Debugging Facilities": "Odpluskwianie",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Czas działania",
|
||||
"Usage reporting is always enabled for candidate releases.": "Raportowanie użycia dla wydań kandydujących jest zawsze włączone.",
|
||||
"Use HTTPS for GUI": "Używaj HTTPS",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Wersja",
|
||||
"Versions": "Wersje",
|
||||
"Versions Path": "Ścieżka wersji",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeśli korzystasz z zewnętrznego obserwatora takiego jak {{syncthingInotify}}, upewnij się, że jest on dezaktywowany.",
|
||||
"Watch for Changes": "Obserwuj zmiany",
|
||||
"Watching for Changes": "Obserwowanie zmian",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Gdy dodajesz nowe urządzenie, pamiętaj że urządzenie musi zostać dodane także po drugiej stronie.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "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",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copiado do original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Direitos reservados © 2014-2016 aos seguintes colaboradores:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 dos seguintes Colaboradores:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Criando filtros, sobrescrevendo o arquivo {{path}}.",
|
||||
"Danger!": "Perigo!",
|
||||
"Debugging Facilities": "Facilidades de depuração",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Tempo ligado",
|
||||
"Usage reporting is always enabled for candidate releases.": "O relatório de uso está sempre habilitado em versões candidatas ao lançamento",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Versão",
|
||||
"Versions": "Versões",
|
||||
"Versions Path": "Caminho do versionamento",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
|
||||
"Watch for Changes": "Observar alterações",
|
||||
"Watching for Changes": "Observando alterações",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "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 deve ser o mesmo em todos os dispositivos.",
|
||||
"Yes": "Sim",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Copiado do original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 os seguintes contribuidores:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 dos seguintes contribuidores:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 dos seguintes contribuidores:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Criando padrões de exclusão, sobrescrevendo um ficheiro existente em {{path}}.",
|
||||
"Danger!": "Perigo!",
|
||||
"Debugging Facilities": "Recursos de depuração",
|
||||
@@ -249,7 +250,7 @@
|
||||
"Running": "Em execução",
|
||||
"Save": "Gravar",
|
||||
"Scan Time Remaining": "Tempo restante da verificação",
|
||||
"Scanning": "Verificando",
|
||||
"Scanning": "Verificação de alterações",
|
||||
"See external versioner help for supported templated command line parameters.": "Veja a ajuda do gestor de versões externo para saber que parâmetros da linha de comandos são suportados.",
|
||||
"See external versioning help for supported templated command line parameters.": "Veja a ajuda externa sobre gestão de versões para ver os modelos suportados de parâmetros para a linha de comandos.",
|
||||
"Select All": "Seleccionar tudo",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Tempo em actividade",
|
||||
"Usage reporting is always enabled for candidate releases.": "O relatório de utilização está sempre activado nas versões candidatas a lançamento.",
|
||||
"Use HTTPS for GUI": "Utilizar HTTPS na interface gráfica",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notificações do sistema de ficheiros para detectar itens alterados.",
|
||||
"Variable Size Blocks": "Blocos de tamanho variável",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Blocos de tamanho variável (também designados por \"blocos grandes\") são mais eficientes para ficheiros grandes.",
|
||||
"Version": "Versão",
|
||||
"Versions": "Versões",
|
||||
"Versions Path": "Caminho das versões",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Aviso: Se estiver a usar um verificador externo, tal como o {{syncthingInotify}}, deve certificar-se que está desactivado.",
|
||||
"Watch for Changes": "Vigiar alterações",
|
||||
"Watching for Changes": "Vigilância de alterações",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "A vigilância de alterações encontra a maior parte das alterações sem a verificação periódica.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando adicionar um novo dispositivo, lembre-se que este dispositivo tem que 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 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",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"Add Device": "Добавить устройство",
|
||||
"Add Folder": "Добавить папку",
|
||||
"Add Remote Device": "Добавить удалённое устройство",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Добавлять устройства, известные рекомендателю, в список устройств, если есть общие с ними папки.",
|
||||
"Add new folder?": "Добавить новую папку?",
|
||||
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Также будет увеличен интервал полного сканирования (в 60 раз, т.е. новое значение по умолчанию 1 час). Вы можете вручную настроить интервал для каждой папки, выбрав \"Нет\".",
|
||||
"Address": "Адрес",
|
||||
@@ -23,7 +23,7 @@
|
||||
"Allowed Networks": "Разрешённые сети",
|
||||
"Alphabetic": "По алфавиту",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "Для версионирования используется внешняя программа. Ей нужно удалить файл из общей папки.",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Для версионирования используется внешняя программа. Ей нужно удалить файл из общей папки. Если путь к приложению содержит пробелы, его нужно взять в кавычки.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Внешний процесс управляет версиями файлов. Процесс удалит файл из синхронизируемой папки.",
|
||||
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимных отчётов изменился. Вы хотите переключиться на новый формат?",
|
||||
@@ -34,8 +34,8 @@
|
||||
"Auto Accept": "Автопринятие",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматическое обновление теперь предлагает выбор между стабильными выпусками и кандидатами в релизы.",
|
||||
"Automatic upgrades": "Автообновление",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Автоматическое обновление всегда включено для кандидатов в релизы.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Автоматически создавать, используя путь по умолчанию, или делиться папками, о которых сообщает это устройство.",
|
||||
"Available debug logging facilities:": "Доступные средства отладочного журнала:",
|
||||
"Be careful!": "Будьте осторожны!",
|
||||
"Bugs": "Ошибки",
|
||||
@@ -56,12 +56,13 @@
|
||||
"Copied from original": "Скопировано с оригинала",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Авторские права © 2014–2016 принадлежат:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Авторские права © 2014—2017 следующие участники:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Авторские права © 2014–2019 принадлежат:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Создание шаблонов игнорирования, существующий файл {{path}} будет перезаписан.",
|
||||
"Danger!": "Опасно!",
|
||||
"Debugging Facilities": "Средства отладки",
|
||||
"Default Folder Path": "Путь для папок",
|
||||
"Deleted": "Удалено",
|
||||
"Deselect All": "Deselect All",
|
||||
"Deselect All": "Снять выделение",
|
||||
"Device": "Устройство",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство «{{name}}» ({{device}} на {{address}}) хочет подключиться. Добавить новое устройство?",
|
||||
"Device ID": "ID устройства",
|
||||
@@ -73,8 +74,8 @@
|
||||
"Disabled": "Отключено",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Периодическое сканирование и отслеживание изменений отключено",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Периодическое сканирование отключено, отслеживание изменений включено",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
|
||||
"Discard": "Discard",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодическое сканирование отключено, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
|
||||
"Discard": "Отменить",
|
||||
"Disconnected": "Нет соединения",
|
||||
"Discovered": "Обнаружено",
|
||||
"Discovery": "Обнаружение",
|
||||
@@ -97,7 +98,7 @@
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Введите положительное значение (например, \"2.35\") и выберите единицу измерения, либо процент от общей ёмкости диска.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Введите непривилегированный порт (1024—65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введите через запятую («tcp://ip:port», «tcp://host:port») адреса, либо «dynamic», чтобы выполнить автоматическое обнаружение адреса.",
|
||||
"Enter 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": "Внешний контроль версий файлов",
|
||||
@@ -115,7 +116,7 @@
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файлы защищены от изменений сделанных на других устройствах, но изменения сделанные на этом устройстве будут отправлены всему кластеру.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Файлы синхронизируются из группы, но изменения, сделанные на этом устройстве, не будут отправлены на другие устройства группы.",
|
||||
"Filesystem Notifications": "Уведомления файловой системы",
|
||||
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
|
||||
"Filesystem Watcher Errors": "Ошибки отслеживания файловой системы",
|
||||
"Filter by date": "Отфильтровать по дате",
|
||||
"Filter by name": "Отфильтровать по имени",
|
||||
"Folder": "Папка",
|
||||
@@ -124,7 +125,7 @@
|
||||
"Folder Path": "Путь к папке",
|
||||
"Folder Type": "Тип папки",
|
||||
"Folders": "Папки",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Для следующих папок произошла ошибка при запуске отслеживания изменений. Попытки будут повторяться раз в минуту, и ошибки скоро могут быть устранены. Если этого не произойдёт, попробуйте разобраться в причинах и попросите поддержки, если у вас не получится.",
|
||||
"Full Rescan Interval (s)": "Интервал полного сканирования (в секундах)",
|
||||
"GUI": "Интерфейс",
|
||||
"GUI Authentication Password": "Пароль для доступа к панели управления",
|
||||
@@ -159,14 +160,14 @@
|
||||
"Later": "Позже",
|
||||
"Latest Change": "Последнее изменение",
|
||||
"Learn more": "Узнать больше",
|
||||
"Limit": "Limit",
|
||||
"Limit": "Ограничение",
|
||||
"Listeners": "Прослушиватель",
|
||||
"Loading data...": "Загрузка данных...",
|
||||
"Loading...": "Загрузка...",
|
||||
"Local Discovery": "Локальное обнаружение",
|
||||
"Local State": "Локальное состояние",
|
||||
"Local State (Total)": "Локальное состояние (всего)",
|
||||
"Locally Changed Items": "Locally Changed Items",
|
||||
"Locally Changed Items": "Объекты, изменённые на этом компьютере",
|
||||
"Log": "Журнал",
|
||||
"Log tailing paused. Click here to continue.": "Вывод журнала приостановлен. Чтобы продолжить, нажмите здесь.",
|
||||
"Log tailing paused. Scroll to bottom continue.": "Вывод журнала приостановлен. Чтобы продолжить, прокрутите журнал до конца.",
|
||||
@@ -177,8 +178,8 @@
|
||||
"Maximum Age": "Максимальный срок",
|
||||
"Metadata Only": "Только метаданные",
|
||||
"Minimum Free Disk Space": "Минимальное свободное место на диске",
|
||||
"Mod. Device": "Mod. Device",
|
||||
"Mod. Time": "Mod. Time",
|
||||
"Mod. Device": "Изм. устройство",
|
||||
"Mod. Time": "Посл. изм.",
|
||||
"Move to top of queue": "Поместить в начало очереди",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Многоуровневая маска (поиск совпадений во всех подпапках)",
|
||||
"Never": "Никогда",
|
||||
@@ -208,16 +209,16 @@
|
||||
"Pause": "Пауза",
|
||||
"Pause All": "Приостановить все",
|
||||
"Paused": "Приостановлено",
|
||||
"Pending changes": "Pending changes",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
|
||||
"Pending changes": "Несохранённые изменения",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Периодическое сканирование с заданным интервалом, отслеживание изменений отключено",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Периодическое сканирование с заданным интервалом и включено отслеживание изменений",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Периодическое сканирование с заданным интервалом, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
|
||||
"Permissions": "Разрешения",
|
||||
"Please consult the release notes before performing a major upgrade.": "Перед проведением обновления основной версии ознакомтесь, пожалуйста, с Замечаниями к версии",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Установите имя пользователя и пароль для интерфейса в настройках",
|
||||
"Please wait": "Пожалуйста, подождите",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Префикс указывает, что файл может быть удалён, если он мешает удалить папку",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Префикс указывает, что регистр букв (заглавные/строчные) игнорируется",
|
||||
"Preview": "Предварительный просмотр",
|
||||
"Preview Usage Report": "Посмотреть отчёт об использовании",
|
||||
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
|
||||
@@ -245,14 +246,14 @@
|
||||
"Resume": "Возобновить",
|
||||
"Resume All": "Возобновить все",
|
||||
"Reused": "Повторно использовано",
|
||||
"Revert Local Changes": "Revert Local Changes",
|
||||
"Revert Local Changes": "Отменить изменения на этом компьютере",
|
||||
"Running": "Запущено",
|
||||
"Save": "Сохранить",
|
||||
"Scan Time Remaining": "Оставшееся время сканирования",
|
||||
"Scanning": "Сканирование",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
|
||||
"Select All": "Select All",
|
||||
"See external versioner help for supported templated command line parameters.": "Поддерживаемые шаблонные параметры командной строки см. в документации сторонней программы контроля версий",
|
||||
"See external versioning help for supported templated command line parameters.": "Поддерживаемые шаблонные параметры командной строки см. в документации сторонней программы контроля версий",
|
||||
"Select All": "Выбрать все",
|
||||
"Select a version": "Выберите версию",
|
||||
"Select latest version": "Выбрать последнюю версию",
|
||||
"Select oldest version": "Выбрать самую старую версию",
|
||||
@@ -267,7 +268,7 @@
|
||||
"Share With Devices": "Предоставить доступ устройствам",
|
||||
"Share this folder?": "Предоставить доступ к этой папке?",
|
||||
"Shared With": "Доступ предоставлен",
|
||||
"Sharing": "Sharing",
|
||||
"Sharing": "Предоставление доступа",
|
||||
"Show ID": "Показать ID",
|
||||
"Show QR": "Показать QR-код",
|
||||
"Show diff with previous version": "Показать различия с предыдущей версией",
|
||||
@@ -289,7 +290,7 @@
|
||||
"Statistics": "Статистика",
|
||||
"Stopped": "Остановлено",
|
||||
"Support": "Поддержка",
|
||||
"Support Bundle": "Support Bundle",
|
||||
"Support Bundle": "Данные для поддержки",
|
||||
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
|
||||
"Syncing": "Синхронизация",
|
||||
"Syncthing has been shut down.": "Syncthing был выключен.",
|
||||
@@ -298,7 +299,7 @@
|
||||
"Syncthing is upgrading.": "Обновление Syncthing.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Кажется, Syncthing не запущен или есть проблемы с подключением к Интернету. Переподключаюсь...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing столкнулся с проблемой при обработке Вашего запроса. Пожалуйста, обновите страницу или перезапустите Syncthing если проблема повторится.",
|
||||
"Take me back": "Take me back",
|
||||
"Take me back": "Вернуться к редактированию",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Эти изменения не вступят в силу, пока адрес панели управления переопределён в настройках запуска.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Административный интерфейс Syncthing настроен для предоставления удаленного доступа без пароля.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Агрегированные статистические данные общедоступны по ссылке ниже.",
|
||||
@@ -313,7 +314,7 @@
|
||||
"The folder path cannot be blank.": "Путь к папке не должен быть пустым.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Используются следующие интервалы: в первый час версия меняется каждые 30 секунд, в первый день - каждый час, первые 30 дней - каждый день, после, до максимального срока - каждую неделю.",
|
||||
"The following items could not be synchronized.": "Невозможно синхронизировать следующие объекты",
|
||||
"The following items were changed locally.": "The following items were changed locally.",
|
||||
"The following items were changed locally.": "Следующие объекты были изменены локально",
|
||||
"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).": "Минимальное свободное место на диске должно быть в процентах между 0 и 100 включительно.",
|
||||
@@ -334,8 +335,8 @@
|
||||
"Trash Can File Versioning": "Использовать версионность для файлов в Корзине",
|
||||
"Type": "Тип",
|
||||
"Unavailable": "Недоступно",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
|
||||
"Undecided (will prompt)": "Undecided (will prompt)",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Недоступно или отключено администратором",
|
||||
"Undecided (will prompt)": "Запрос каждый раз",
|
||||
"Unignore": "Не игнорировать",
|
||||
"Unknown": "Неизвестно",
|
||||
"Unshared": "Необщедоступно",
|
||||
@@ -349,18 +350,22 @@
|
||||
"Uptime": "Время работы",
|
||||
"Usage reporting is always enabled for candidate releases.": "Отправка отчётов об использовании всегда включена для кандидатов в релизы.",
|
||||
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
|
||||
"Use notifications from the filesystem to detect changed items.": "Использовать уведомления от файловой системы для обнаружения изменённых объектов.",
|
||||
"Variable Size Blocks": "Блоки переменного размера",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Блоки переменного размера (или \"большие блоки\") более эффективны для файлов большого размера.",
|
||||
"Version": "Версия",
|
||||
"Versions": "Версии",
|
||||
"Versions Path": "Путь к версиям",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Версии удаляются автоматически, если они существуют дольше максимального срока или превышают разрешённое количество файлов за интервал.",
|
||||
"Waiting to scan": "Waiting to scan",
|
||||
"Waiting to scan": "Ожидание сканирования",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolder}}».",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolderLabel}}» ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Осторожно, этот путь является подкаталогом существующей папки «{{otherFolder}}».",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Внимание! Этот путь — поддиректория уже существующей папки «{{otherFolderLabel}}» ({{otherFolder}}).",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Внимание! Если вы используете внешнюю программу для отслеживания изменений, например, {{syncthingInotify}}, убедитесь, что она отключена.",
|
||||
"Watch for Changes": "Следить за изменениями",
|
||||
"Watching for Changes": "Слежение за изменениями",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Отслеживание изменений обнаруживает большинство изменений без периодического сканирования.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Когда добавляете устройство, помните о том, что это же устройство должно быть добавлено и другой стороной.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
|
||||
"Yes": "Да",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Skopírované z originálu",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 následujúci prispivatelia:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 následujúci prispivatelia:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Vytváranie vzorov ignorovania, prepísanie existujúceho súboru v {{path}}.",
|
||||
"Danger!": "Pozor!",
|
||||
"Debugging Facilities": "Debugging Facilities",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Doba prevádzky",
|
||||
"Usage reporting is always enabled for candidate releases.": "Hlásenia o používaní sú pri kandidátoch na vydanie vždy povolené.",
|
||||
"Use HTTPS for GUI": "Použiť HTTPS pre grafické rozhranie",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Verzia",
|
||||
"Versions": "Verzie",
|
||||
"Versions Path": "Cesta k verziám",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
|
||||
"Watch for Changes": "Watch for Changes",
|
||||
"Watching for Changes": "Watching for Changes",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "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": "Áno",
|
||||
|
||||
@@ -56,11 +56,12 @@
|
||||
"Copied from original": "Kopierat från original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 följande bidragare:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 följande bidragande:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 följande bidragsgivare:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Skapa ignorera mönster, skriver över en existerande fil på {{path}}.",
|
||||
"Danger!": "Fara!",
|
||||
"Debugging Facilities": "Felsökningsanläggningar",
|
||||
"Default Folder Path": "Standard mappsökväg",
|
||||
"Deleted": "Tog bort",
|
||||
"Deleted": "Raderade",
|
||||
"Deselect All": "Avmarkera alla",
|
||||
"Device": "Enhet",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
|
||||
@@ -108,10 +109,10 @@
|
||||
"File Pull Order": "Filhämtningsprioritering",
|
||||
"File Versioning": "Filversionshantering",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras under sökning efter förändringar. Används på FAT-filsystem.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen vid byte eller tas bort av Syncthing.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen när de ersätts eller tas bort av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till datumstämplade versioner i en .stversions-mapp när de ersätts eller tas bort av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions mapp när de ersätts eller tas bort av Syncthing.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen vid byte eller raderas av Syncthing.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen när de ersätts eller raderas av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till datumstämplade versioner i en .stversions-mapp när de ersätts eller raderas av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersätts eller raderas av 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.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Filer synkroniseras från klustret, men alla ändringar som görs lokalt skickas inte till andra enheter.",
|
||||
"Filesystem Notifications": "filsystemsnotifieringar",
|
||||
@@ -187,7 +188,7 @@
|
||||
"Newest First": "Nyast först",
|
||||
"No": "Nej",
|
||||
"No File Versioning": "Ingen filversionshantering",
|
||||
"No files will be deleted as a result of this operation.": "Inga filer kommer att tas bort till följd av denna operation.",
|
||||
"No files will be deleted as a result of this operation.": "Inga filer kommer att raderas till följd av denna operation.",
|
||||
"No upgrades": "Inga uppgraderingar",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Observera",
|
||||
@@ -203,7 +204,7 @@
|
||||
"Path": "Sökväg",
|
||||
"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": "Sökväg till mappen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Sökvägen där nya automatiskt accepterade mappar kommer att skapas, liksom den föreslagna sökvägen när du lägger till nya mappar via gränssnittet. Tecknet tilde (~) expanderar till {{tilde}}.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Sökväg där versioner ska lagras (lämna tomt för standard .stversions-katalogen i den delade katalogen).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Sökväg där versioner ska lagras (lämna tomt för standard .stversions-mappen i den delade katalogen).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna tomt för att använda standard .stversions-mappen i mappen).",
|
||||
"Pause": "Paus",
|
||||
"Pause All": "Pausa alla",
|
||||
@@ -211,12 +212,12 @@
|
||||
"Pending changes": "Väntar på ändringar",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Periodisk uppdatering i givet intervall och inaktiverad spaning efter ändringar",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Periodisk uppdatering i givet intervall och aktiverad spaning efter ändringar",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk uppdatering i givet intervall och misslyckades med att ställa in spaning efter ändringar, försök igen var 1m:",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk uppdatering i givet intervall och misslyckades med att ställa in utkik efter ändringar, försök igen var 1m:",
|
||||
"Permissions": "Behörigheter",
|
||||
"Please consult the release notes before performing a major upgrade.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ställ in ett grafiska gränssnittets användarautentisering och lösenord i inställningsdialogrutan.",
|
||||
"Please wait": "Var god vänta",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix som indikerar att filen kan raderas om det förhindrar borttagning av katalog",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix som indikerar att filen kan raderas om det förhindrar radering av katalog",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix som indikerar att mönstret ska matchas utan skiftlägeskänslighet",
|
||||
"Preview": "Förhandsgranska",
|
||||
"Preview Usage Report": "Förhandsgranska statistik",
|
||||
@@ -349,10 +350,13 @@
|
||||
"Uptime": "Drifttid",
|
||||
"Usage reporting is always enabled for candidate releases.": "Användningsrapportering är alltid aktiverad för kandidatutgåvor.",
|
||||
"Use HTTPS for GUI": "Använd HTTPS för gränssnittet",
|
||||
"Use notifications from the filesystem to detect changed items.": "Använd aviseringar från filsystemet för att upptäcka ändrade objekt.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Version",
|
||||
"Versions": "Versioner",
|
||||
"Versions Path": "Sökväg för versioner",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner tas bort automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i intervallet.",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner raderas automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i intervallet.",
|
||||
"Waiting to scan": "Väntar på uppdatering",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en överordnad mapp av en befintlig mapp \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Varning, denna sökväg är en överordnad mapp av en befintlig mapp \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Varning: Om du använder en extern bevakare som {{syncthingInotify}}, bör du se till att den är inaktiverad.",
|
||||
"Watch for Changes": "Håll utkik efter ändringar",
|
||||
"Watching for Changes": "Håller utkik efter ändringar",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Hålla utkik efter ändringar upptäcker de flesta förändringar utan periodisk uppdatering.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet, kom ihåg att den här enheten måste läggas till på den andra enheten också.",
|
||||
"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 mapp, tänk på att mapp-ID knyter ihop mappar mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
|
||||
"Yes": "Ja",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "Скопійовано з оригіналу",
|
||||
"Copyright © 2014-2016 the following Contributors:": "© 2014-2016 Всі права застережено, вклад внесли:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "© 2014-2017 Всі права застережено, вклад внесли:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Створення шаблонів винятків з перезаписом існуючого файлу {{path}}.",
|
||||
"Danger!": "Небезпечно!",
|
||||
"Debugging Facilities": "Засоби відладки",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "Тривалість роботи",
|
||||
"Usage reporting is always enabled for candidate releases.": "Звіти про користування завжди увімкнені для реліз-кандидатів.",
|
||||
"Use HTTPS for GUI": "Використовувати HTTPS для доступу до панелі управління",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "Версія",
|
||||
"Versions": "Версії",
|
||||
"Versions Path": "Шлях до версій",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
|
||||
"Watch for Changes": "Моніторити зміни",
|
||||
"Watching for Changes": "Моніторинг щмін",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Коли додаєте новий вузол, пам’ятайте, що цей вузол повинен бути доданий і на іншій стороні.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між пристроями. Назви повинні точно співпадати між усіма пристроями, регістр символів має значення.",
|
||||
"Yes": "Так",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "从源复制",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 以下贡献者:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 以下贡献者:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "版权所有 © 2014-2019 以下贡献者:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "正在创建忽略模式,覆盖位于 {{path}} 的已有文件。",
|
||||
"Danger!": "危险!",
|
||||
"Debugging Facilities": "调试功能",
|
||||
@@ -289,7 +290,7 @@
|
||||
"Statistics": "统计",
|
||||
"Stopped": "已停止",
|
||||
"Support": "支持",
|
||||
"Support Bundle": "Support Bundle",
|
||||
"Support Bundle": "支持捆绑包",
|
||||
"Sync Protocol Listen Addresses": "协议监听地址",
|
||||
"Syncing": "同步中",
|
||||
"Syncthing has been shut down.": "Syncthing 已关闭。",
|
||||
@@ -336,7 +337,7 @@
|
||||
"Unavailable": "无效",
|
||||
"Unavailable/Disabled by administrator or maintainer": "无效/禁用(由管理员或维护者)",
|
||||
"Undecided (will prompt)": "待定(将提示)",
|
||||
"Unignore": "屏蔽",
|
||||
"Unignore": "解除忽略",
|
||||
"Unknown": "未知",
|
||||
"Unshared": "未共享",
|
||||
"Unused": "未使用",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "已启动",
|
||||
"Usage reporting is always enabled for candidate releases.": "发布候选版总是会启用使用报告。",
|
||||
"Use HTTPS for GUI": "使用加密连接到图形管理页面",
|
||||
"Use notifications from the filesystem to detect changed items.": "使用文件系统的通知来检测更改的项目。",
|
||||
"Variable Size Blocks": "可变大小块",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "可变大小块(既“较大块”)对于大文件更有效率。",
|
||||
"Version": "版本",
|
||||
"Versions": "历史版本",
|
||||
"Versions Path": "历史版本路径",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "警告:如果你在使用外部的监视器如 {{syncthingInotify}},你应该确保它已经取消激活。",
|
||||
"Watch for Changes": "监视更改",
|
||||
"Watching for Changes": "正在监视更改",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "对更改的监视无需定期扫描就可以发现大多数更改。",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "若您在本机添加新设备,记住您也必须在这个设备上添加本机。",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "若你添加了新文件夹,记住文件夹 ID 是用以在不同设备间建立联系的。在不同设备间拥有相同 ID 的文件夹将会被同步。且文件夹 ID 区分大小写。",
|
||||
"Yes": "是",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"Copied from original": "從原處複製",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 下列貢獻者:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 下列貢獻者:",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "建立忽略樣式,覆蓋已存在的 {{path}}。",
|
||||
"Danger!": "危險!",
|
||||
"Debugging Facilities": "除錯工具",
|
||||
@@ -349,6 +350,9 @@
|
||||
"Uptime": "上線時間",
|
||||
"Usage reporting is always enabled for candidate releases.": "發行候選版永遠啟用使用數據回報。",
|
||||
"Use HTTPS for GUI": "為 GUI 使用 HTTPS",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Variable Size Blocks": "Variable Size Blocks",
|
||||
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
|
||||
"Version": "版本",
|
||||
"Versions": "版本",
|
||||
"Versions Path": "歷史版本路徑",
|
||||
@@ -361,6 +365,7 @@
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "警告:如果您正在使用外部監視工具,如 {{syncthingInotify}},您應該確認已經將其關閉。",
|
||||
"Watch for Changes": "監視變動",
|
||||
"Watching for Changes": "正在監視變動",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "當新增一個裝置時,務必記住,當前的這個裝置也同樣必須被添加至另一邊。",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "當新增一個資料夾時,請記住,資料夾識別碼是用來將裝置之間的資料夾綁定在一起的。它們有區分大小寫,且必須在所有裝置之間完全相同。",
|
||||
"Yes": "是",
|
||||
|
||||
@@ -1 +1 @@
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","fi":"Finnish","fr":"French","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","fi":"Finnish","fr":"French","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","es","es-ES","fi","fr","fy","hu","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ru","sk","sv","uk","zh-CN","zh-TW"]
|
||||
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","eo","es","es-ES","fi","fr","fy","hu","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ru","sk","sv","uk","zh-CN","zh-TW"]
|
||||
|
||||
@@ -328,6 +328,7 @@
|
||||
<span ng-show="syncRemaining(folder.id)">({{syncPercentage(folder.id) | percent}}, {{syncRemaining(folder.id) | binary}}B)</span>
|
||||
</span>
|
||||
<span ng-switch-when="outofsync"><span class="hidden-xs" translate>Out of Sync</span><span class="visible-xs" aria-label="{{'Out of Sync' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
|
||||
<span ng-switch-when="faileditems"><span class="hidden-xs" translate>Failed Items</span><span class="visible-xs" aria-label="{{'Failed Items' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
|
||||
</div>
|
||||
<div class="panel-title-text">
|
||||
<span tooltip data-original-title="{{folder.label.length != 0 ? folder.id : ''}}">{{folder.label.length != 0 ? folder.label : folder.id}}</span>
|
||||
@@ -709,7 +710,7 @@
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-link"></span> <span translate>Address</span></th>
|
||||
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }}">
|
||||
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }} {{ connections[deviceCfg.deviceID].crypto }}">
|
||||
{{deviceAddr(deviceCfg)}}
|
||||
</span>
|
||||
</td>
|
||||
@@ -740,7 +741,7 @@
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.introducedBy">
|
||||
<th><span class="far fa-fw fa-meh"></span> <span translate>Introduced By</span></th>
|
||||
<th><span class="far fa-fw fa-handshake-o"></span> <span translate>Introduced By</span></th>
|
||||
<td class="text-right">{{ deviceName(findDevice(deviceCfg.introducedBy)) || deviceCfg.introducedBy.substring(0, 5) }}</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
</h1>
|
||||
<hr />
|
||||
|
||||
<p translate>Copyright © 2014-2017 the following Contributors:</p>
|
||||
<p translate>Syncthing is Free and Open Source Software licensed as MPL v2.0.</p>
|
||||
<hr />
|
||||
<p translate>Copyright © 2014-2019 the following Contributors:</p>
|
||||
<div class="row">
|
||||
<div class="col-md-12" id="contributor-list">
|
||||
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, Aaron Bieber, Adam Piggott, Adel Qalieh, Alessandro G., Andrew Dunham, Andrew Rabert, Andrey D, Antoine Lamielle, Aranjedeath, Arthur Axel fREW Schmidt, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Dale Visser, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Han Boetes, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Iskander (Alex) Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matic Potočnik, Matt Burke, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Tilli, Mike Boone, MikeLund, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Niels Peter Roest, Nils Jakobi, NoLooseEnds, Oyebanji Jacob Mayowa, Pascal Jungblut, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Roman Zaynetdinov, Ross Smith II, Sacheendra Talluri, Scott Klupfel, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tomas Cerveny, Tommy Thorn, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, chucic, derekriemer, desbma, janost, jaseg, klemens, marco-m, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
|
||||
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, Aaron Bieber, Adam Piggott, Adel Qalieh, Alessandro G., Andrew Dunham, Andrew Rabert, Andrey D, Antoine Lamielle, Aranjedeath, Arthur Axel fREW Schmidt, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Dale Visser, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Evgeny Kuznetsov, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Han Boetes, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Tilli, Mike Boone, MikeLund, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Niels Peter Roest, Nils Jakobi, NoLooseEnds, Oyebanji Jacob Mayowa, Pascal Jungblut, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Roman Zaynetdinov, Ross Smith II, Sacheendra Talluri, Scott Klupfel, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tomas Cerveny, Tommy Thorn, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, chucic, derekriemer, desbma, georgespatton, janost, jaseg, klemens, marco-m, otbutz, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
@@ -20,21 +22,47 @@ Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, And
|
||||
<p translate>Syncthing includes the following software or portions thereof:</p>
|
||||
<ul class="list-unstyled two-columns" id="copyright-notices">
|
||||
<li><a href="http://getbootstrap.com/">Bootstrap</a>, Copyright © 2011-2016 Twitter, Inc.</li>
|
||||
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright © 2010-2016 Google, Inc.</li>
|
||||
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright © 2010-2014, 2016 Google, Inc.</li>
|
||||
<li><a href="http://www.daterangepicker.com/">Date Range Picker</a>, Copyright © 2012-2018 Dan Grossman.</li>
|
||||
<li><a href="https://github.com/mar10/fancytree">JQuery Fancytree Plugin</a>, Copyright © 2008-2018 Martin Wendt.</li>
|
||||
<li><a href="https://forkaweso.me/Fork-Awesome/">Fork Awesome</a>, Copyright © 2018 Dave Gandy & Fork Awesome.</li>
|
||||
<li><a href="http://jquery.com/">jQuery JavaScript Library</a>, Copyright © jQuery Foundation and other contributors.</li>
|
||||
<li><a href="http://momentjs.com/">moment.js</a>, Copyright © JS Foundation and other contributors.</li>
|
||||
<li><a href="https://prometheus.io/">Prometheus</a>, Copyright © 2012-2015 The Prometheus Authors.</li>
|
||||
<li><a href="https://github.com/AudriusButkevicius/go-nat-pmp">AudriusButkevicius/go-nat-pmp</a>, Copyright © 2013 John Howard Palevich.</li>
|
||||
<li><a href="https://github.com/AudriusButkevicius/recli">AudriusButkevicius/recli</a>, Copyright © 2019 Audrius Butkevicius.</li>
|
||||
<li><a href="https://github.com/beorn7/perks">beorn7/perks</a>, Copyright © 2013 Blake Mizerany.</li>
|
||||
<li><a href="https://github.com/bkaradzic/go-lz4">bkaradzic/go-lz4</a>, Copyright © 2011-2012 Branimir Karadzic, 2013 Damian Gryski.</li>
|
||||
<li><a href="https://github.com/chmduquesne/rollinghash">chmduquesne/rollinghash</a>, Copyright © 2015 Christophe-Marie Duquesne</li>
|
||||
<li><a href="https://github.com/calmh/du">calmh/du</a>, Public domain.</li>
|
||||
<li><a href="https://github.com/calmh/xdr">calmh/xdr</a>, Copyright © 2014 Jakob Borg.</li>
|
||||
<li><a href="https://github.com/chmduquesne/rollinghash">chmduquesne/rollinghash</a>, Copyright © 2015 Christophe-Marie Duquesne.</li>
|
||||
<li><a href="https://github.com/d4l3k/messagediff">d4l3k/messagediff</a>, Copyright © 2015 Tristan Rice.</li>
|
||||
<li><a href="https://github.com/flynn-archive/go-shlex">flynn-archive/go-shlex</a>, Copyright © 2012 Google Inc.</li>
|
||||
<li><a href="https://github.com/gobwas/glob">gobwas/glob</a>, Copyright © 2016 Sergey Kamardin.</li>
|
||||
<li><a href="https://github.com/gogo/protobuf">gogo/protobuf</a>, Copyright © 2013 The GoGo Authors.</li>
|
||||
<li><a href="https://github.com/golang/groupcache">golang/groupcache</a>, Copyright © 2013 Google Inc.</li>
|
||||
<li><a href="https://github.com/golang/protobuf">golang/protobuf</a>, Copyright © 2010 The Go Authors.</li>
|
||||
<li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright © 2011 The Snappy-Go Authors.</li>
|
||||
<li><a href="https://github.com/jackpal/gateway">jackpal/gateway</a>, Copyright © 2010 Jack Palevich.</li>
|
||||
<li><a href="https://github.com/minio/sha256-simd">minio/sha256-simd</a>, Copyright © 2016 Minio, Inc.</li>
|
||||
<li><a href="https://github.com/kballard/go-shellquote">kballard/go-shellquote</a>, Copyright © 2014 Kevin Ballard.</li>
|
||||
<li><a href="https://github.com/lib/pq">lib/pq</a>, Copyright © 2011-2013, 'pq' Contributors, portions Copyright © 2011 Blake Mizerany.</li>
|
||||
<li><a href="https://github.com/mattn/go-isatty">mattn/go-isatty</a>, Copyright © Yasuhiro MATSUMOTO.</li>
|
||||
<li><a href="https://github.com/matttproud/golang_protobuf_extensions">matttproud/golang_protobuf_extensions</a>, Copyright © 2012 Matt T. Proud.</li>
|
||||
<li><a href="https://github.com/minio/sha256-simd">minio/sha256-simd</a>, Copyright © 2016-2017 Minio, Inc.</li>
|
||||
<li><a href="https://github.com/oschwald/geoip2-golang">oschwald/geoip2-golang</a>, Copyright © 2015, Gregory J. Oschwald.</li>
|
||||
<li><a href="https://github.com/oschwald/maxminddb-golang">oschwald/maxminddb-golang</a>, Copyright © 2015, Gregory J. Oschwald.</li>
|
||||
<li><a href="https://github.com/petermattis/goid">petermattis/goid</a>, Copyright © 2015-2016 Peter Mattis.</li>
|
||||
<li><a href="https://github.com/pkg/errors">pkg/errors</a>, Copyright © 2015, Dave Cheney.</li>
|
||||
<li><a href="https://github.com/rcrowley/go-metrics">rcrowley/go-metrics</a>, Copyright © 2012 Richard Crowley.</li>
|
||||
<li><a href="https://github.com/sasha-s/go-deadlock">sasha-s/go-deadlock</a>, Copyright © 2016 sasha-s</li>
|
||||
<li><a href="https://github.com/sasha-s/go-deadlock">sasha-s/go-deadlock</a>, Copyright © 2016 sasha-s.</li>
|
||||
<li><a href="https://github.com/syncthing/notify">syncthing/notify</a>, Copyright © 2014-2015 The Notify Authors.</li>
|
||||
<li><a href="https://github.com/syndtr/goleveldb">syndtr/goleveldb</a>, Copyright © 2012 Suryandaru Triandana.</li>
|
||||
<li><a href="https://github.com/thejerf/suture">thejerf/suture</a>, Copyright © 2014-2015 Barracuda Networks, Inc.</li>
|
||||
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright © The Go Authors.</li>
|
||||
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright © 2012 The Go Authors.</li>
|
||||
<li><a href="https://github.com/urfave/cli">urfave/cli</a>, Copyright © 2016 Jeremy Saenz & Contributors.</li>
|
||||
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright © 2010-2011 The Go Authors.</li>
|
||||
<li><a href="https://gopkg.in/asn1-ber.v1">gopkg.in/asn1-ber.v1</a>, Copyright © 2011-2015 Michael Mitton, portions Copyright © 2015-2016 go-asn1-ber Authors.</li>
|
||||
<li><a href="https://gopkg.in/ldap.v2">gopkg.in/ldap.v2</a>, Copyright © 2011-2015 Michael Mitton, portions Copyright © 2015-2016 go-ldap Authors.</li>
|
||||
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright © 2009 The Go Authors.</li>
|
||||
<li>Font Awesome by Dave Gandy - <a href="http://fontawesome.io/">http://fontawesome.io</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -751,6 +751,9 @@ angular.module('syncthing.core')
|
||||
if (state === 'idle' && $scope.model[folderCfg.id].needTotalItems > 0) {
|
||||
return 'outofsync';
|
||||
}
|
||||
if ($scope.hasFailedFiles(folderCfg.id)) {
|
||||
return 'faileditems';
|
||||
}
|
||||
if (state === 'scanning') {
|
||||
return state;
|
||||
}
|
||||
@@ -777,7 +780,7 @@ angular.module('syncthing.core')
|
||||
if (status === 'unknown') {
|
||||
return 'info';
|
||||
}
|
||||
if (status === 'stopped' || status === 'outofsync' || status === 'error') {
|
||||
if (status === 'stopped' || status === 'outofsync' || status === 'error' || status === 'faileditems') {
|
||||
return 'danger';
|
||||
}
|
||||
if (status === 'unshared' || status === 'scan-waiting') {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -40,142 +40,104 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/stats"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
"github.com/syncthing/syncthing/lib/ur"
|
||||
"github.com/thejerf/suture"
|
||||
"github.com/vitrun/qart/qr"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
startTime = time.Now()
|
||||
|
||||
// matches a bcrypt hash and not too much else
|
||||
bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
|
||||
)
|
||||
// matches a bcrypt hash and not too much else
|
||||
var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
|
||||
|
||||
const (
|
||||
defaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
|
||||
diskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
|
||||
eventSubBufferSize = 1000
|
||||
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
|
||||
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
|
||||
EventSubBufferSize = 1000
|
||||
defaultEventTimeout = time.Minute
|
||||
)
|
||||
|
||||
type apiService struct {
|
||||
id protocol.DeviceID
|
||||
cfg configIntf
|
||||
httpsCertFile string
|
||||
httpsKeyFile string
|
||||
statics *staticsServer
|
||||
model modelIntf
|
||||
eventSubs map[events.EventType]events.BufferedSubscription
|
||||
eventSubsMut sync.Mutex
|
||||
discoverer discover.CachingMux
|
||||
connectionsService connectionsIntf
|
||||
fss *folderSummaryService
|
||||
systemConfigMut sync.Mutex // serializes posts to /rest/system/config
|
||||
stop chan struct{} // signals intentional stop
|
||||
configChanged chan struct{} // signals intentional listener close due to config change
|
||||
started chan string // signals startup complete by sending the listener address, for testing only
|
||||
startedOnce chan struct{} // the service has started successfully at least once
|
||||
cpu rater
|
||||
type service struct {
|
||||
id protocol.DeviceID
|
||||
cfg config.Wrapper
|
||||
statics *staticsServer
|
||||
model model.Model
|
||||
eventSubs map[events.EventType]events.BufferedSubscription
|
||||
eventSubsMut sync.Mutex
|
||||
discoverer discover.CachingMux
|
||||
connectionsService connections.Service
|
||||
fss model.FolderSummaryService
|
||||
urService *ur.Service
|
||||
systemConfigMut sync.Mutex // serializes posts to /rest/system/config
|
||||
cpu Rater
|
||||
contr Controller
|
||||
noUpgrade bool
|
||||
tlsDefaultCommonName string
|
||||
stop chan struct{} // signals intentional stop
|
||||
configChanged chan struct{} // signals intentional listener close due to config change
|
||||
started chan string // signals startup complete by sending the listener address, for testing only
|
||||
startedOnce chan struct{} // the service has started successfully at least once
|
||||
startupErr error
|
||||
|
||||
guiErrors logger.Recorder
|
||||
systemLog logger.Recorder
|
||||
}
|
||||
|
||||
type modelIntf interface {
|
||||
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
|
||||
Completion(device protocol.DeviceID, folder string) model.FolderCompletion
|
||||
Override(folder string)
|
||||
Revert(folder string)
|
||||
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
|
||||
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
|
||||
LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated
|
||||
NeedSize(folder string) db.Counts
|
||||
ConnectionStats() map[string]interface{}
|
||||
DeviceStatistics() map[string]stats.DeviceStatistics
|
||||
FolderStatistics() map[string]stats.FolderStatistics
|
||||
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
|
||||
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
|
||||
ResetFolder(folder string)
|
||||
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []model.Availability
|
||||
GetIgnores(folder string) ([]string, []string, error)
|
||||
GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
|
||||
RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
|
||||
SetIgnores(folder string, content []string) error
|
||||
DelayScan(folder string, next time.Duration)
|
||||
ScanFolder(folder string) error
|
||||
ScanFolders() map[string]error
|
||||
ScanFolderSubdirs(folder string, subs []string) error
|
||||
BringToFront(folder, file string)
|
||||
Connection(deviceID protocol.DeviceID) (connections.Connection, bool)
|
||||
GlobalSize(folder string) db.Counts
|
||||
LocalSize(folder string) db.Counts
|
||||
ReceiveOnlyChangedSize(folder string) db.Counts
|
||||
CurrentSequence(folder string) (int64, bool)
|
||||
RemoteSequence(folder string) (int64, bool)
|
||||
State(folder string) (string, time.Time, error)
|
||||
UsageReportingStats(version int, preview bool) map[string]interface{}
|
||||
FolderErrors(folder string) ([]model.FileError, error)
|
||||
WatchError(folder string) error
|
||||
}
|
||||
|
||||
type configIntf interface {
|
||||
GUI() config.GUIConfiguration
|
||||
LDAP() config.LDAPConfiguration
|
||||
RawCopy() config.Configuration
|
||||
Options() config.OptionsConfiguration
|
||||
Replace(cfg config.Configuration) (config.Waiter, error)
|
||||
Subscribe(c config.Committer)
|
||||
Folders() map[string]config.FolderConfiguration
|
||||
Devices() map[protocol.DeviceID]config.DeviceConfiguration
|
||||
SetDevice(config.DeviceConfiguration) (config.Waiter, error)
|
||||
SetDevices([]config.DeviceConfiguration) (config.Waiter, error)
|
||||
Save() error
|
||||
ListenAddresses() []string
|
||||
RequiresRestart() bool
|
||||
}
|
||||
|
||||
type connectionsIntf interface {
|
||||
Status() map[string]interface{}
|
||||
NATType() string
|
||||
}
|
||||
|
||||
type rater interface {
|
||||
type Rater interface {
|
||||
Rate() float64
|
||||
}
|
||||
|
||||
func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder, cpu rater) *apiService {
|
||||
service := &apiService{
|
||||
id: id,
|
||||
cfg: cfg,
|
||||
httpsCertFile: httpsCertFile,
|
||||
httpsKeyFile: httpsKeyFile,
|
||||
statics: newStaticsServer(cfg.GUI().Theme, assetDir),
|
||||
model: m,
|
||||
eventSubs: map[events.EventType]events.BufferedSubscription{
|
||||
defaultEventMask: defaultSub,
|
||||
diskEventMask: diskSub,
|
||||
},
|
||||
eventSubsMut: sync.NewMutex(),
|
||||
discoverer: discoverer,
|
||||
connectionsService: connectionsService,
|
||||
systemConfigMut: sync.NewMutex(),
|
||||
stop: make(chan struct{}),
|
||||
configChanged: make(chan struct{}),
|
||||
startedOnce: make(chan struct{}),
|
||||
guiErrors: errors,
|
||||
systemLog: systemLog,
|
||||
cpu: cpu,
|
||||
}
|
||||
|
||||
return service
|
||||
type Controller interface {
|
||||
ExitUpgrading()
|
||||
Restart()
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
|
||||
cert, err := tls.LoadX509KeyPair(s.httpsCertFile, s.httpsKeyFile)
|
||||
type Service interface {
|
||||
suture.Service
|
||||
config.Committer
|
||||
WaitForStart() error
|
||||
}
|
||||
|
||||
func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, cpu Rater, contr Controller, noUpgrade bool) Service {
|
||||
return &service{
|
||||
id: id,
|
||||
cfg: cfg,
|
||||
statics: newStaticsServer(cfg.GUI().Theme, assetDir),
|
||||
model: m,
|
||||
eventSubs: map[events.EventType]events.BufferedSubscription{
|
||||
DefaultEventMask: defaultSub,
|
||||
DiskEventMask: diskSub,
|
||||
},
|
||||
eventSubsMut: sync.NewMutex(),
|
||||
discoverer: discoverer,
|
||||
connectionsService: connectionsService,
|
||||
fss: fss,
|
||||
urService: urService,
|
||||
systemConfigMut: sync.NewMutex(),
|
||||
guiErrors: errors,
|
||||
systemLog: systemLog,
|
||||
cpu: cpu,
|
||||
contr: contr,
|
||||
noUpgrade: noUpgrade,
|
||||
tlsDefaultCommonName: tlsDefaultCommonName,
|
||||
stop: make(chan struct{}),
|
||||
configChanged: make(chan struct{}),
|
||||
startedOnce: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) WaitForStart() error {
|
||||
<-s.startedOnce
|
||||
return s.startupErr
|
||||
}
|
||||
|
||||
func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
|
||||
httpsCertFile := locations.Get(locations.HTTPSCertFile)
|
||||
httpsKeyFile := locations.Get(locations.HTTPSKeyFile)
|
||||
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
|
||||
if err != nil {
|
||||
l.Infoln("Loading HTTPS certificate:", err)
|
||||
l.Infoln("Creating new HTTPS certificate")
|
||||
@@ -185,10 +147,10 @@ func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener,
|
||||
var name string
|
||||
name, err = os.Hostname()
|
||||
if err != nil {
|
||||
name = tlsDefaultCommonName
|
||||
name = s.tlsDefaultCommonName
|
||||
}
|
||||
|
||||
cert, err = tlsutil.NewCertificate(s.httpsCertFile, s.httpsKeyFile, name)
|
||||
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -228,7 +190,7 @@ func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
|
||||
fmt.Fprintf(w, "%s\n", bs)
|
||||
}
|
||||
|
||||
func (s *apiService) Serve() {
|
||||
func (s *service) Serve() {
|
||||
listener, err := s.getListener(s.cfg.GUI())
|
||||
if err != nil {
|
||||
select {
|
||||
@@ -236,14 +198,15 @@ func (s *apiService) Serve() {
|
||||
// We let this be a loud user-visible warning as it may be the only
|
||||
// indication they get that the GUI won't be available.
|
||||
l.Warnln("Starting API/GUI:", err)
|
||||
return
|
||||
|
||||
default:
|
||||
// This is during initialization. A failure here should be fatal
|
||||
// as there will be no way for the user to communicate with us
|
||||
// otherwise anyway.
|
||||
l.Fatalln("Starting API/GUI:", err)
|
||||
s.startupErr = err
|
||||
close(s.startedOnce)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if listener == nil {
|
||||
@@ -254,6 +217,9 @@ func (s *apiService) Serve() {
|
||||
|
||||
defer listener.Close()
|
||||
|
||||
s.cfg.Subscribe(s)
|
||||
defer s.cfg.Unsubscribe(s)
|
||||
|
||||
// The GET handlers
|
||||
getRestMux := http.NewServeMux()
|
||||
getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
|
||||
@@ -369,10 +335,6 @@ func (s *apiService) Serve() {
|
||||
ReadTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
s.fss = newFolderSummaryService(s.cfg, s.model)
|
||||
defer s.fss.Stop()
|
||||
s.fss.ServeBackground()
|
||||
|
||||
l.Infoln("GUI and API listening on", listener.Addr())
|
||||
l.Infoln("Access the GUI via the following URL:", guiCfg.URL())
|
||||
if s.started != nil {
|
||||
@@ -410,15 +372,28 @@ func (s *apiService) Serve() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) Stop() {
|
||||
// Complete implements suture.IsCompletable, which signifies to the supervisor
|
||||
// whether to stop restarting the service.
|
||||
func (s *service) Complete() bool {
|
||||
select {
|
||||
case <-s.startedOnce:
|
||||
return s.startupErr != nil
|
||||
case <-s.stop:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *service) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
func (s *apiService) String() string {
|
||||
return fmt.Sprintf("apiService@%p", s)
|
||||
func (s *service) String() string {
|
||||
return fmt.Sprintf("api.service@%p", s)
|
||||
}
|
||||
|
||||
func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
|
||||
func (s *service) VerifyConfiguration(from, to config.Configuration) error {
|
||||
if to.GUI.Network() != "tcp" {
|
||||
return nil
|
||||
}
|
||||
@@ -426,7 +401,7 @@ func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
|
||||
func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// No action required when this changes, so mask the fact that it changed at all.
|
||||
from.GUI.Debugging = to.GUI.Debugging
|
||||
|
||||
@@ -478,7 +453,7 @@ func debugMiddleware(h http.Handler) http.Handler {
|
||||
written = rf.Int()
|
||||
}
|
||||
}
|
||||
httpl.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
|
||||
l.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -586,7 +561,7 @@ func localhostMiddleware(h http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) whenDebugging(h http.Handler) http.Handler {
|
||||
func (s *service) whenDebugging(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.cfg.GUI().Debugging {
|
||||
h.ServeHTTP(w, r)
|
||||
@@ -597,11 +572,11 @@ func (s *apiService) whenDebugging(h http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) restPing(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string]string{"ping": "pong"})
|
||||
}
|
||||
|
||||
func (s *apiService) getJSMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getJSMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
meta, _ := json.Marshal(map[string]string{
|
||||
"deviceID": s.id.String(),
|
||||
})
|
||||
@@ -609,7 +584,7 @@ func (s *apiService) getJSMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "var metadata = %s;\n", meta)
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string]interface{}{
|
||||
"version": build.Version,
|
||||
"codename": build.Codename,
|
||||
@@ -622,7 +597,7 @@ func (s *apiService) getSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
names := l.Facilities()
|
||||
enabled := l.FacilityDebugging()
|
||||
sort.Strings(enabled)
|
||||
@@ -632,7 +607,7 @@ func (s *apiService) getSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) 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"), ",") {
|
||||
@@ -651,7 +626,7 @@ func (s *apiService) postSystemDebug(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) getDBBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
prefix := qs.Get("prefix")
|
||||
@@ -665,7 +640,7 @@ func (s *apiService) getDBBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly))
|
||||
}
|
||||
|
||||
func (s *apiService) getDBCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var folder = qs.Get("folder")
|
||||
var deviceStr = qs.Get("device")
|
||||
@@ -676,100 +651,26 @@ func (s *apiService) getDBCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
sendJSON(w, jsonCompletion(s.model.Completion(device, folder)))
|
||||
sendJSON(w, s.model.Completion(device, folder).Map())
|
||||
}
|
||||
|
||||
func jsonCompletion(comp model.FolderCompletion) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"completion": comp.CompletionPct,
|
||||
"needBytes": comp.NeedBytes,
|
||||
"needItems": comp.NeedItems,
|
||||
"globalBytes": comp.GlobalBytes,
|
||||
"needDeletes": comp.NeedDeletes,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBStatus(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
if sum, err := folderSummary(s.cfg, s.model, folder); err != nil {
|
||||
if sum, err := s.fss.Summary(folder); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
sendJSON(w, sum)
|
||||
}
|
||||
}
|
||||
|
||||
func folderSummary(cfg configIntf, m modelIntf, folder string) (map[string]interface{}, error) {
|
||||
var res = make(map[string]interface{})
|
||||
|
||||
errors, err := m.FolderErrors(folder)
|
||||
if err != nil && err != model.ErrFolderPaused {
|
||||
// Stats from the db can still be obtained if the folder is just paused
|
||||
return nil, err
|
||||
}
|
||||
res["errors"] = len(errors)
|
||||
res["pullErrors"] = len(errors) // deprecated
|
||||
|
||||
res["invalid"] = "" // Deprecated, retains external API for now
|
||||
|
||||
global := m.GlobalSize(folder)
|
||||
res["globalFiles"], res["globalDirectories"], res["globalSymlinks"], res["globalDeleted"], res["globalBytes"], res["globalTotalItems"] = global.Files, global.Directories, global.Symlinks, global.Deleted, global.Bytes, global.TotalItems()
|
||||
|
||||
local := m.LocalSize(folder)
|
||||
res["localFiles"], res["localDirectories"], res["localSymlinks"], res["localDeleted"], res["localBytes"], res["localTotalItems"] = local.Files, local.Directories, local.Symlinks, local.Deleted, local.Bytes, local.TotalItems()
|
||||
|
||||
need := m.NeedSize(folder)
|
||||
res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"], res["needTotalItems"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes, need.TotalItems()
|
||||
|
||||
if cfg.Folders()[folder].Type == config.FolderTypeReceiveOnly {
|
||||
// Add statistics for things that have changed locally in a receive
|
||||
// only folder.
|
||||
ro := m.ReceiveOnlyChangedSize(folder)
|
||||
res["receiveOnlyChangedFiles"] = ro.Files
|
||||
res["receiveOnlyChangedDirectories"] = ro.Directories
|
||||
res["receiveOnlyChangedSymlinks"] = ro.Symlinks
|
||||
res["receiveOnlyChangedDeletes"] = ro.Deleted
|
||||
res["receiveOnlyChangedBytes"] = ro.Bytes
|
||||
res["receiveOnlyTotalItems"] = ro.TotalItems()
|
||||
}
|
||||
|
||||
res["inSyncFiles"], res["inSyncBytes"] = global.Files-need.Files, global.Bytes-need.Bytes
|
||||
|
||||
res["state"], res["stateChanged"], err = m.State(folder)
|
||||
if err != nil {
|
||||
res["error"] = err.Error()
|
||||
}
|
||||
|
||||
ourSeq, _ := m.CurrentSequence(folder)
|
||||
remoteSeq, _ := m.RemoteSequence(folder)
|
||||
|
||||
res["version"] = ourSeq + remoteSeq // legacy
|
||||
res["sequence"] = ourSeq + remoteSeq // new name
|
||||
|
||||
ignorePatterns, _, _ := m.GetIgnores(folder)
|
||||
res["ignorePatterns"] = false
|
||||
for _, line := range ignorePatterns {
|
||||
if len(line) > 0 && !strings.HasPrefix(line, "//") {
|
||||
res["ignorePatterns"] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = m.WatchError(folder)
|
||||
if err != nil {
|
||||
res["watchError"] = err.Error()
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *apiService) postDBOverride(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postDBOverride(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var folder = qs.Get("folder")
|
||||
go s.model.Override(folder)
|
||||
}
|
||||
|
||||
func (s *apiService) postDBRevert(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postDBRevert(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var folder = qs.Get("folder")
|
||||
go s.model.Revert(folder)
|
||||
@@ -787,7 +688,7 @@ func getPagingParams(qs url.Values) (int, int) {
|
||||
return page, perpage
|
||||
}
|
||||
|
||||
func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBNeed(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
folder := qs.Get("folder")
|
||||
@@ -806,7 +707,7 @@ func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
folder := qs.Get("folder")
|
||||
@@ -830,7 +731,7 @@ func (s *apiService) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
folder := qs.Get("folder")
|
||||
@@ -846,19 +747,19 @@ func (s *apiService) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemConnections(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemConnections(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, s.model.ConnectionStats())
|
||||
}
|
||||
|
||||
func (s *apiService) getDeviceStats(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDeviceStats(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, s.model.DeviceStatistics())
|
||||
}
|
||||
|
||||
func (s *apiService) getFolderStats(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getFolderStats(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, s.model.FolderStatistics())
|
||||
}
|
||||
|
||||
func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
file := qs.Get("file")
|
||||
@@ -879,15 +780,15 @@ func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, s.cfg.RawCopy())
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
s.systemConfigMut.Lock()
|
||||
defer s.systemConfigMut.Unlock()
|
||||
|
||||
to, err := config.ReadJSON(r.Body, myID)
|
||||
to, err := config.ReadJSON(r.Body, s.id)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
l.Warnln("Decoding posted config:", err)
|
||||
@@ -926,16 +827,16 @@ func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string]bool{"configInSync": !s.cfg.RequiresRestart()})
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemRestart(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemRestart(w http.ResponseWriter, r *http.Request) {
|
||||
s.flushResponse(`{"ok": "restarting"}`, w)
|
||||
go restart()
|
||||
go s.contr.Restart()
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemReset(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
|
||||
@@ -958,27 +859,27 @@ func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
|
||||
s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
|
||||
}
|
||||
|
||||
go restart()
|
||||
go s.contr.Restart()
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
|
||||
s.flushResponse(`{"ok": "shutting down"}`, w)
|
||||
go shutdown()
|
||||
go s.contr.Shutdown()
|
||||
}
|
||||
|
||||
func (s *apiService) flushResponse(resp string, w http.ResponseWriter) {
|
||||
func (s *service) flushResponse(resp string, w http.ResponseWriter) {
|
||||
w.Write([]byte(resp + "\n"))
|
||||
f := w.(http.Flusher)
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
tilde, _ := fs.ExpandTilde("~")
|
||||
res := make(map[string]interface{})
|
||||
res["myID"] = myID.String()
|
||||
res["myID"] = s.id.String()
|
||||
res["goroutines"] = runtime.NumGoroutine()
|
||||
res["alloc"] = m.Alloc
|
||||
res["sys"] = m.Sys - m.HeapReleased
|
||||
@@ -1002,31 +903,31 @@ func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// gives us percent
|
||||
res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU())
|
||||
res["pathSeparator"] = string(filepath.Separator)
|
||||
res["urVersionMax"] = usageReportVersion
|
||||
res["uptime"] = int(time.Since(startTime).Seconds())
|
||||
res["startTime"] = startTime
|
||||
res["urVersionMax"] = ur.Version
|
||||
res["uptime"] = s.urService.UptimeS()
|
||||
res["startTime"] = ur.StartTime
|
||||
res["guiAddressOverridden"] = s.cfg.GUI().IsOverridden()
|
||||
|
||||
sendJSON(w, res)
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string][]logger.Line{
|
||||
"errors": s.guiErrors.Since(time.Time{}),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
bs, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
l.Warnln(string(bs))
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
|
||||
s.guiErrors.Clear()
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemLog(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemLog(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
since, err := time.Parse(time.RFC3339, q.Get("since"))
|
||||
if err != nil {
|
||||
@@ -1037,7 +938,7 @@ func (s *apiService) getSystemLog(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
since, err := time.Parse(time.RFC3339, q.Get("since"))
|
||||
if err != nil {
|
||||
@@ -1055,7 +956,7 @@ type fileEntry struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
var files []fileEntry
|
||||
|
||||
// Redacted configuration as a JSON
|
||||
@@ -1112,7 +1013,7 @@ func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Report Data as a JSON
|
||||
if usageReportingData, err := json.MarshalIndent(reportData(s.cfg, s.model, s.connectionsService, usageReportVersion, true), "", " "); err != nil {
|
||||
if usageReportingData, err := json.MarshalIndent(s.urService.ReportData(), "", " "); err != nil {
|
||||
l.Warnln("Support bundle: failed to create versionPlatform.json:", err)
|
||||
} else {
|
||||
files = append(files, fileEntry{name: "usage-reporting.json.txt", data: usageReportingData})
|
||||
@@ -1157,7 +1058,7 @@ func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
io.Copy(w, &zipFilesBuffer)
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
stats := make(map[string]interface{})
|
||||
metrics.Each(func(name string, intf interface{}) {
|
||||
if m, ok := intf.(*metrics.StandardTimer); ok {
|
||||
@@ -1177,7 +1078,7 @@ func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request
|
||||
w.Write(bs)
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
devices := make(map[string]discover.CacheEntry)
|
||||
|
||||
if s.discoverer != nil {
|
||||
@@ -1192,15 +1093,15 @@ func (s *apiService) getSystemDiscovery(w http.ResponseWriter, r *http.Request)
|
||||
sendJSON(w, devices)
|
||||
}
|
||||
|
||||
func (s *apiService) getReport(w http.ResponseWriter, r *http.Request) {
|
||||
version := usageReportVersion
|
||||
func (s *service) getReport(w http.ResponseWriter, r *http.Request) {
|
||||
version := ur.Version
|
||||
if val, _ := strconv.Atoi(r.URL.Query().Get("version")); val > 0 {
|
||||
version = val
|
||||
}
|
||||
sendJSON(w, reportData(s.cfg, s.model, s.connectionsService, version, true))
|
||||
sendJSON(w, s.urService.ReportDataPreview(version))
|
||||
}
|
||||
|
||||
func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getRandomString(w http.ResponseWriter, r *http.Request) {
|
||||
length := 32
|
||||
if val, _ := strconv.Atoi(r.URL.Query().Get("length")); val > 0 {
|
||||
length = val
|
||||
@@ -1210,7 +1111,7 @@ func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string]string{"random": str})
|
||||
}
|
||||
|
||||
func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
folder := qs.Get("folder")
|
||||
@@ -1227,7 +1128,7 @@ func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
bs, err := ioutil.ReadAll(r.Body)
|
||||
@@ -1253,19 +1154,19 @@ func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
s.getDBIgnores(w, r)
|
||||
}
|
||||
|
||||
func (s *apiService) getIndexEvents(w http.ResponseWriter, r *http.Request) {
|
||||
s.fss.gotEventRequest()
|
||||
func (s *service) getIndexEvents(w http.ResponseWriter, r *http.Request) {
|
||||
s.fss.OnEventRequest()
|
||||
mask := s.getEventMask(r.URL.Query().Get("events"))
|
||||
sub := s.getEventSub(mask)
|
||||
s.getEvents(w, r, sub)
|
||||
}
|
||||
|
||||
func (s *apiService) getDiskEvents(w http.ResponseWriter, r *http.Request) {
|
||||
sub := s.getEventSub(diskEventMask)
|
||||
func (s *service) getDiskEvents(w http.ResponseWriter, r *http.Request) {
|
||||
sub := s.getEventSub(DiskEventMask)
|
||||
s.getEvents(w, r, sub)
|
||||
}
|
||||
|
||||
func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request, eventSub events.BufferedSubscription) {
|
||||
func (s *service) getEvents(w http.ResponseWriter, r *http.Request, eventSub events.BufferedSubscription) {
|
||||
qs := r.URL.Query()
|
||||
sinceStr := qs.Get("since")
|
||||
limitStr := qs.Get("limit")
|
||||
@@ -1294,8 +1195,8 @@ func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request, eventSub
|
||||
sendJSON(w, evs)
|
||||
}
|
||||
|
||||
func (s *apiService) getEventMask(evs string) events.EventType {
|
||||
eventMask := defaultEventMask
|
||||
func (s *service) getEventMask(evs string) events.EventType {
|
||||
eventMask := DefaultEventMask
|
||||
if evs != "" {
|
||||
eventList := strings.Split(evs, ",")
|
||||
eventMask = 0
|
||||
@@ -1306,12 +1207,12 @@ func (s *apiService) getEventMask(evs string) events.EventType {
|
||||
return eventMask
|
||||
}
|
||||
|
||||
func (s *apiService) getEventSub(mask events.EventType) events.BufferedSubscription {
|
||||
func (s *service) getEventSub(mask events.EventType) events.BufferedSubscription {
|
||||
s.eventSubsMut.Lock()
|
||||
bufsub, ok := s.eventSubs[mask]
|
||||
if !ok {
|
||||
evsub := events.Default.Subscribe(mask)
|
||||
bufsub = events.NewBufferedSubscription(evsub, eventSubBufferSize)
|
||||
bufsub = events.NewBufferedSubscription(evsub, EventSubBufferSize)
|
||||
s.eventSubs[mask] = bufsub
|
||||
}
|
||||
s.eventSubsMut.Unlock()
|
||||
@@ -1319,8 +1220,8 @@ func (s *apiService) getEventSub(mask events.EventType) events.BufferedSubscript
|
||||
return bufsub
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
if noUpgradeFromEnv {
|
||||
func (s *service) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
if s.noUpgrade {
|
||||
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
|
||||
return
|
||||
}
|
||||
@@ -1339,7 +1240,7 @@ func (s *apiService) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, res)
|
||||
}
|
||||
|
||||
func (s *apiService) getDeviceID(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getDeviceID(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
idStr := qs.Get("id")
|
||||
id, err := protocol.DeviceIDFromString(idStr)
|
||||
@@ -1355,7 +1256,7 @@ func (s *apiService) getDeviceID(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) getLang(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getLang(w http.ResponseWriter, r *http.Request) {
|
||||
lang := r.Header.Get("Accept-Language")
|
||||
var langs []string
|
||||
for _, l := range strings.Split(lang, ",") {
|
||||
@@ -1365,7 +1266,7 @@ func (s *apiService) getLang(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, langs)
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
opts := s.cfg.Options()
|
||||
rel, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
|
||||
if err != nil {
|
||||
@@ -1383,12 +1284,11 @@ func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
s.flushResponse(`{"ok": "restarting"}`, w)
|
||||
l.Infoln("Upgrading")
|
||||
stop <- exitUpgrading
|
||||
s.contr.ExitUpgrading()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) makeDevicePauseHandler(paused bool) http.HandlerFunc {
|
||||
func (s *service) makeDevicePauseHandler(paused bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var deviceStr = qs.Get("device")
|
||||
@@ -1423,7 +1323,7 @@ func (s *apiService) makeDevicePauseHandler(paused bool) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postDBScan(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
if folder != "" {
|
||||
@@ -1448,7 +1348,7 @@ func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) postDBPrio(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postDBPrio(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
file := qs.Get("file")
|
||||
@@ -1456,7 +1356,7 @@ func (s *apiService) postDBPrio(w http.ResponseWriter, r *http.Request) {
|
||||
s.getDBNeed(w, r)
|
||||
}
|
||||
|
||||
func (s *apiService) getQR(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getQR(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var text = qs.Get("text")
|
||||
code, err := qr.Encode(text, qr.M)
|
||||
@@ -1469,7 +1369,7 @@ func (s *apiService) getQR(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(code.PNG())
|
||||
}
|
||||
|
||||
func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
tot := map[string]float64{}
|
||||
count := map[string]float64{}
|
||||
|
||||
@@ -1493,7 +1393,7 @@ func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, comp)
|
||||
}
|
||||
|
||||
func (s *apiService) getFolderVersions(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getFolderVersions(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
versions, err := s.model.GetFolderVersions(qs.Get("folder"))
|
||||
if err != nil {
|
||||
@@ -1503,7 +1403,7 @@ func (s *apiService) getFolderVersions(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, versions)
|
||||
}
|
||||
|
||||
func (s *apiService) postFolderVersionsRestore(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postFolderVersionsRestore(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
bs, err := ioutil.ReadAll(r.Body)
|
||||
@@ -1528,7 +1428,7 @@ func (s *apiService) postFolderVersionsRestore(w http.ResponseWriter, r *http.Re
|
||||
sendJSON(w, ferr)
|
||||
}
|
||||
|
||||
func (s *apiService) getFolderErrors(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getFolderErrors(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
page, perpage := getPagingParams(qs)
|
||||
@@ -1558,7 +1458,7 @@ func (s *apiService) getFolderErrors(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
current := qs.Get("current")
|
||||
|
||||
@@ -1637,7 +1537,7 @@ func browseFiles(current string, fsType fs.FilesystemType) []string {
|
||||
return append(exactMatches, caseInsMatches...)
|
||||
}
|
||||
|
||||
func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getCPUProf(w http.ResponseWriter, r *http.Request) {
|
||||
duration, err := time.ParseDuration(r.FormValue("duration"))
|
||||
if err != nil {
|
||||
duration = 30 * time.Second
|
||||
@@ -1654,7 +1554,7 @@ func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) getHeapProf(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) getHeapProf(w http.ResponseWriter, r *http.Request) {
|
||||
filename := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, build.Version, time.Now().Format("150405")) // hhmmss
|
||||
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -53,7 +53,7 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
|
||||
}
|
||||
}
|
||||
|
||||
httpl.Debugln("Sessionless HTTP request with authentication; this is expensive.")
|
||||
l.Debugln("Sessionless HTTP request with authentication; this is expensive.")
|
||||
|
||||
error := func() {
|
||||
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -57,7 +57,7 @@ func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, n
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
cookie, err := r.Cookie("CSRF-Token-" + unique)
|
||||
if err != nil || !validCsrfToken(cookie.Value) {
|
||||
httpl.Debugln("new CSRF cookie in response to request for", r.URL)
|
||||
l.Debugln("new CSRF cookie in response to request for", r.URL)
|
||||
cookie = &http.Cookie{
|
||||
Name: "CSRF-Token-" + unique,
|
||||
Value: newCsrfToken(),
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -27,11 +27,25 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/ur"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
orig := locations.GetBaseDir(locations.ConfigBaseDir)
|
||||
locations.SetBaseDir(locations.ConfigBaseDir, "testdata/config")
|
||||
|
||||
exitCode := m.Run()
|
||||
|
||||
locations.SetBaseDir(locations.ConfigBaseDir, orig)
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestCSRFToken(t *testing.T) {
|
||||
t1 := newCsrfToken()
|
||||
t2 := newCsrfToken()
|
||||
@@ -74,7 +88,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
|
||||
}
|
||||
w := config.Wrap("/dev/null", cfg)
|
||||
|
||||
srv := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
|
||||
srv.started = make(chan string)
|
||||
|
||||
sup := suture.New("test", suture.Spec{
|
||||
@@ -180,7 +194,7 @@ func expectURLToContain(t *testing.T, url, exp string) {
|
||||
|
||||
func TestDirNames(t *testing.T) {
|
||||
names := dirNames("testdata")
|
||||
expected := []string{"default", "foo", "testfolder"}
|
||||
expected := []string{"config", "default", "foo", "testfolder"}
|
||||
if diff, equal := messagediff.PrettyDiff(expected, names); !equal {
|
||||
t.Errorf("Unexpected dirNames return: %#v\n%s", names, diff)
|
||||
}
|
||||
@@ -470,9 +484,7 @@ func TestHTTPLogin(t *testing.T) {
|
||||
}
|
||||
|
||||
func startHTTP(cfg *mockedConfig) (string, error) {
|
||||
model := new(mockedModel)
|
||||
httpsCertFile := "../../test/h1/https-cert.pem"
|
||||
httpsKeyFile := "../../test/h1/https-key.pem"
|
||||
m := new(mockedModel)
|
||||
assetDir := "../../gui"
|
||||
eventSub := new(mockedEventSub)
|
||||
diskEventSub := new(mockedEventSub)
|
||||
@@ -484,8 +496,9 @@ func startHTTP(cfg *mockedConfig) (string, error) {
|
||||
addrChan := make(chan string)
|
||||
|
||||
// Instantiate the API service
|
||||
svc := newAPIService(protocol.LocalDeviceID, cfg, httpsCertFile, httpsKeyFile, assetDir, model,
|
||||
eventSub, diskEventSub, discoverer, connections, errorLog, systemLog, cpu)
|
||||
urService := ur.New(cfg, m, connections, false)
|
||||
summaryService := model.NewFolderSummaryService(cfg, m, protocol.LocalDeviceID)
|
||||
svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, discoverer, connections, urService, summaryService, errorLog, systemLog, cpu, nil, false).(*service)
|
||||
svc.started = addrChan
|
||||
|
||||
// Actually start the API service
|
||||
@@ -946,10 +959,10 @@ func TestEventMasks(t *testing.T) {
|
||||
cfg := new(mockedConfig)
|
||||
defSub := new(mockedEventSub)
|
||||
diskSub := new(mockedEventSub)
|
||||
svc := newAPIService(protocol.LocalDeviceID, cfg, "", "", "", nil, defSub, diskSub, nil, nil, nil, nil, nil)
|
||||
svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, nil, nil, nil, nil, nil, nil, nil, nil, false).(*service)
|
||||
|
||||
if mask := svc.getEventMask(""); mask != defaultEventMask {
|
||||
t.Errorf("incorrect default mask %x != %x", int64(mask), int64(defaultEventMask))
|
||||
if mask := svc.getEventMask(""); mask != DefaultEventMask {
|
||||
t.Errorf("incorrect default mask %x != %x", int64(mask), int64(DefaultEventMask))
|
||||
}
|
||||
|
||||
expected := events.FolderSummary | events.LocalChangeDetected
|
||||
@@ -962,10 +975,10 @@ func TestEventMasks(t *testing.T) {
|
||||
t.Errorf("incorrect parsed mask %x != %x", int64(mask), int64(expected))
|
||||
}
|
||||
|
||||
if res := svc.getEventSub(defaultEventMask); res != defSub {
|
||||
if res := svc.getEventSub(DefaultEventMask); res != defSub {
|
||||
t.Errorf("should have returned the given default event sub")
|
||||
}
|
||||
if res := svc.getEventSub(diskEventMask); res != diskSub {
|
||||
if res := svc.getEventSub(DiskEventMask); res != diskSub {
|
||||
t.Errorf("should have returned the given disk event sub")
|
||||
}
|
||||
if res := svc.getEventSub(events.LocalIndexUpdated); res == nil || res == defSub || res == diskSub {
|
||||
28
lib/api/debug.go
Normal file
28
lib/api/debug.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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 https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("api", "REST API")
|
||||
)
|
||||
|
||||
func shouldDebugHTTP() bool {
|
||||
return l.ShouldDebug("api")
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The debug facility was originally named "http", changed in:
|
||||
// https://github.com/syncthing/syncthing/pull/5548
|
||||
l.SetDebug("api", strings.Contains(os.Getenv("STTRACE"), "api") || strings.Contains(os.Getenv("STTRACE"), "http") || 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 https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
@@ -44,6 +44,8 @@ func (c *mockedConfig) Replace(cfg config.Configuration) (config.Waiter, error)
|
||||
|
||||
func (c *mockedConfig) Subscribe(cm config.Committer) {}
|
||||
|
||||
func (c *mockedConfig) Unsubscribe(cm config.Committer) {}
|
||||
|
||||
func (c *mockedConfig) Folders() map[string]config.FolderConfiguration {
|
||||
return nil
|
||||
}
|
||||
@@ -68,6 +70,58 @@ func (c *mockedConfig) RequiresRestart() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *mockedConfig) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {}
|
||||
|
||||
func (c *mockedConfig) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {}
|
||||
|
||||
func (m *mockedConfig) MyName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockedConfig) ConfigPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockedConfig) SetGUI(gui config.GUIConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (m *mockedConfig) SetOptions(opts config.OptionsConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (m *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
|
||||
return config.FolderConfiguration{}, false
|
||||
}
|
||||
|
||||
func (m *mockedConfig) FolderList() []config.FolderConfiguration {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedConfig) SetFolder(fld config.FolderConfiguration) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (m *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
|
||||
return config.DeviceConfiguration{}, false
|
||||
}
|
||||
|
||||
func (m *mockedConfig) RemoveDevice(id protocol.DeviceID) (config.Waiter, error) {
|
||||
return noopWaiter{}, nil
|
||||
}
|
||||
|
||||
func (m *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *mockedConfig) GlobalDiscoveryServers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
type noopWaiter struct{}
|
||||
|
||||
func (noopWaiter) Wait() {}
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
type mockedConnections struct{}
|
||||
|
||||
@@ -15,3 +15,7 @@ func (m *mockedConnections) Status() map[string]interface{} {
|
||||
func (m *mockedConnections) NATType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockedConnections) Serve() {}
|
||||
|
||||
func (m *mockedConnections) 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 https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
type mockedCPUService struct{}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -4,11 +4,13 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/connections"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
@@ -150,3 +152,38 @@ func (m *mockedModel) WatchError(folder string) error {
|
||||
func (m *mockedModel) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) Serve() {}
|
||||
func (m *mockedModel) Stop() {}
|
||||
func (m *mockedModel) Index(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {}
|
||||
func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {
|
||||
}
|
||||
|
||||
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) ClusterConfig(deviceID protocol.DeviceID, config protocol.ClusterConfig) {}
|
||||
|
||||
func (m *mockedModel) Closed(conn protocol.Connection, err error) {}
|
||||
|
||||
func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
|
||||
}
|
||||
|
||||
func (m *mockedModel) AddConnection(conn connections.Connection, hello protocol.HelloResult) {}
|
||||
|
||||
func (m *mockedModel) OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) GetHello(protocol.DeviceID) protocol.HelloIntf {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedModel) AddFolder(cfg config.FolderConfiguration) {}
|
||||
|
||||
func (m *mockedModel) RestartFolder(from, to config.FolderConfiguration) {}
|
||||
|
||||
func (m *mockedModel) StartFolder(folder string) {}
|
||||
|
||||
func (m *mockedModel) StartDeadlockDetector(timeout time.Duration) {}
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// getRedactedConfig redacting some parts of config
|
||||
func getRedactedConfig(s *apiService) config.Configuration {
|
||||
func getRedactedConfig(s *service) config.Configuration {
|
||||
rawConf := s.cfg.RawCopy()
|
||||
rawConf.GUI.APIKey = "REDACTED"
|
||||
if rawConf.GUI.Password != "" {
|
||||
23
lib/api/testdata/config/cert.pem
vendored
Normal file
23
lib/api/testdata/config/cert.pem
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID3jCCAkigAwIBAgIBADALBgkqhkiG9w0BAQUwFDESMBAGA1UEAxMJc3luY3Ro
|
||||
aW5nMB4XDTE0MDMxNDA3MDA1M1oXDTQ5MTIzMTIzNTk1OVowFDESMBAGA1UEAxMJ
|
||||
c3luY3RoaW5nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArDOcd5ft
|
||||
R7SnalxF1ckU3lDQpgfMIPhFDU//4dvdSSFevrMuVDTbUYhyCfGtg/g+F5TmKhZg
|
||||
E2peYhllITupz5MP7OHGaO2GHf2XnUDD4QUO3E+KVAUw7dyFSwy09esqApVLzH3+
|
||||
ov+QXyyzmRWPsJe9u18BHU1Hob/RmBhS9m2CAJgzN6EJ8KGjApiW3iR8lD/hjVyi
|
||||
IVde8IRD6qYHEJYiPJuziTVcQpCblVYxTz3ScmmT190/O9UvViIpcOPQdwgOdewP
|
||||
NNMK35c9Edt0AH5flYp6jgrja9NkLQJ3+KOiro6yl9IUS5w87GMxI8qzI8SgCAZZ
|
||||
pYSoLbu1FJPvxV4p5eHwuprBCwmFYZWw6Y7rqH0sN52C+3TeObJCMNP9ilPadqRI
|
||||
+G0Q99TCaloeR022x33r/8D8SIn3FP35zrlFM+DvqlxoS6glbNb/Bj3p9vN0XONO
|
||||
RCuynOGe9F/4h/DaNnrbrRWqJOxBsZTsbbcJaKATfWU/Z9GcC+pUpPRhAgMBAAGj
|
||||
PzA9MA4GA1UdDwEB/wQEAwIAoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
|
||||
AwIwDAYDVR0TAQH/BAIwADALBgkqhkiG9w0BAQUDggGBAFF8dklGoC43fMrUZfb4
|
||||
6areRWG8quO6cSX6ATzRQVJ8WJ5VcC7OJk8/FeiYA+wcvUJ/1Zm/VHMYugtOz5M8
|
||||
CrWAF1r9D3Xfe5D8qfrEOYG2XjxD2nFHCnkbY4fP+SMSuXaDs7ixQnzw0UFh1wsV
|
||||
9Jy/QrgXFAIFZtu1Nz+rrvoAgw24gkDhY3557MbmYfmfPsJ8cw+WJ845sxGMPFF2
|
||||
c+5EN0jiSm0AwZK11BMJda36ke829UZctDkopbGEg1peydDR5LiyhiTAPtWn7uT/
|
||||
PkzHYLuaECAkVbWC3bZLocMGOP6F1pG+BMr00NJgVy05ASQzi4FPjcZQNNY8s69R
|
||||
ZgoCIBaJZq3ti1EsZQ1H0Ynm2c2NMVKdj4czoy8a9ZC+DCuhG7EV5Foh20VhCWgA
|
||||
RfPhlHVJthuimsWBx39X85gjSBR017uk0AxOJa6pzh/b/RPCRtUfX8EArInS3XCf
|
||||
RvRtdrnBZNI3tiREopZGt0SzgDZUs4uDVBUX8HnHzyFJrg==
|
||||
-----END CERTIFICATE-----
|
||||
134
lib/api/testdata/config/config.xml
vendored
Normal file
134
lib/api/testdata/config/config.xml
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
<configuration version="28">
|
||||
<folder id="default" label="" path="s1/" type="sendreceive" rescanIntervalS="10" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||
<filesystemType>basic</filesystemType>
|
||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
|
||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
|
||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
|
||||
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" introducedBy=""></device>
|
||||
<minDiskFree unit="%">1</minDiskFree>
|
||||
<versioning></versioning>
|
||||
<copiers>1</copiers>
|
||||
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
|
||||
<hashers>0</hashers>
|
||||
<order>random</order>
|
||||
<ignoreDelete>false</ignoreDelete>
|
||||
<scanProgressIntervalS>0</scanProgressIntervalS>
|
||||
<pullerPauseS>0</pullerPauseS>
|
||||
<maxConflicts>-1</maxConflicts>
|
||||
<disableSparseFiles>false</disableSparseFiles>
|
||||
<disableTempIndexes>false</disableTempIndexes>
|
||||
<paused>false</paused>
|
||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||
<markerName>.stfolder</markerName>
|
||||
<useLargeBlocks>true</useLargeBlocks>
|
||||
</folder>
|
||||
<folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" label="" path="s12-1/" type="sendreceive" rescanIntervalS="10" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||
<filesystemType>basic</filesystemType>
|
||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
|
||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
|
||||
<minDiskFree unit="%">1</minDiskFree>
|
||||
<versioning></versioning>
|
||||
<copiers>1</copiers>
|
||||
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
|
||||
<hashers>0</hashers>
|
||||
<order>random</order>
|
||||
<ignoreDelete>false</ignoreDelete>
|
||||
<scanProgressIntervalS>0</scanProgressIntervalS>
|
||||
<pullerPauseS>0</pullerPauseS>
|
||||
<maxConflicts>-1</maxConflicts>
|
||||
<disableSparseFiles>false</disableSparseFiles>
|
||||
<disableTempIndexes>false</disableTempIndexes>
|
||||
<paused>false</paused>
|
||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||
<markerName>.stfolder</markerName>
|
||||
<useLargeBlocks>true</useLargeBlocks>
|
||||
</folder>
|
||||
<device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||
<address>tcp://127.0.0.1:22004</address>
|
||||
<paused>false</paused>
|
||||
<autoAcceptFolders>false</autoAcceptFolders>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<maxRequestKiB>0</maxRequestKiB>
|
||||
</device>
|
||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||
<address>tcp://127.0.0.1:22001</address>
|
||||
<paused>false</paused>
|
||||
<autoAcceptFolders>false</autoAcceptFolders>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<maxRequestKiB>0</maxRequestKiB>
|
||||
</device>
|
||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||
<address>tcp://127.0.0.1:22002</address>
|
||||
<paused>false</paused>
|
||||
<autoAcceptFolders>false</autoAcceptFolders>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<maxRequestKiB>0</maxRequestKiB>
|
||||
</device>
|
||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||
<address>tcp://127.0.0.1:22003</address>
|
||||
<paused>false</paused>
|
||||
<autoAcceptFolders>false</autoAcceptFolders>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<maxRequestKiB>0</maxRequestKiB>
|
||||
</device>
|
||||
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||
<address>tcp://127.0.0.1:22004</address>
|
||||
<paused>false</paused>
|
||||
<autoAcceptFolders>false</autoAcceptFolders>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<maxRequestKiB>0</maxRequestKiB>
|
||||
</device>
|
||||
<gui enabled="true" tls="false" debugging="true">
|
||||
<address>127.0.0.1:8081</address>
|
||||
<user>testuser</user>
|
||||
<password>$2a$10$7tKL5uvLDGn5s2VLPM2yWOK/II45az0mTel8hxAUJDRQN1Tk2QYwu</password>
|
||||
<apikey>abc123</apikey>
|
||||
<theme>default</theme>
|
||||
</gui>
|
||||
<ldap></ldap>
|
||||
<options>
|
||||
<listenAddress>tcp://127.0.0.1:22001</listenAddress>
|
||||
<globalAnnounceServer>default</globalAnnounceServer>
|
||||
<globalAnnounceEnabled>false</globalAnnounceEnabled>
|
||||
<localAnnounceEnabled>true</localAnnounceEnabled>
|
||||
<localAnnouncePort>21027</localAnnouncePort>
|
||||
<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<reconnectionIntervalS>5</reconnectionIntervalS>
|
||||
<relaysEnabled>false</relaysEnabled>
|
||||
<relayReconnectIntervalM>10</relayReconnectIntervalM>
|
||||
<startBrowser>false</startBrowser>
|
||||
<natEnabled>true</natEnabled>
|
||||
<natLeaseMinutes>0</natLeaseMinutes>
|
||||
<natRenewalMinutes>30</natRenewalMinutes>
|
||||
<natTimeoutSeconds>10</natTimeoutSeconds>
|
||||
<urAccepted>3</urAccepted>
|
||||
<urSeen>2</urSeen>
|
||||
<urUniqueID>tmwxxCqi</urUniqueID>
|
||||
<urURL>https://data.syncthing.net/newdata</urURL>
|
||||
<urPostInsecurely>false</urPostInsecurely>
|
||||
<urInitialDelayS>1800</urInitialDelayS>
|
||||
<restartOnWakeup>true</restartOnWakeup>
|
||||
<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
|
||||
<upgradeToPreReleases>false</upgradeToPreReleases>
|
||||
<keepTemporariesH>24</keepTemporariesH>
|
||||
<cacheIgnoredFiles>false</cacheIgnoredFiles>
|
||||
<progressUpdateIntervalS>5</progressUpdateIntervalS>
|
||||
<limitBandwidthInLan>false</limitBandwidthInLan>
|
||||
<minHomeDiskFree unit="%">1</minHomeDiskFree>
|
||||
<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
|
||||
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
|
||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||
<trafficClass>0</trafficClass>
|
||||
<defaultFolderPath>~</defaultFolderPath>
|
||||
<setLowPriority>true</setLowPriority>
|
||||
<maxConcurrentScans>0</maxConcurrentScans>
|
||||
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
||||
</options>
|
||||
</configuration>
|
||||
23
lib/api/testdata/config/https-cert.pem
vendored
Normal file
23
lib/api/testdata/config/https-cert.pem
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID5TCCAk+gAwIBAgIIBYqoKiSgB+owCwYJKoZIhvcNAQELMBQxEjAQBgNVBAMT
|
||||
CXN5bmN0aGluZzAeFw0xNDA5MTQyMjIzMzVaFw00OTEyMzEyMzU5NTlaMBQxEjAQ
|
||||
BgNVBAMTCXN5bmN0aGluZzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB
|
||||
AKZK/sjb6ZuVVHPvo77Cp5E8LfiznfoIWJRoX/MczE99iDyFZm1Wf9GFT8WhXICM
|
||||
C2kgGbr/gAxhkeEcZ500vhA2C+aois1DGcb+vNY53I0qp3vSUl4ow55R0xJ4UjpJ
|
||||
nJWF8p9iPDMwMP6WQ/E/ekKRKCOt0TFj4xqtiSt0pxPLeHfKVpWXxqIVDhnsoGQ+
|
||||
NWuUjM3FkmEmhp5DdRtwskiZZYz1zCgoHkFzKt/+IxjCuzbO0+Ti8R3b/d0A+WLN
|
||||
LHr0SjatajLbHebA+9c3ts6t3V5YzcMqDJ4MyxFtRoXFJjEbcM9IqKQE8t8TIhv8
|
||||
a302yRikJ2uPx+fXJGospnmWCbaK2rViPbvICSgvSBA3As0f3yPzXsEt+aW5NmDV
|
||||
fLBX1DU7Ow6oBqZTlI+STrzZR1qfvIuweIWoPqnPNd4sxuoxAK50ViUKdOtSYL/a
|
||||
F0eM3bqbp2ozhct+Bfmqu2oI/RHXe+RUfAXrlFQ8p6jcISW2ip+oiBtR4GZkncI9
|
||||
YQIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCAKAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
|
||||
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4IBgQBsYc5XVQy5
|
||||
aJVdwx+mAKiuCs5ZCvV4H4VWY9XUwEJuUUD3yXw2xyzuQl5+lOxfiQcaudhVwARC
|
||||
Dao75MUctXmx1YU+J5G31cGdC9kbxWuo1xypkK+2Zl+Kwh65aod3OkHVz9oNkKpf
|
||||
JnXbdph4UiFJzijSruXDDaerrQdABUvlusPozZn8vMwZ21Ls/eNIOJvA0S2d2jep
|
||||
fvmu7yQPejDp7zcgPdmneuZqmUyXLxxFopYqHqFQVM8f+Y8iZ8HnMiAJgLKQcmro
|
||||
pp1z/NY0Xr0pLyBY5d/sO+tZmQkyUEWegHtEtQQOO+x8BWinDEAurej/YvZTWTmN
|
||||
+YoUvGdKyV6XfC6WPFcUDFHY4KPSqS3xoLmoVV4xNjJU3aG/xL4uDencNZR/UFNw
|
||||
wKsdvm9SX4TpSLlQa0wu1iNv7QyeR4ZKgaBNSwp2rxpatOi7TTs9KRPfjLFLpYAg
|
||||
bIons/a890SIxpuneuhQZkH63t930EXIZ+9GkU0aUs7MFg5cCmwmlvE=
|
||||
-----END CERTIFICATE-----
|
||||
39
lib/api/testdata/config/https-key.pem
vendored
Normal file
39
lib/api/testdata/config/https-key.pem
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIG5AIBAAKCAYEApkr+yNvpm5VUc++jvsKnkTwt+LOd+ghYlGhf8xzMT32IPIVm
|
||||
bVZ/0YVPxaFcgIwLaSAZuv+ADGGR4RxnnTS+EDYL5qiKzUMZxv681jncjSqne9JS
|
||||
XijDnlHTEnhSOkmclYXyn2I8MzAw/pZD8T96QpEoI63RMWPjGq2JK3SnE8t4d8pW
|
||||
lZfGohUOGeygZD41a5SMzcWSYSaGnkN1G3CySJlljPXMKCgeQXMq3/4jGMK7Ns7T
|
||||
5OLxHdv93QD5Ys0sevRKNq1qMtsd5sD71ze2zq3dXljNwyoMngzLEW1GhcUmMRtw
|
||||
z0iopATy3xMiG/xrfTbJGKQna4/H59ckaiymeZYJtoratWI9u8gJKC9IEDcCzR/f
|
||||
I/NewS35pbk2YNV8sFfUNTs7DqgGplOUj5JOvNlHWp+8i7B4hag+qc813izG6jEA
|
||||
rnRWJQp061Jgv9oXR4zdupunajOFy34F+aq7agj9Edd75FR8BeuUVDynqNwhJbaK
|
||||
n6iIG1HgZmSdwj1hAgMBAAECggGAQkd334TPSmStgXwNLrYU5a0vwYWNvJ9g9t3X
|
||||
CGX9BN3K1BxzY7brQQ46alHTNaUb0y2pM8AsQEMPSsLwhVcFPh7chXW9xOwutQLJ
|
||||
LzVms5lBofeFPuROe6avUxhD5dl7IJl/x4j254wYqxAnSlt7llaWwgnAbEgct4Bd
|
||||
QMXA5gHeJRivg/Y3hFiSA0Et+GZXEmbl7AoIOtKJK0FFxscXOBpzwEgjtAmxbXLC
|
||||
rv5y7KaIyeKL0Bmn8rfBKjn+LCQMJt4wZCrNtFLg3aSpkmqZl6r8Q84OwHMp2x8l
|
||||
SFNVi7j1Cv8DC/yhyEOCbHIRZrK/vzt6Cqe+yjr1UG9niwhQJbEvaV26odzvMSNZ
|
||||
1VodN+ltCZRFFEBc+z3CR7SKDZayT93dLxolzQ4DuSfDnk0fBLtOfeISxS/Wg7Yv
|
||||
5q0XF6cTmQEsDbuDswvlHo3k8w3cjz9SmxMasxgHx6jHkSBbkw0iFLT3KdqA8PrG
|
||||
D3uo67fIQEkcncmRLP3I1qUiWX21AoHBAMVQLLgOd3bOrByyVeugA+5dhef0uopJ
|
||||
GadzBlAT4EY7Vuxu1Qu/m876FnhQc3tGTLfZhcnL9VXV+3DSTosfRz+YDm+K5lOh
|
||||
ZRtswuZscm+l26X+2j1h+AGW8SIz5f9M0CnFpqjC8KkopPk/ZKTcDvrNRRxI5EPx
|
||||
TPZaiPhztlcsc7K5jkLJRL0GiadUniOFY7kUA18hs3MEyzkdYbz8WolUyHeSJT2H
|
||||
hmpdsA5tzUKB1NVdsIsjWESQF3Hd2FFHMwKBwQDXwOCUq5KSBKa1BSO1oQxhyHy3
|
||||
ZQ86d5weLNxovwrHd4ivaVPJ46YLjNk+/q685XPUfoDxO1fnyIYIy4ChtkhXmyli
|
||||
LOPfNt0iSW2M1/L1wb6ZwMz+RWpb3zqPgjMlDCEtD5hQ8Cl5do2tyh3sIrLgamVG
|
||||
sY1hx+VD0BmXUUTGjl8nJqQSMYl6IXTKzrFrx+QWdzA0yWN753XiAF5cLkxNahes
|
||||
SKb/ibrMtO/JKt3RBlZPS3wiFRkxtNcS1HrVWRsCgcBaFir0thYxNlc6munDtMFW
|
||||
uXiD2Sa6MHn4C/pb4VdKeZlMRaYbwRYAQAq2T/UJ2aT5Y+VDp02SLSqp7jtSJavA
|
||||
C0q7/qz+jfe9t8Cct/LfqthIR72YvPwgravWs99U2ttH1ygqcSaz9QytiBYJdzeX
|
||||
ptTg/x7JLoi3CcrztNERqAgDF9kuAPrTWwLKVUYGbcaEH/ESJC7sWsn2f8W6JXWo
|
||||
sf79KMq79v6V3cSeMd+/d8uWxzntrOuGEkvB/0negiUCgcEAp0YwGLQJGFKo2XIZ
|
||||
pIkva2SgZSPiMadoj/CiFkf/2HRxseYMg1uPcicKjA+zdFrFejt2RxGGbvsGCC2X
|
||||
FkmYPuvaovZA2d/UhO+/EtKe2TEUUGqtxHoXIxGoenkspA2Kb0BHDIGW9kgXQmWQ
|
||||
23JvkxSKXsvr3KK5uuDN5oaotvTNCzKnRD/J4bmsrkygO/sneM+BvXtiOT9UIxu8
|
||||
DOYMXHzjy7wsVbT38hxaSHKGtbefFS1mGZqYBPS7Rysb7Ot/AoHBAL0SAbt1a2Ol
|
||||
ObrK8vjTHcQHJH74n+6PWRfsBO+UJ1vtOYFzW85BiVZmi8tC4bJ0Hd89TT7AibzP
|
||||
L1Ftrn0XmBfniwV1SsrjVaRy/KbBeUhjruqyQ2oDLEU7DAm5Z2jG4aG2rLbXYAS9
|
||||
yOQITLN5AVraI4Pr1IWjZTzd/zaaWA5nFNthyXSww1II0f1BgX1S/49k4aWjXeMn
|
||||
FrKN5T7BqIh9W6d7YTrzXoH9lEsUPQHV/ci+YRP4mrfrcC9hJZ3O9g==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
39
lib/api/testdata/config/key.pem
vendored
Normal file
39
lib/api/testdata/config/key.pem
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIG5AIBAAKCAYEArDOcd5ftR7SnalxF1ckU3lDQpgfMIPhFDU//4dvdSSFevrMu
|
||||
VDTbUYhyCfGtg/g+F5TmKhZgE2peYhllITupz5MP7OHGaO2GHf2XnUDD4QUO3E+K
|
||||
VAUw7dyFSwy09esqApVLzH3+ov+QXyyzmRWPsJe9u18BHU1Hob/RmBhS9m2CAJgz
|
||||
N6EJ8KGjApiW3iR8lD/hjVyiIVde8IRD6qYHEJYiPJuziTVcQpCblVYxTz3ScmmT
|
||||
190/O9UvViIpcOPQdwgOdewPNNMK35c9Edt0AH5flYp6jgrja9NkLQJ3+KOiro6y
|
||||
l9IUS5w87GMxI8qzI8SgCAZZpYSoLbu1FJPvxV4p5eHwuprBCwmFYZWw6Y7rqH0s
|
||||
N52C+3TeObJCMNP9ilPadqRI+G0Q99TCaloeR022x33r/8D8SIn3FP35zrlFM+Dv
|
||||
qlxoS6glbNb/Bj3p9vN0XONORCuynOGe9F/4h/DaNnrbrRWqJOxBsZTsbbcJaKAT
|
||||
fWU/Z9GcC+pUpPRhAgMBAAECggGAL8+Unc/c3Y/W+7zq1tShqqgdhjub/XtxEKUp
|
||||
kngNFITjXWc6cb7LNfQAVap4Vq/R7ZI15XGY80sRMYODhJqgJzXZshdtkyx/lEwY
|
||||
kFyvBgb1fU3IRlO6phAYIiJBDBZi75ysEvbYgEEcwJAUvWgzIQDAeQmDsbMHNG2h
|
||||
r+zw++Kjua6IaeWYcOsv60Safsr6m96wrSMPENrFTVor0TaPt5c3okRIsMvT9ddY
|
||||
mzn3Lt0nVQTjO4f+SoqCPhP2FZXqksfKlZlKlr6BLxXGt6b49OrLSXM5eQXIcIZn
|
||||
ZDRsO24X5z8156qPgM9cA8oNEjuSdnArUTreBOsTwNoSpf24Qadsv/uTZlaHM19V
|
||||
q6zQvkjH3ERcOpixmg48TKdIj8cPYxezvcbNqSbZmdyQuaVlgDbUxwYI8A4IhhWl
|
||||
6xhwpX3qPDgw/QHIEngFIWfiIfCk11EPY0SN4cGO6f1rLYug8kqxMPuIQ5Jz9Hhx
|
||||
eFSRnr/fWoJcVYG6bMDKn9YWObQBAoHBAM8NahsLbjl8mdT43LH1Od1tDmDch+0Y
|
||||
JM7TgiIN/GM3piZSpGMOFqToLAqvY+Gf3l4sPgNs10cqdPAEpMk8MJ/IXGmbKq38
|
||||
iVmMaqHTQorCxyUbc54q9AbFU4HKv//F6ZN6K1wSaJt2RBeZpYI+MyBXr5baFiBZ
|
||||
ddXtXlqoEcCFyNR0DhlXrlZPs+cnyM2ZDp++lpn9Wfy+zkv36+NWpAkXVnARjxdF
|
||||
l6M+L7OlurYAWiyJE4uHUjawAM82i5+w8QKBwQDU6RCN6/AMmVrYqPy+7QcnAq67
|
||||
tPDv25gzVExeMKLBAMoz1TkMS+jIF1NMp3cYg5GbLqvx8Qd27fjFbWe/GPeZvlgL
|
||||
qdQI/T8J60dHAySMeOFOB2QWXhI1kwh0b2X0SDkTgfdJBKGdrKVcLTuLyVE24exu
|
||||
yRc8cXpYwBtVkXNBYFd7XEM+tC4b1khO23OJXHJUen9+hgsmn8/zUjASAoq3+Zly
|
||||
J+OHwwXcDcTFLeok3kX3A9NuqIV/Fa9DOGYlenECgcEAvO1onDTZ5uqjE4nhFyDE
|
||||
JB+WtxuDi/wz2eV1IM3SNlZY7S8LgLciQmb3iOhxIzdVGGkWTNnLtcwv17LlCho5
|
||||
5BJXAKXtU8TTLzrJMdArL6J7RIi//tsCwAreH9h5SVG1yDP5zJGfkftgNoikVSuc
|
||||
Sy63sdZdyjbXJtTo+5/QUvPARNuA4e73zRn89jd/Kts2VNz7XpemvND+PKOEQnSU
|
||||
SRdab/gVsQ53RyU/MZVPwTKhFXIeu3pGsk/27RzAWn6BAoHBAMIRYwaKDffd/SHJ
|
||||
/v+lHEThvBXa21c26ae36hhc6q1UI/tVGrfrpVZldIdFilgs7RbvVsmksvIj/gMv
|
||||
M0bL4j0gdC7FcUF0XPaUoBbJdZIZSP0P3ZpJyv1MdYN0WxFsl6IBcD79WrdXPC8m
|
||||
B8XmDgIhsppU77onkaa+DOxVNSJdR8BpG95W7ERxcN14SPrm6ku4kOfqFNXzC+C1
|
||||
hJ2V9Y22lLiqRUplaLzpS/eTX36VoF6E/T87mtt5D5UNHoaA8QKBwH5sRqZXoatU
|
||||
X+vw1MHU5eptMwG7LXR0gw2xmvG3cCN4hbnnBp5YaXlWPiIMmaWhpvschgBIo1TP
|
||||
qGWUpMEETGES18NenLBym+tWIXlfuyZH3B4NUi4kItiZaKb09LzmTjFvzdfQzun4
|
||||
HzIeigTNBDHdS0rdicNIn83QLZ4pJaOZJHq79+mFYkp+9It7UUoWsws6DGl/qX8o
|
||||
0cj4NmJB6QiJa1QCzrGkaajbtThbFoQal9Twk2h3jHgJzX3FbwCpLw==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -84,18 +84,18 @@ func New(myID protocol.DeviceID) Configuration {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func NewWithFreePorts(myID protocol.DeviceID) Configuration {
|
||||
func NewWithFreePorts(myID protocol.DeviceID) (Configuration, error) {
|
||||
cfg := New(myID)
|
||||
|
||||
port, err := getFreePort("127.0.0.1", DefaultGUIPort)
|
||||
if err != nil {
|
||||
l.Fatalln("get free port (GUI):", err)
|
||||
return Configuration{}, fmt.Errorf("get free port (GUI): %v", err)
|
||||
}
|
||||
cfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port)
|
||||
|
||||
port, err = getFreePort("0.0.0.0", DefaultTCPPort)
|
||||
if err != nil {
|
||||
l.Fatalln("get free port (BEP):", err)
|
||||
return Configuration{}, fmt.Errorf("get free port (BEP): %v", err)
|
||||
}
|
||||
if port == DefaultTCPPort {
|
||||
cfg.Options.ListenAddresses = []string{"default"}
|
||||
@@ -106,7 +106,7 @@ func NewWithFreePorts(myID protocol.DeviceID) Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func ReadXML(r io.Reader, myID protocol.DeviceID) (Configuration, error) {
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestDeviceConfig(t *testing.T) {
|
||||
t.Fatal("Unexpected file")
|
||||
}
|
||||
|
||||
cfg := wr.cfg
|
||||
cfg := wr.(*wrapper).cfg
|
||||
|
||||
expectedFolders := []FolderConfiguration{
|
||||
{
|
||||
@@ -515,7 +515,7 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
cfg := Wrap(path, intCfg)
|
||||
|
||||
// To make the equality pass later
|
||||
cfg.cfg.XMLName.Local = "configuration"
|
||||
cfg.(*wrapper).cfg.XMLName.Local = "configuration"
|
||||
|
||||
if exists(path) {
|
||||
t.Error(path, "exists")
|
||||
@@ -827,7 +827,7 @@ func TestIgnoredFolders(t *testing.T) {
|
||||
|
||||
// 2 for folder2, 1 for folder1, as non-existing device and device the folder is shared with is removed.
|
||||
expectedIgnoredFolders := 3
|
||||
for _, dev := range wrapper.cfg.Devices {
|
||||
for _, dev := range wrapper.Devices() {
|
||||
expectedIgnoredFolders -= len(dev.IgnoredFolders)
|
||||
}
|
||||
if expectedIgnoredFolders != 0 {
|
||||
|
||||
@@ -52,7 +52,7 @@ type FolderConfiguration struct {
|
||||
Paused bool `xml:"paused" json:"paused"`
|
||||
WeakHashThresholdPct int `xml:"weakHashThresholdPct" json:"weakHashThresholdPct"` // Use weak hash if more than X percent of the file has changed. Set to -1 to always use weak hash.
|
||||
MarkerName string `xml:"markerName" json:"markerName"`
|
||||
UseLargeBlocks bool `xml:"useLargeBlocks" json:"useLargeBlocks"`
|
||||
UseLargeBlocks bool `xml:"useLargeBlocks" json:"useLargeBlocks" default:"true"`
|
||||
CopyOwnershipFromParent bool `xml:"copyOwnershipFromParent" json:"copyOwnershipFromParent"`
|
||||
|
||||
cachedFilesystem fs.Filesystem
|
||||
@@ -106,7 +106,7 @@ func (f FolderConfiguration) Versioner() versioner.Versioner {
|
||||
}
|
||||
versionerFactory, ok := versioner.Factories[f.Versioning.Type]
|
||||
if !ok {
|
||||
l.Fatalf("Requested versioning type %q that does not exist", f.Versioning.Type)
|
||||
panic(fmt.Sprintf("Requested versioning type %q that does not exist", f.Versioning.Type))
|
||||
}
|
||||
|
||||
return versionerFactory(f.ID, f.Filesystem(), f.Versioning.Params)
|
||||
|
||||
@@ -52,10 +52,48 @@ type noopWaiter struct{}
|
||||
|
||||
func (noopWaiter) Wait() {}
|
||||
|
||||
// A wrapper around a Configuration that manages loads, saves and published
|
||||
// A Wrapper around a Configuration that manages loads, saves and published
|
||||
// notifications of changes to registered Handlers
|
||||
type Wrapper interface {
|
||||
MyName() string
|
||||
ConfigPath() string
|
||||
|
||||
type Wrapper struct {
|
||||
RawCopy() Configuration
|
||||
Replace(cfg Configuration) (Waiter, error)
|
||||
RequiresRestart() bool
|
||||
Save() error
|
||||
|
||||
GUI() GUIConfiguration
|
||||
SetGUI(gui GUIConfiguration) (Waiter, error)
|
||||
LDAP() LDAPConfiguration
|
||||
|
||||
Options() OptionsConfiguration
|
||||
SetOptions(opts OptionsConfiguration) (Waiter, error)
|
||||
|
||||
Folder(id string) (FolderConfiguration, bool)
|
||||
Folders() map[string]FolderConfiguration
|
||||
FolderList() []FolderConfiguration
|
||||
SetFolder(fld FolderConfiguration) (Waiter, error)
|
||||
|
||||
Device(id protocol.DeviceID) (DeviceConfiguration, bool)
|
||||
Devices() map[protocol.DeviceID]DeviceConfiguration
|
||||
RemoveDevice(id protocol.DeviceID) (Waiter, error)
|
||||
SetDevice(DeviceConfiguration) (Waiter, error)
|
||||
SetDevices([]DeviceConfiguration) (Waiter, error)
|
||||
|
||||
AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string)
|
||||
AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID)
|
||||
IgnoredDevice(id protocol.DeviceID) bool
|
||||
IgnoredFolder(device protocol.DeviceID, folder string) bool
|
||||
|
||||
ListenAddresses() []string
|
||||
GlobalDiscoveryServers() []string
|
||||
|
||||
Subscribe(c Committer)
|
||||
Unsubscribe(c Committer)
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
cfg Configuration
|
||||
path string
|
||||
|
||||
@@ -69,8 +107,8 @@ type Wrapper struct {
|
||||
|
||||
// Wrap wraps an existing Configuration structure and ties it to a file on
|
||||
// disk.
|
||||
func Wrap(path string, cfg Configuration) *Wrapper {
|
||||
w := &Wrapper{
|
||||
func Wrap(path string, cfg Configuration) Wrapper {
|
||||
w := &wrapper{
|
||||
cfg: cfg,
|
||||
path: path,
|
||||
mut: sync.NewMutex(),
|
||||
@@ -80,7 +118,7 @@ func Wrap(path string, cfg Configuration) *Wrapper {
|
||||
|
||||
// Load loads an existing file on disk and returns a new configuration
|
||||
// wrapper.
|
||||
func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
|
||||
func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,13 +133,13 @@ func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
|
||||
return Wrap(path, cfg), nil
|
||||
}
|
||||
|
||||
func (w *Wrapper) ConfigPath() string {
|
||||
func (w *wrapper) ConfigPath() string {
|
||||
return w.path
|
||||
}
|
||||
|
||||
// Subscribe registers the given handler to be called on any future
|
||||
// configuration changes.
|
||||
func (w *Wrapper) Subscribe(c Committer) {
|
||||
func (w *wrapper) Subscribe(c Committer) {
|
||||
w.mut.Lock()
|
||||
w.subs = append(w.subs, c)
|
||||
w.mut.Unlock()
|
||||
@@ -109,7 +147,7 @@ func (w *Wrapper) Subscribe(c Committer) {
|
||||
|
||||
// Unsubscribe de-registers the given handler from any future calls to
|
||||
// configuration changes
|
||||
func (w *Wrapper) Unsubscribe(c Committer) {
|
||||
func (w *wrapper) Unsubscribe(c Committer) {
|
||||
w.mut.Lock()
|
||||
for i := range w.subs {
|
||||
if w.subs[i] == c {
|
||||
@@ -123,20 +161,20 @@ func (w *Wrapper) Unsubscribe(c Committer) {
|
||||
}
|
||||
|
||||
// RawCopy returns a copy of the currently wrapped Configuration object.
|
||||
func (w *Wrapper) RawCopy() Configuration {
|
||||
func (w *wrapper) RawCopy() Configuration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Copy()
|
||||
}
|
||||
|
||||
// Replace swaps the current configuration object for the given one.
|
||||
func (w *Wrapper) Replace(cfg Configuration) (Waiter, error) {
|
||||
func (w *wrapper) Replace(cfg Configuration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.replaceLocked(cfg.Copy())
|
||||
}
|
||||
|
||||
func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
|
||||
func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
|
||||
from := w.cfg
|
||||
|
||||
if err := to.clean(); err != nil {
|
||||
@@ -158,7 +196,7 @@ func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
|
||||
return w.notifyListeners(from.Copy(), to.Copy()), nil
|
||||
}
|
||||
|
||||
func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
|
||||
func (w *wrapper) notifyListeners(from, to Configuration) Waiter {
|
||||
wg := sync.NewWaitGroup()
|
||||
wg.Add(len(w.subs))
|
||||
for _, sub := range w.subs {
|
||||
@@ -170,7 +208,7 @@ func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
|
||||
return wg
|
||||
}
|
||||
|
||||
func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
|
||||
func (w *wrapper) notifyListener(sub Committer, from, to Configuration) {
|
||||
l.Debugln(sub, "committing configuration")
|
||||
if !sub.CommitConfiguration(from, to) {
|
||||
l.Debugln(sub, "requires restart")
|
||||
@@ -179,7 +217,7 @@ func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
|
||||
}
|
||||
|
||||
// Devices returns a map of devices.
|
||||
func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
|
||||
func (w *wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
if w.deviceMap == nil {
|
||||
@@ -193,7 +231,7 @@ func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
|
||||
|
||||
// SetDevices adds new devices to the configuration, or overwrites existing
|
||||
// devices with the same ID.
|
||||
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
|
||||
func (w *wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -218,12 +256,12 @@ func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
|
||||
|
||||
// SetDevice adds a new device to the configuration, or overwrites an existing
|
||||
// device with the same ID.
|
||||
func (w *Wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
|
||||
func (w *wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
|
||||
return w.SetDevices([]DeviceConfiguration{dev})
|
||||
}
|
||||
|
||||
// RemoveDevice removes the device from the configuration
|
||||
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
|
||||
func (w *wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -240,7 +278,7 @@ func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
|
||||
|
||||
// Folders returns a map of folders. Folder structures should not be changed,
|
||||
// other than for the purpose of updating via SetFolder().
|
||||
func (w *Wrapper) Folders() map[string]FolderConfiguration {
|
||||
func (w *wrapper) Folders() map[string]FolderConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
if w.folderMap == nil {
|
||||
@@ -253,7 +291,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
|
||||
}
|
||||
|
||||
// FolderList returns a slice of folders.
|
||||
func (w *Wrapper) FolderList() []FolderConfiguration {
|
||||
func (w *wrapper) FolderList() []FolderConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Copy().Folders
|
||||
@@ -261,7 +299,7 @@ func (w *Wrapper) FolderList() []FolderConfiguration {
|
||||
|
||||
// SetFolder adds a new folder to the configuration, or overwrites an existing
|
||||
// folder with the same ID.
|
||||
func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
|
||||
func (w *wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -280,14 +318,14 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
|
||||
}
|
||||
|
||||
// Options returns the current options configuration object.
|
||||
func (w *Wrapper) Options() OptionsConfiguration {
|
||||
func (w *wrapper) Options() OptionsConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Options.Copy()
|
||||
}
|
||||
|
||||
// SetOptions replaces the current options configuration object.
|
||||
func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
|
||||
func (w *wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
@@ -295,21 +333,21 @@ func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
func (w *Wrapper) LDAP() LDAPConfiguration {
|
||||
func (w *wrapper) LDAP() LDAPConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.LDAP.Copy()
|
||||
}
|
||||
|
||||
// GUI returns the current GUI configuration object.
|
||||
func (w *Wrapper) GUI() GUIConfiguration {
|
||||
func (w *wrapper) GUI() GUIConfiguration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.GUI.Copy()
|
||||
}
|
||||
|
||||
// SetGUI replaces the current GUI configuration object.
|
||||
func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
|
||||
func (w *wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
newCfg := w.cfg.Copy()
|
||||
@@ -319,7 +357,7 @@ func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
|
||||
|
||||
// IgnoredDevice returns whether or not connection attempts from the given
|
||||
// device should be silently ignored.
|
||||
func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
for _, device := range w.cfg.IgnoredDevices {
|
||||
@@ -332,7 +370,7 @@ func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
|
||||
// IgnoredFolder returns whether or not share attempts for the given
|
||||
// folder should be silently ignored.
|
||||
func (w *Wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
|
||||
func (w *wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
|
||||
dev, ok := w.Device(device)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -341,7 +379,7 @@ func (w *Wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
|
||||
}
|
||||
|
||||
// Device returns the configuration for the given device and an "ok" bool.
|
||||
func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
|
||||
func (w *wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
for _, device := range w.cfg.Devices {
|
||||
@@ -353,7 +391,7 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
|
||||
}
|
||||
|
||||
// Folder returns the configuration for the given folder and an "ok" bool.
|
||||
func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
|
||||
func (w *wrapper) Folder(id string) (FolderConfiguration, bool) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
for _, folder := range w.cfg.Folders {
|
||||
@@ -365,7 +403,7 @@ func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
|
||||
}
|
||||
|
||||
// Save writes the configuration to disk, and generates a ConfigSaved event.
|
||||
func (w *Wrapper) Save() error {
|
||||
func (w *wrapper) Save() error {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -390,7 +428,7 @@ func (w *Wrapper) Save() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wrapper) GlobalDiscoveryServers() []string {
|
||||
func (w *wrapper) GlobalDiscoveryServers() []string {
|
||||
var servers []string
|
||||
for _, srv := range w.Options().GlobalAnnServers {
|
||||
switch srv {
|
||||
@@ -407,7 +445,7 @@ func (w *Wrapper) GlobalDiscoveryServers() []string {
|
||||
return util.UniqueStrings(servers)
|
||||
}
|
||||
|
||||
func (w *Wrapper) ListenAddresses() []string {
|
||||
func (w *wrapper) ListenAddresses() []string {
|
||||
var addresses []string
|
||||
for _, addr := range w.Options().ListenAddresses {
|
||||
switch addr {
|
||||
@@ -420,15 +458,15 @@ func (w *Wrapper) ListenAddresses() []string {
|
||||
return util.UniqueStrings(addresses)
|
||||
}
|
||||
|
||||
func (w *Wrapper) RequiresRestart() bool {
|
||||
func (w *wrapper) RequiresRestart() bool {
|
||||
return atomic.LoadUint32(&w.requiresRestart) != 0
|
||||
}
|
||||
|
||||
func (w *Wrapper) setRequiresRestart() {
|
||||
func (w *wrapper) setRequiresRestart() {
|
||||
atomic.StoreUint32(&w.requiresRestart, 1)
|
||||
}
|
||||
|
||||
func (w *Wrapper) MyName() string {
|
||||
func (w *wrapper) MyName() string {
|
||||
w.mut.Lock()
|
||||
myID := w.cfg.MyID
|
||||
w.mut.Unlock()
|
||||
@@ -436,7 +474,7 @@ func (w *Wrapper) MyName() string {
|
||||
return cfg.Name
|
||||
}
|
||||
|
||||
func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
|
||||
func (w *wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
|
||||
defer w.Save()
|
||||
|
||||
w.mut.Lock()
|
||||
@@ -444,7 +482,7 @@ func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, addre
|
||||
|
||||
for i := range w.cfg.PendingDevices {
|
||||
if w.cfg.PendingDevices[i].ID == device {
|
||||
w.cfg.PendingDevices[i].Time = time.Now()
|
||||
w.cfg.PendingDevices[i].Time = time.Now().Round(time.Second)
|
||||
w.cfg.PendingDevices[i].Name = name
|
||||
w.cfg.PendingDevices[i].Address = address
|
||||
return
|
||||
@@ -452,14 +490,14 @@ func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, addre
|
||||
}
|
||||
|
||||
w.cfg.PendingDevices = append(w.cfg.PendingDevices, ObservedDevice{
|
||||
Time: time.Now(),
|
||||
Time: time.Now().Round(time.Second),
|
||||
ID: device,
|
||||
Name: name,
|
||||
Address: address,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
|
||||
func (w *wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
|
||||
defer w.Save()
|
||||
|
||||
w.mut.Lock()
|
||||
@@ -470,12 +508,12 @@ func (w *Wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.Dev
|
||||
for j := range w.cfg.Devices[i].PendingFolders {
|
||||
if w.cfg.Devices[i].PendingFolders[j].ID == id {
|
||||
w.cfg.Devices[i].PendingFolders[j].Label = label
|
||||
w.cfg.Devices[i].PendingFolders[j].Time = time.Now()
|
||||
w.cfg.Devices[i].PendingFolders[j].Time = time.Now().Round(time.Second)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.cfg.Devices[i].PendingFolders = append(w.cfg.Devices[i].PendingFolders, ObservedFolder{
|
||||
Time: time.Now(),
|
||||
Time: time.Now().Round(time.Second),
|
||||
ID: id,
|
||||
Label: label,
|
||||
})
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestIsLANHost(t *testing.T) {
|
||||
AlwaysLocalNets: []string{"10.20.30.0/24"},
|
||||
},
|
||||
})
|
||||
s := &Service{cfg: cfg}
|
||||
s := &service{cfg: cfg}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := s.isLANHost(tc.addr)
|
||||
|
||||
@@ -36,7 +36,7 @@ type waiter interface {
|
||||
|
||||
const limiterBurstSize = 4 * 128 << 10
|
||||
|
||||
func newLimiter(cfg *config.Wrapper) *limiter {
|
||||
func newLimiter(cfg config.Wrapper) *limiter {
|
||||
l := &limiter{
|
||||
write: rate.NewLimiter(rate.Inf, limiterBurstSize),
|
||||
read: rate.NewLimiter(rate.Inf, limiterBurstSize),
|
||||
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
device4, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
|
||||
}
|
||||
|
||||
func initConfig() *config.Wrapper {
|
||||
func initConfig() config.Wrapper {
|
||||
cfg := config.Wrap("/dev/null", config.New(device1))
|
||||
dev1Conf = config.NewDeviceConfiguration(device1, "device1")
|
||||
dev2Conf = config.NewDeviceConfiguration(device2, "device2")
|
||||
|
||||
@@ -22,7 +22,7 @@ func init() {
|
||||
}
|
||||
|
||||
type relayDialer struct {
|
||||
cfg *config.Wrapper
|
||||
cfg config.Wrapper
|
||||
tlsCfg *tls.Config
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (d *relayDialer) RedialFrequency() time.Duration {
|
||||
|
||||
type relayDialerFactory struct{}
|
||||
|
||||
func (relayDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
|
||||
func (relayDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
|
||||
return &relayDialer{
|
||||
cfg: cfg,
|
||||
tlsCfg: tlsCfg,
|
||||
|
||||
@@ -29,7 +29,7 @@ type relayListener struct {
|
||||
onAddressesChangedNotifier
|
||||
|
||||
uri *url.URL
|
||||
cfg *config.Wrapper
|
||||
cfg config.Wrapper
|
||||
tlsCfg *tls.Config
|
||||
conns chan internalConn
|
||||
factory listenerFactory
|
||||
@@ -180,7 +180,7 @@ func (t *relayListener) NATType() string {
|
||||
|
||||
type relayListenerFactory struct{}
|
||||
|
||||
func (f *relayListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
|
||||
func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
|
||||
return &relayListener{
|
||||
uri: uri,
|
||||
cfg: cfg,
|
||||
|
||||
@@ -51,6 +51,7 @@ const (
|
||||
|
||||
// From go/src/crypto/tls/cipher_suites.go
|
||||
var tlsCipherSuiteNames = map[uint16]string{
|
||||
// TLS 1.2
|
||||
0x0005: "TLS_RSA_WITH_RC4_128_SHA",
|
||||
0x000a: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0x002f: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
@@ -73,13 +74,29 @@ var tlsCipherSuiteNames = map[uint16]string{
|
||||
0xc02c: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
0xcca8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
||||
0xcca9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
|
||||
// TLS 1.3
|
||||
0x1301: "TLS_AES_128_GCM_SHA256",
|
||||
0x1302: "TLS_AES_256_GCM_SHA384",
|
||||
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
|
||||
}
|
||||
|
||||
var tlsVersionNames = map[uint16]string{
|
||||
tls.VersionTLS12: "TLS1.2",
|
||||
772: "TLS1.3", // tls.VersionTLS13 constant available in Go 1.12+
|
||||
}
|
||||
|
||||
// Service listens and dials all configured unconnected devices, via supported
|
||||
// dialers. Successful connections are handed to the model.
|
||||
type Service struct {
|
||||
type Service interface {
|
||||
suture.Service
|
||||
Status() map[string]interface{}
|
||||
NATType() string
|
||||
}
|
||||
|
||||
type service struct {
|
||||
*suture.Supervisor
|
||||
cfg *config.Wrapper
|
||||
cfg config.Wrapper
|
||||
myID protocol.DeviceID
|
||||
model Model
|
||||
tlsCfg *tls.Config
|
||||
@@ -97,10 +114,10 @@ type Service struct {
|
||||
listenerSupervisor *suture.Supervisor
|
||||
}
|
||||
|
||||
func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
|
||||
bepProtocolName string, tlsDefaultCommonName string) *Service {
|
||||
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
|
||||
bepProtocolName string, tlsDefaultCommonName string) *service {
|
||||
|
||||
service := &Service{
|
||||
service := &service{
|
||||
Supervisor: suture.New("connections.Service", suture.Spec{
|
||||
Log: func(line string) {
|
||||
l.Infoln(line)
|
||||
@@ -156,7 +173,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
|
||||
return service
|
||||
}
|
||||
|
||||
func (s *Service) handle() {
|
||||
func (s *service) handle() {
|
||||
next:
|
||||
for c := range s.conns {
|
||||
cs := c.ConnectionState()
|
||||
@@ -275,14 +292,14 @@ next:
|
||||
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
|
||||
modelConn := completeConn{c, protoConn}
|
||||
|
||||
l.Infof("Established secure connection to %s at %s (%s)", remoteID, c, tlsCipherSuiteNames[c.ConnectionState().CipherSuite])
|
||||
l.Infof("Established secure connection to %s at %s", remoteID, c)
|
||||
|
||||
s.model.AddConnection(modelConn, hello)
|
||||
continue next
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) connect() {
|
||||
func (s *service) connect() {
|
||||
nextDial := make(map[string]time.Time)
|
||||
|
||||
// Used as delay for the first few connection attempts, increases
|
||||
@@ -433,7 +450,7 @@ func (s *Service) connect() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) isLANHost(host string) bool {
|
||||
func (s *service) isLANHost(host string) bool {
|
||||
// Probably we are called with an ip:port combo which we can resolve as
|
||||
// a TCP address.
|
||||
if addr, err := net.ResolveTCPAddr("tcp", host); err == nil {
|
||||
@@ -447,7 +464,7 @@ func (s *Service) isLANHost(host string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) isLAN(addr net.Addr) bool {
|
||||
func (s *service) isLAN(addr net.Addr) bool {
|
||||
var ip net.IP
|
||||
|
||||
switch addr := addr.(type) {
|
||||
@@ -488,7 +505,7 @@ func (s *Service) isLAN(addr net.Addr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
|
||||
func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
|
||||
// must be called with listenerMut held
|
||||
|
||||
l.Debugln("Starting listener", uri)
|
||||
@@ -500,7 +517,7 @@ func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Service) logListenAddressesChangedEvent(l genericListener) {
|
||||
func (s *service) logListenAddressesChangedEvent(l genericListener) {
|
||||
events.Default.Log(events.ListenAddressesChanged, map[string]interface{}{
|
||||
"address": l.URI(),
|
||||
"lan": l.LANAddresses(),
|
||||
@@ -508,11 +525,11 @@ func (s *Service) logListenAddressesChangedEvent(l genericListener) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
|
||||
func (s *service) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
|
||||
for _, dev := range to.Devices {
|
||||
newDevices[dev.DeviceID] = true
|
||||
@@ -589,7 +606,7 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Service) AllAddresses() []string {
|
||||
func (s *service) AllAddresses() []string {
|
||||
s.listenersMut.RLock()
|
||||
var addrs []string
|
||||
for _, listener := range s.listeners {
|
||||
@@ -604,7 +621,7 @@ func (s *Service) AllAddresses() []string {
|
||||
return util.UniqueStrings(addrs)
|
||||
}
|
||||
|
||||
func (s *Service) ExternalAddresses() []string {
|
||||
func (s *service) ExternalAddresses() []string {
|
||||
s.listenersMut.RLock()
|
||||
var addrs []string
|
||||
for _, listener := range s.listeners {
|
||||
@@ -616,7 +633,7 @@ func (s *Service) ExternalAddresses() []string {
|
||||
return util.UniqueStrings(addrs)
|
||||
}
|
||||
|
||||
func (s *Service) Status() map[string]interface{} {
|
||||
func (s *service) Status() map[string]interface{} {
|
||||
s.listenersMut.RLock()
|
||||
result := make(map[string]interface{})
|
||||
for addr, listener := range s.listeners {
|
||||
@@ -636,7 +653,7 @@ func (s *Service) Status() map[string]interface{} {
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Service) NATType() string {
|
||||
func (s *service) NATType() string {
|
||||
s.listenersMut.RLock()
|
||||
defer s.listenersMut.RUnlock()
|
||||
for _, listener := range s.listeners {
|
||||
|
||||
@@ -27,6 +27,7 @@ type Connection interface {
|
||||
RemoteAddr() net.Addr
|
||||
Priority() int
|
||||
String() string
|
||||
Crypto() string
|
||||
}
|
||||
|
||||
// completeConn is the aggregation of an internalConn and the
|
||||
@@ -101,6 +102,11 @@ func (c internalConn) Priority() int {
|
||||
return c.priority
|
||||
}
|
||||
|
||||
func (c internalConn) Crypto() string {
|
||||
cs := c.ConnectionState()
|
||||
return fmt.Sprintf("%s-%s", tlsVersionNames[cs.Version], tlsCipherSuiteNames[cs.CipherSuite])
|
||||
}
|
||||
|
||||
func (c internalConn) Transport() string {
|
||||
transport := c.connType.Transport()
|
||||
host, _, err := net.SplitHostPort(c.LocalAddr().String())
|
||||
@@ -118,11 +124,11 @@ func (c internalConn) Transport() string {
|
||||
}
|
||||
|
||||
func (c internalConn) String() string {
|
||||
return fmt.Sprintf("%s-%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type())
|
||||
return fmt.Sprintf("%s-%s/%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto())
|
||||
}
|
||||
|
||||
type dialerFactory interface {
|
||||
New(*config.Wrapper, *tls.Config) genericDialer
|
||||
New(config.Wrapper, *tls.Config) genericDialer
|
||||
Priority() int
|
||||
AlwaysWAN() bool
|
||||
Valid(config.Configuration) error
|
||||
@@ -135,7 +141,7 @@ type genericDialer interface {
|
||||
}
|
||||
|
||||
type listenerFactory interface {
|
||||
New(*url.URL, *config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
|
||||
New(*url.URL, config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
|
||||
Valid(config.Configuration) error
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
}
|
||||
|
||||
type tcpDialer struct {
|
||||
cfg *config.Wrapper
|
||||
cfg config.Wrapper
|
||||
tlsCfg *tls.Config
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (d *tcpDialer) RedialFrequency() time.Duration {
|
||||
|
||||
type tcpDialerFactory struct{}
|
||||
|
||||
func (tcpDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
|
||||
func (tcpDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
|
||||
return &tcpDialer{
|
||||
cfg: cfg,
|
||||
tlsCfg: tlsCfg,
|
||||
|
||||
@@ -29,7 +29,7 @@ type tcpListener struct {
|
||||
onAddressesChangedNotifier
|
||||
|
||||
uri *url.URL
|
||||
cfg *config.Wrapper
|
||||
cfg config.Wrapper
|
||||
tlsCfg *tls.Config
|
||||
stop chan struct{}
|
||||
conns chan internalConn
|
||||
@@ -195,7 +195,7 @@ func (t *tcpListener) NATType() string {
|
||||
|
||||
type tcpListenerFactory struct{}
|
||||
|
||||
func (f *tcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
|
||||
func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
|
||||
return &tcpListener{
|
||||
uri: fixupPort(uri, config.DefaultTCPPort),
|
||||
cfg: cfg,
|
||||
|
||||
@@ -73,7 +73,7 @@ func addToBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
|
||||
name := []byte(f.Name)
|
||||
for i, block := range f.Blocks {
|
||||
binary.BigEndian.PutUint32(blockBuf, uint32(i))
|
||||
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
keyBuf = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
t.Put(keyBuf, blockBuf)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func discardFromBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
|
||||
if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() {
|
||||
name := []byte(ef.Name)
|
||||
for _, block := range ef.Blocks {
|
||||
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
keyBuf = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
t.Delete(keyBuf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
|
||||
if ok {
|
||||
if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() {
|
||||
for _, block := range ef.Blocks {
|
||||
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
keyBuf = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
t.Delete(keyBuf)
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
|
||||
l.Debugf("insert (local); folder=%q %v", folder, f)
|
||||
t.Put(dk, mustMarshal(&f))
|
||||
|
||||
gk = t.db.keyer.GenerateGlobalVersionKey(gk, folder, []byte(f.Name))
|
||||
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, []byte(f.Name))
|
||||
keyBuf, _ = t.updateGlobal(gk, keyBuf, folder, protocol.LocalDeviceID[:], f, meta)
|
||||
|
||||
keyBuf = db.keyer.GenerateSequenceKey(keyBuf, folder, f.Sequence)
|
||||
@@ -110,7 +110,7 @@ func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
|
||||
if !f.IsDirectory() && !f.IsDeleted() && !f.IsInvalid() {
|
||||
for i, block := range f.Blocks {
|
||||
binary.BigEndian.PutUint32(blockBuf, uint32(i))
|
||||
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
keyBuf = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
t.Put(keyBuf, blockBuf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import (
|
||||
|
||||
const (
|
||||
dbMaxOpenFiles = 100
|
||||
dbWriteBuffer = 4 << 20
|
||||
dbWriteBuffer = 16 << 20
|
||||
dbFlushBatch = dbWriteBuffer / 4 // Some leeway for any leveldb in-memory optimizations
|
||||
)
|
||||
|
||||
// Lowlevel is the lowest level database interface. It has a very simple
|
||||
@@ -127,3 +128,29 @@ func leveldbIsCorrupted(err error) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type batch struct {
|
||||
*leveldb.Batch
|
||||
db *Lowlevel
|
||||
}
|
||||
|
||||
func (db *Lowlevel) newBatch() *batch {
|
||||
return &batch{
|
||||
Batch: new(leveldb.Batch),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// checkFlush flushes and resets the batch if its size exceeds dbFlushBatch.
|
||||
func (b *batch) checkFlush() {
|
||||
if len(b.Dump()) > dbFlushBatch {
|
||||
b.flush()
|
||||
b.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *batch) flush() {
|
||||
if err := b.db.Write(b.Batch, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
@@ -39,21 +38,12 @@ func NewNamespacedKV(db *Lowlevel, prefix string) *NamespacedKV {
|
||||
func (n *NamespacedKV) Reset() {
|
||||
it := n.db.NewIterator(util.BytesPrefix(n.prefix), nil)
|
||||
defer it.Release()
|
||||
batch := new(leveldb.Batch)
|
||||
batch := n.db.newBatch()
|
||||
for it.Next() {
|
||||
batch.Delete(it.Key())
|
||||
if batch.Len() > batchFlushSize {
|
||||
if err := n.db.Write(batch, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
batch.Reset()
|
||||
}
|
||||
}
|
||||
if batch.Len() > 0 {
|
||||
if err := n.db.Write(batch, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
batch.checkFlush()
|
||||
}
|
||||
batch.flush()
|
||||
}
|
||||
|
||||
// PutInt64 stores a new int64. Any existing value (even if of another type)
|
||||
|
||||
@@ -104,18 +104,18 @@ func (db *schemaUpdater) updateSchema0to1() {
|
||||
var gk, buf []byte
|
||||
|
||||
for dbi.Next() {
|
||||
t.checkFlush()
|
||||
|
||||
folder, ok := db.keyer.FolderFromDeviceFileKey(dbi.Key())
|
||||
if !ok {
|
||||
// not having the folder in the index is bad; delete and continue
|
||||
t.Delete(dbi.Key())
|
||||
t.checkFlush()
|
||||
continue
|
||||
}
|
||||
device, ok := db.keyer.DeviceFromDeviceFileKey(dbi.Key())
|
||||
if !ok {
|
||||
// not having the device in the index is bad; delete and continue
|
||||
t.Delete(dbi.Key())
|
||||
t.checkFlush()
|
||||
continue
|
||||
}
|
||||
name := db.keyer.NameFromDeviceFileKey(dbi.Key())
|
||||
@@ -128,7 +128,6 @@ func (db *schemaUpdater) updateSchema0to1() {
|
||||
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
|
||||
buf = t.removeFromGlobal(gk, buf, folder, device, nil, nil)
|
||||
t.Delete(dbi.Key())
|
||||
t.checkFlush()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -149,7 +148,6 @@ func (db *schemaUpdater) updateSchema0to1() {
|
||||
panic("can't happen: " + err.Error())
|
||||
}
|
||||
t.Put(dbi.Key(), bs)
|
||||
t.checkFlush()
|
||||
symlinkConv++
|
||||
}
|
||||
|
||||
@@ -210,7 +208,7 @@ func (db *schemaUpdater) updateSchema2to3() {
|
||||
if !need(f, ok, v) {
|
||||
return true
|
||||
}
|
||||
nk = t.db.keyer.GenerateNeedFileKey(nk, folder, []byte(f.FileName()))
|
||||
nk = t.keyer.GenerateNeedFileKey(nk, folder, []byte(f.FileName()))
|
||||
t.Put(nk, nil)
|
||||
t.checkFlush()
|
||||
return true
|
||||
@@ -282,7 +280,7 @@ func (db *schemaUpdater) updateSchema6to7() {
|
||||
svl, err := t.Get(gk, nil)
|
||||
if err != nil {
|
||||
// If there is no global list, we hardly need it.
|
||||
t.Delete(t.db.keyer.GenerateNeedFileKey(nk, folder, name))
|
||||
t.Delete(t.keyer.GenerateNeedFileKey(nk, folder, name))
|
||||
return true
|
||||
}
|
||||
var fl VersionList
|
||||
@@ -293,7 +291,7 @@ func (db *schemaUpdater) updateSchema6to7() {
|
||||
return true
|
||||
}
|
||||
if localFV, haveLocalFV := fl.Get(protocol.LocalDeviceID[:]); !need(global, haveLocalFV, localFV.Version) {
|
||||
t.Delete(t.db.keyer.GenerateNeedFileKey(nk, folder, name))
|
||||
t.Delete(t.keyer.GenerateNeedFileKey(nk, folder, name))
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -12,13 +12,10 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// Flush batches to disk when they contain this many records.
|
||||
const batchFlushSize = 64
|
||||
|
||||
// A readOnlyTransaction represents a database snapshot.
|
||||
type readOnlyTransaction struct {
|
||||
*leveldb.Snapshot
|
||||
db *instance
|
||||
keyer keyer
|
||||
}
|
||||
|
||||
func (db *instance) newReadOnlyTransaction() readOnlyTransaction {
|
||||
@@ -28,7 +25,7 @@ func (db *instance) newReadOnlyTransaction() readOnlyTransaction {
|
||||
}
|
||||
return readOnlyTransaction{
|
||||
Snapshot: snap,
|
||||
db: db,
|
||||
keyer: db.keyer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +34,7 @@ func (t readOnlyTransaction) close() {
|
||||
}
|
||||
|
||||
func (t readOnlyTransaction) getFile(folder, device, file []byte) (protocol.FileInfo, bool) {
|
||||
return t.getFileByKey(t.db.keyer.GenerateDeviceFileKey(nil, folder, device, file))
|
||||
return t.getFileByKey(t.keyer.GenerateDeviceFileKey(nil, folder, device, file))
|
||||
}
|
||||
|
||||
func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool) {
|
||||
@@ -65,7 +62,7 @@ func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (FileIntf, boo
|
||||
}
|
||||
|
||||
func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate bool) ([]byte, FileIntf, bool) {
|
||||
keyBuf = t.db.keyer.GenerateGlobalVersionKey(keyBuf, folder, file)
|
||||
keyBuf = t.keyer.GenerateGlobalVersionKey(keyBuf, folder, file)
|
||||
|
||||
bs, err := t.Get(keyBuf, nil)
|
||||
if err != nil {
|
||||
@@ -77,7 +74,7 @@ func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate boo
|
||||
return keyBuf, nil, false
|
||||
}
|
||||
|
||||
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, vl.Versions[0].Device, file)
|
||||
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, vl.Versions[0].Device, file)
|
||||
if fi, ok := t.getFileTrunc(keyBuf, truncate); ok {
|
||||
return keyBuf, fi, true
|
||||
}
|
||||
@@ -90,14 +87,13 @@ func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate boo
|
||||
// batch size.
|
||||
type readWriteTransaction struct {
|
||||
readOnlyTransaction
|
||||
*leveldb.Batch
|
||||
*batch
|
||||
}
|
||||
|
||||
func (db *instance) newReadWriteTransaction() readWriteTransaction {
|
||||
t := db.newReadOnlyTransaction()
|
||||
return readWriteTransaction{
|
||||
readOnlyTransaction: t,
|
||||
Batch: new(leveldb.Batch),
|
||||
readOnlyTransaction: db.newReadOnlyTransaction(),
|
||||
batch: db.newBatch(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,19 +102,6 @@ func (t readWriteTransaction) close() {
|
||||
t.readOnlyTransaction.close()
|
||||
}
|
||||
|
||||
func (t readWriteTransaction) checkFlush() {
|
||||
if t.Batch.Len() > batchFlushSize {
|
||||
t.flush()
|
||||
t.Batch.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (t readWriteTransaction) flush() {
|
||||
if err := t.db.Write(t.Batch, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// updateGlobal adds this device+version to the version list for the given
|
||||
// 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.
|
||||
@@ -142,7 +125,7 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
|
||||
// Inserted a new newest version
|
||||
global = file
|
||||
} else {
|
||||
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, name)
|
||||
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, name)
|
||||
if new, ok := t.getFileByKey(keyBuf); ok {
|
||||
global = new
|
||||
} else {
|
||||
@@ -167,7 +150,7 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
|
||||
// The previous newest version is now at index 1
|
||||
oldGlobalFV = fl.Versions[1]
|
||||
}
|
||||
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, oldGlobalFV.Device, name)
|
||||
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, oldGlobalFV.Device, name)
|
||||
if oldFile, ok := t.getFileByKey(keyBuf); ok {
|
||||
// A failure to get the file here is surprising and our
|
||||
// global size data will be incorrect until a restart...
|
||||
@@ -187,7 +170,7 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
|
||||
// device according to the version list and global FileInfo given and updates
|
||||
// the db accordingly.
|
||||
func (t readWriteTransaction) updateLocalNeed(keyBuf, folder, name []byte, fl VersionList, global protocol.FileInfo) []byte {
|
||||
keyBuf = t.db.keyer.GenerateNeedFileKey(keyBuf, folder, name)
|
||||
keyBuf = t.keyer.GenerateNeedFileKey(keyBuf, folder, name)
|
||||
hasNeeded, _ := t.Has(keyBuf, nil)
|
||||
if localFV, haveLocalFV := fl.Get(protocol.LocalDeviceID[:]); need(global, haveLocalFV, localFV.Version) {
|
||||
if !hasNeeded {
|
||||
@@ -246,21 +229,21 @@ func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device []byte
|
||||
if removedAt == 0 {
|
||||
// A failure to get the file here is surprising and our
|
||||
// global size data will be incorrect until a restart...
|
||||
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, device, file)
|
||||
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, device, file)
|
||||
if f, ok := t.getFileByKey(keyBuf); ok {
|
||||
meta.removeFile(protocol.GlobalDeviceID, f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fl.Versions) == 0 {
|
||||
keyBuf = t.db.keyer.GenerateNeedFileKey(keyBuf, folder, file)
|
||||
keyBuf = t.keyer.GenerateNeedFileKey(keyBuf, folder, file)
|
||||
t.Delete(keyBuf)
|
||||
t.Delete(gk)
|
||||
return keyBuf
|
||||
}
|
||||
|
||||
if removedAt == 0 {
|
||||
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, file)
|
||||
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, file)
|
||||
global, ok := t.getFileByKey(keyBuf)
|
||||
if !ok {
|
||||
panic("This file must exist in the db")
|
||||
|
||||
@@ -144,7 +144,7 @@ func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFileInfo{fi}, err
|
||||
return basicFileInfo{fi}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Remove(name string) error {
|
||||
@@ -184,7 +184,7 @@ func (f *BasicFilesystem) Stat(name string) (FileInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFileInfo{fi}, err
|
||||
return basicFileInfo{fi}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
|
||||
@@ -215,7 +215,7 @@ func (f *BasicFilesystem) Open(name string) (File, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFile{fd, name}, err
|
||||
return basicFile{fd, name}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File, error) {
|
||||
@@ -227,7 +227,7 @@ func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFile{fd, name}, err
|
||||
return basicFile{fd, name}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Create(name string) (File, error) {
|
||||
@@ -239,7 +239,7 @@ func (f *BasicFilesystem) Create(name string) (File, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFile{fd, name}, err
|
||||
return basicFile{fd, name}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Walk(root string, walkFn WalkFunc) error {
|
||||
@@ -283,8 +283,8 @@ func (f *BasicFilesystem) URI() string {
|
||||
func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
|
||||
// Like os.SameFile, we always return false unless fi1 and fi2 were created
|
||||
// by this package's Stat/Lstat method.
|
||||
f1, ok1 := fi1.(fsFileInfo)
|
||||
f2, ok2 := fi2.(fsFileInfo)
|
||||
f1, ok1 := fi1.(basicFileInfo)
|
||||
f2, ok2 := fi2.(basicFileInfo)
|
||||
if !ok1 || !ok2 {
|
||||
return false
|
||||
}
|
||||
@@ -292,36 +292,36 @@ func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
|
||||
return os.SameFile(f1.FileInfo, f2.FileInfo)
|
||||
}
|
||||
|
||||
// fsFile implements the fs.File interface on top of an os.File
|
||||
type fsFile struct {
|
||||
// basicFile implements the fs.File interface on top of an os.File
|
||||
type basicFile struct {
|
||||
*os.File
|
||||
name string
|
||||
}
|
||||
|
||||
func (f fsFile) Name() string {
|
||||
func (f basicFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f fsFile) Stat() (FileInfo, error) {
|
||||
func (f basicFile) Stat() (FileInfo, error) {
|
||||
info, err := f.File.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFileInfo{info}, nil
|
||||
return basicFileInfo{info}, nil
|
||||
}
|
||||
|
||||
// fsFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
|
||||
type fsFileInfo struct {
|
||||
// basicFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
|
||||
type basicFileInfo struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (e fsFileInfo) IsSymlink() bool {
|
||||
// Must use fsFileInfo.Mode() because it may apply magic.
|
||||
func (e basicFileInfo) IsSymlink() bool {
|
||||
// Must use basicFileInfo.Mode() because it may apply magic.
|
||||
return e.Mode()&ModeSymlink != 0
|
||||
}
|
||||
|
||||
func (e fsFileInfo) IsRegular() bool {
|
||||
// Must use fsFileInfo.Mode() because it may apply magic.
|
||||
func (e basicFileInfo) IsRegular() bool {
|
||||
// Must use basicFileInfo.Mode() because it may apply magic.
|
||||
return e.Mode()&ModeType == 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019 The Syncthing Authors.
|
||||
// Copyright (C) 2017 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,
|
||||
@@ -10,14 +10,18 @@ package fs
|
||||
|
||||
import "syscall"
|
||||
|
||||
func (e fsFileInfo) Owner() int {
|
||||
func (e basicFileInfo) Mode() FileMode {
|
||||
return FileMode(e.FileInfo.Mode())
|
||||
}
|
||||
|
||||
func (e basicFileInfo) Owner() int {
|
||||
if st, ok := e.Sys().(*syscall.Stat_t); ok {
|
||||
return int(st.Uid)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (e fsFileInfo) Group() int {
|
||||
func (e basicFileInfo) Group() int {
|
||||
if st, ok := e.Sys().(*syscall.Stat_t); ok {
|
||||
return int(st.Gid)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func isWindowsExecutable(path string) bool {
|
||||
return execExts[strings.ToLower(filepath.Ext(path))]
|
||||
}
|
||||
|
||||
func (e fsFileInfo) Mode() FileMode {
|
||||
func (e basicFileInfo) Mode() FileMode {
|
||||
m := e.FileInfo.Mode()
|
||||
if m&os.ModeSymlink != 0 && e.Size() > 0 {
|
||||
// "Symlinks" with nonzero size are in fact "hard" links, such as
|
||||
@@ -48,3 +48,11 @@ func (e fsFileInfo) Mode() FileMode {
|
||||
m &^= 0022
|
||||
return FileMode(m)
|
||||
}
|
||||
|
||||
func (e basicFileInfo) Owner() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (e basicFileInfo) Group() int {
|
||||
return -1
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/syncthing/notify"
|
||||
)
|
||||
@@ -32,6 +33,12 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove `\\?\` prefix if the path is just a drive letter as a dirty
|
||||
// fix for https://github.com/syncthing/syncthing/issues/5578
|
||||
if runtime.GOOS == "windows" && len(absName) <= 7 && len(absName) > 4 && absName[:4] == `\\?\` {
|
||||
absName = absName[4:]
|
||||
}
|
||||
|
||||
outChan := make(chan Event)
|
||||
backendChan := make(chan notify.EventInfo, backendBuffer)
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (C) 2019 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fs
|
||||
|
||||
func (e fsFileInfo) Owner() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (e fsFileInfo) Group() int {
|
||||
return -1
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (C) 2017 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
func (e fsFileInfo) Mode() FileMode {
|
||||
return FileMode(e.FileInfo.Mode())
|
||||
}
|
||||
@@ -25,7 +25,6 @@ const (
|
||||
LevelVerbose
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelFatal
|
||||
NumLevels
|
||||
)
|
||||
|
||||
@@ -49,8 +48,6 @@ type Logger interface {
|
||||
Infof(format string, vals ...interface{})
|
||||
Warnln(vals ...interface{})
|
||||
Warnf(format string, vals ...interface{})
|
||||
Fatalln(vals ...interface{})
|
||||
Fatalf(format string, vals ...interface{})
|
||||
ShouldDebug(facility string) bool
|
||||
SetDebug(facility string, enabled bool)
|
||||
Facilities() map[string]string
|
||||
@@ -190,28 +187,6 @@ func (l *logger) Warnf(format string, vals ...interface{}) {
|
||||
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{}) {
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
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{}) {
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
l.logger.Output(2, "FATAL: "+s)
|
||||
l.callHandlers(LevelFatal, s)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// ShouldDebug returns true if the given facility has debugging enabled.
|
||||
func (l *logger) ShouldDebug(facility string) bool {
|
||||
l.mut.Lock()
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/scanner"
|
||||
"github.com/syncthing/syncthing/lib/stats"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/watchaggregator"
|
||||
)
|
||||
@@ -37,10 +38,14 @@ var errWatchNotStarted = errors.New("not started")
|
||||
type folder struct {
|
||||
stateTracker
|
||||
config.FolderConfiguration
|
||||
*stats.FolderStatisticsReference
|
||||
|
||||
localFlags uint32
|
||||
|
||||
model *Model
|
||||
model *model
|
||||
shortID protocol.ShortID
|
||||
fset *db.FileSet
|
||||
ignores *ignore.Matcher
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
@@ -73,15 +78,18 @@ type puller interface {
|
||||
pull() bool // true when successfull and should not be retried
|
||||
}
|
||||
|
||||
func newFolder(model *Model, cfg config.FolderConfiguration) folder {
|
||||
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration) folder {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return folder{
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
FolderConfiguration: cfg,
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
FolderConfiguration: cfg,
|
||||
FolderStatisticsReference: stats.NewFolderStatisticsReference(model.db, cfg.ID),
|
||||
|
||||
model: model,
|
||||
shortID: model.shortID,
|
||||
fset: fset,
|
||||
ignores: ignores,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
|
||||
@@ -190,9 +198,9 @@ func (f *folder) Serve() {
|
||||
|
||||
func (f *folder) BringToFront(string) {}
|
||||
|
||||
func (f *folder) Override(fs *db.FileSet, updateFn func([]protocol.FileInfo)) {}
|
||||
func (f *folder) Override() {}
|
||||
|
||||
func (f *folder) Revert(fs *db.FileSet, updateFn func([]protocol.FileInfo)) {}
|
||||
func (f *folder) Revert() {}
|
||||
|
||||
func (f *folder) DelayScan(next time.Duration) {
|
||||
f.Delay(next)
|
||||
@@ -285,11 +293,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
f.model.fmut.RLock()
|
||||
fset := f.model.folderFiles[f.ID]
|
||||
ignores := f.model.folderIgnores[f.ID]
|
||||
f.model.fmut.RUnlock()
|
||||
mtimefs := fset.MtimeFS()
|
||||
mtimefs := f.fset.MtimeFS()
|
||||
|
||||
f.setState(FolderScanWaiting)
|
||||
scanLimiter.take(1)
|
||||
@@ -311,16 +315,16 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
// Check if the ignore patterns changed as part of scanning this folder.
|
||||
// If they did we should schedule a pull of the folder so that we
|
||||
// request things we might have suddenly become unignored and so on.
|
||||
oldHash := ignores.Hash()
|
||||
oldHash := f.ignores.Hash()
|
||||
defer func() {
|
||||
if ignores.Hash() != oldHash {
|
||||
if f.ignores.Hash() != oldHash {
|
||||
l.Debugln("Folder", f.Description(), "ignore patterns change detected while scanning; triggering puller")
|
||||
f.ignoresUpdated()
|
||||
f.SchedulePull()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||
err = fmt.Errorf("loading ignores: %v", err)
|
||||
f.setError(err)
|
||||
return err
|
||||
@@ -329,8 +333,8 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
// Clean the list of subitems to ensure that we start at a known
|
||||
// directory, and don't scan subdirectories of things we've already
|
||||
// scanned.
|
||||
subDirs = unifySubs(subDirs, func(f string) bool {
|
||||
_, ok := fset.Get(protocol.LocalDeviceID, f)
|
||||
subDirs = unifySubs(subDirs, func(file string) bool {
|
||||
_, ok := f.fset.Get(protocol.LocalDeviceID, file)
|
||||
return ok
|
||||
})
|
||||
|
||||
@@ -339,14 +343,14 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
fchan := scanner.Walk(f.ctx, scanner.Config{
|
||||
Folder: f.ID,
|
||||
Subs: subDirs,
|
||||
Matcher: ignores,
|
||||
Matcher: f.ignores,
|
||||
TempLifetime: time.Duration(f.model.cfg.Options().KeepTemporariesH) * time.Hour,
|
||||
CurrentFiler: cFiler{f.model, f.ID},
|
||||
CurrentFiler: cFiler{f.fset},
|
||||
Filesystem: mtimefs,
|
||||
IgnorePerms: f.IgnorePerms,
|
||||
AutoNormalize: f.AutoNormalize,
|
||||
Hashers: f.model.numHashers(f.ID),
|
||||
ShortID: f.model.shortID,
|
||||
ShortID: f.shortID,
|
||||
ProgressTickIntervalS: f.ScanProgressIntervalS,
|
||||
UseLargeBlocks: f.UseLargeBlocks,
|
||||
LocalFlags: f.localFlags,
|
||||
@@ -357,7 +361,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
l.Debugf("Stopping scan of folder %s due to: %s", f.Description(), err)
|
||||
return err
|
||||
}
|
||||
f.model.updateLocalsFromScanning(f.ID, fs)
|
||||
f.updateLocalsFromScanning(fs)
|
||||
return nil
|
||||
}
|
||||
// Resolve items which are identical with the global state.
|
||||
@@ -365,7 +369,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
oldBatchFn := batchFn // can't reference batchFn directly (recursion)
|
||||
batchFn = func(fs []protocol.FileInfo) error {
|
||||
for i := range fs {
|
||||
switch gf, ok := fset.GetGlobal(fs[i].Name); {
|
||||
switch gf, ok := f.fset.GetGlobal(fs[i].Name); {
|
||||
case !ok:
|
||||
continue
|
||||
case gf.IsEquivalentOptional(fs[i], false, false, protocol.FlagLocalReceiveOnly):
|
||||
@@ -425,7 +429,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
for _, sub := range subDirs {
|
||||
var iterError error
|
||||
|
||||
fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
|
||||
f.fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
|
||||
file := fi.(db.FileInfoTruncated)
|
||||
|
||||
if err := batch.flushIfFull(); err != nil {
|
||||
@@ -436,7 +440,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
if ignoredParent != "" && !fs.IsParent(file.Name, ignoredParent) {
|
||||
for _, file := range toIgnore {
|
||||
l.Debugln("marking file as ignored", file)
|
||||
nf := file.ConvertToIgnoredFileInfo(f.model.id.Short())
|
||||
nf := file.ConvertToIgnoredFileInfo(f.shortID)
|
||||
batch.append(nf)
|
||||
changes++
|
||||
if err := batch.flushIfFull(); err != nil {
|
||||
@@ -448,7 +452,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
ignoredParent = ""
|
||||
}
|
||||
|
||||
switch ignored := ignores.Match(file.Name).IsIgnored(); {
|
||||
switch ignored := f.ignores.Match(file.Name).IsIgnored(); {
|
||||
case !file.IsIgnored() && ignored:
|
||||
// File was not ignored at last pass but has been ignored.
|
||||
if file.IsDirectory() {
|
||||
@@ -463,7 +467,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
}
|
||||
|
||||
l.Debugln("marking file as ignored", f)
|
||||
nf := file.ConvertToIgnoredFileInfo(f.model.id.Short())
|
||||
nf := file.ConvertToIgnoredFileInfo(f.shortID)
|
||||
batch.append(nf)
|
||||
changes++
|
||||
|
||||
@@ -490,9 +494,9 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
Size: 0,
|
||||
ModifiedS: file.ModifiedS,
|
||||
ModifiedNs: file.ModifiedNs,
|
||||
ModifiedBy: f.model.id.Short(),
|
||||
ModifiedBy: f.shortID,
|
||||
Deleted: true,
|
||||
Version: file.Version.Update(f.model.shortID),
|
||||
Version: file.Version.Update(f.shortID),
|
||||
LocalFlags: f.localFlags,
|
||||
}
|
||||
// We do not want to override the global version
|
||||
@@ -501,7 +505,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
// other existing versions, which will be resolved
|
||||
// by the normal pulling mechanisms.
|
||||
if file.ShouldConflict() {
|
||||
nf.Version = nf.Version.DropOthers(f.model.shortID)
|
||||
nf.Version = nf.Version.DropOthers(f.shortID)
|
||||
}
|
||||
|
||||
batch.append(nf)
|
||||
@@ -513,7 +517,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
if iterError == nil && len(toIgnore) > 0 {
|
||||
for _, file := range toIgnore {
|
||||
l.Debugln("marking file as ignored", f)
|
||||
nf := file.ConvertToIgnoredFileInfo(f.model.id.Short())
|
||||
nf := file.ConvertToIgnoredFileInfo(f.shortID)
|
||||
batch.append(nf)
|
||||
changes++
|
||||
if iterError = batch.flushIfFull(); iterError != nil {
|
||||
@@ -532,7 +536,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
f.model.folderStatRef(f.ID).ScanCompleted()
|
||||
f.ScanCompleted()
|
||||
f.setState(FolderIdle)
|
||||
return nil
|
||||
}
|
||||
@@ -603,24 +607,21 @@ func (f *folder) restartWatch() {
|
||||
// this asynchronously, you should probably use scheduleWatchRestart instead.
|
||||
func (f *folder) startWatch() {
|
||||
ctx, cancel := context.WithCancel(f.ctx)
|
||||
f.model.fmut.RLock()
|
||||
ignores := f.model.folderIgnores[f.folderID]
|
||||
f.model.fmut.RUnlock()
|
||||
f.watchMut.Lock()
|
||||
f.watchChan = make(chan []string)
|
||||
f.watchCancel = cancel
|
||||
f.watchMut.Unlock()
|
||||
go f.startWatchAsync(ctx, ignores)
|
||||
go f.startWatchAsync(ctx)
|
||||
}
|
||||
|
||||
// startWatchAsync tries to start the filesystem watching and retries every minute on failure.
|
||||
// It is a convenience function that should not be used except in startWatch.
|
||||
func (f *folder) startWatchAsync(ctx context.Context, ignores *ignore.Matcher) {
|
||||
func (f *folder) startWatchAsync(ctx context.Context) {
|
||||
timer := time.NewTimer(0)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
eventChan, err := f.Filesystem().Watch(".", ignores, ctx, f.IgnorePerms)
|
||||
eventChan, err := f.Filesystem().Watch(".", f.ignores, ctx, f.IgnorePerms)
|
||||
f.watchMut.Lock()
|
||||
prevErr := f.watchErr
|
||||
f.watchErr = err
|
||||
@@ -736,6 +737,86 @@ func (f *folder) Errors() []FileError {
|
||||
return append([]FileError{}, f.scanErrors...)
|
||||
}
|
||||
|
||||
// ForceRescan marks the file such that it gets rehashed on next scan and then
|
||||
// immediately executes that scan.
|
||||
func (f *folder) ForceRescan(file protocol.FileInfo) error {
|
||||
file.SetMustRescan(f.shortID)
|
||||
f.fset.Update(protocol.LocalDeviceID, []protocol.FileInfo{file})
|
||||
|
||||
return f.Scan([]string{file.Name})
|
||||
}
|
||||
|
||||
func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) {
|
||||
f.updateLocals(fs)
|
||||
|
||||
f.emitDiskChangeEvents(fs, events.LocalChangeDetected)
|
||||
}
|
||||
|
||||
func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) {
|
||||
f.updateLocals(fs)
|
||||
|
||||
f.emitDiskChangeEvents(fs, events.RemoteChangeDetected)
|
||||
}
|
||||
|
||||
func (f *folder) updateLocals(fs []protocol.FileInfo) {
|
||||
f.fset.Update(protocol.LocalDeviceID, fs)
|
||||
|
||||
filenames := make([]string, len(fs))
|
||||
for i, file := range fs {
|
||||
filenames[i] = file.Name
|
||||
}
|
||||
|
||||
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
|
||||
"folder": f.ID,
|
||||
"items": len(fs),
|
||||
"filenames": filenames,
|
||||
"version": f.fset.Sequence(protocol.LocalDeviceID),
|
||||
})
|
||||
}
|
||||
|
||||
func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events.EventType) {
|
||||
for _, file := range fs {
|
||||
if file.IsInvalid() {
|
||||
continue
|
||||
}
|
||||
|
||||
objType := "file"
|
||||
action := "modified"
|
||||
|
||||
switch {
|
||||
case file.IsDeleted():
|
||||
action = "deleted"
|
||||
|
||||
// If our local vector is version 1 AND it is the only version
|
||||
// vector so far seen for this file then it is a new file. Else if
|
||||
// it is > 1 it's not new, and if it is 1 but another shortId
|
||||
// version vector exists then it is new for us but created elsewhere
|
||||
// so the file is still not new but modified by us. Only if it is
|
||||
// truly new do we change this to 'added', else we leave it as
|
||||
// 'modified'.
|
||||
case len(file.Version.Counters) == 1 && file.Version.Counters[0].Value == 1:
|
||||
action = "added"
|
||||
}
|
||||
|
||||
if file.IsSymlink() {
|
||||
objType = "symlink"
|
||||
} else if file.IsDirectory() {
|
||||
objType = "dir"
|
||||
}
|
||||
|
||||
// Two different events can be fired here based on what EventType is passed into function
|
||||
events.Default.Log(typeOfEvent, map[string]string{
|
||||
"folder": f.ID,
|
||||
"folderID": f.ID, // incorrect, deprecated, kept for historical compliance
|
||||
"label": f.Label,
|
||||
"action": action,
|
||||
"type": objType,
|
||||
"path": filepath.FromSlash(file.Name),
|
||||
"modifiedBy": file.ModifiedBy.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The exists function is expected to return true for all known paths
|
||||
// (excluding "" and ".")
|
||||
func unifySubs(dirs []string, exists func(dir string) bool) []string {
|
||||
@@ -769,3 +850,12 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string {
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
type cFiler struct {
|
||||
*db.FileSet
|
||||
}
|
||||
|
||||
// Implements scanner.CurrentFiler
|
||||
func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
|
||||
return cf.Get(protocol.LocalDeviceID, file)
|
||||
}
|
||||
|
||||
@@ -56,34 +56,29 @@ type receiveOnlyFolder struct {
|
||||
*sendReceiveFolder
|
||||
}
|
||||
|
||||
func newReceiveOnlyFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
|
||||
sr := newSendReceiveFolder(model, cfg, ver, fs).(*sendReceiveFolder)
|
||||
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
|
||||
sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs).(*sendReceiveFolder)
|
||||
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
|
||||
return &receiveOnlyFolder{sr}
|
||||
}
|
||||
|
||||
func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.FileInfo)) {
|
||||
func (f *receiveOnlyFolder) Revert() {
|
||||
f.setState(FolderScanning)
|
||||
defer f.setState(FolderIdle)
|
||||
|
||||
// XXX: This *really* should be given to us in the constructor...
|
||||
f.model.fmut.RLock()
|
||||
ignores := f.model.folderIgnores[f.folderID]
|
||||
f.model.fmut.RUnlock()
|
||||
|
||||
scanChan := make(chan string)
|
||||
go f.pullScannerRoutine(scanChan)
|
||||
defer close(scanChan)
|
||||
|
||||
delQueue := &deleteQueue{
|
||||
handler: f, // for the deleteFile and deleteDir methods
|
||||
ignores: ignores,
|
||||
handler: f, // for the deleteItemOnDisk and deleteDirOnDisk methods
|
||||
ignores: f.ignores,
|
||||
scanChan: scanChan,
|
||||
}
|
||||
|
||||
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
|
||||
batchSizeBytes := 0
|
||||
fs.WithHave(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
|
||||
f.fset.WithHave(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
|
||||
fi := intf.(protocol.FileInfo)
|
||||
if !fi.IsReceiveOnlyChanged() {
|
||||
// We're only interested in files that have changed locally in
|
||||
@@ -129,14 +124,14 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File
|
||||
batchSizeBytes += fi.ProtoSize()
|
||||
|
||||
if len(batch) >= maxBatchSizeFiles || batchSizeBytes >= maxBatchSizeBytes {
|
||||
updateFn(batch)
|
||||
f.updateLocalsFromScanning(batch)
|
||||
batch = batch[:0]
|
||||
batchSizeBytes = 0
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(batch) > 0 {
|
||||
updateFn(batch)
|
||||
f.updateLocalsFromScanning(batch)
|
||||
}
|
||||
batch = batch[:0]
|
||||
batchSizeBytes = 0
|
||||
@@ -158,7 +153,7 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File
|
||||
})
|
||||
}
|
||||
if len(batch) > 0 {
|
||||
updateFn(batch)
|
||||
f.updateLocalsFromScanning(batch)
|
||||
}
|
||||
|
||||
// We will likely have changed our local index, but that won't trigger a
|
||||
@@ -171,8 +166,8 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File
|
||||
// directories for last.
|
||||
type deleteQueue struct {
|
||||
handler interface {
|
||||
deleteFile(file protocol.FileInfo, scanChan chan<- string) (dbUpdateJob, error)
|
||||
deleteDir(dir string, ignores *ignore.Matcher, scanChan chan<- string) error
|
||||
deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) error
|
||||
deleteDirOnDisk(dir string, scanChan chan<- string) error
|
||||
}
|
||||
ignores *ignore.Matcher
|
||||
dirs []string
|
||||
@@ -193,7 +188,7 @@ func (q *deleteQueue) handle(fi protocol.FileInfo) (bool, error) {
|
||||
}
|
||||
|
||||
// Kill it.
|
||||
_, err := q.handler.deleteFile(fi, q.scanChan)
|
||||
err := q.handler.deleteItemOnDisk(fi, q.scanChan)
|
||||
return true, err
|
||||
}
|
||||
|
||||
@@ -205,7 +200,7 @@ func (q *deleteQueue) flush() ([]string, error) {
|
||||
var deleted []string
|
||||
|
||||
for _, dir := range q.dirs {
|
||||
if err := q.handler.deleteDir(dir, q.ignores, q.scanChan); err == nil {
|
||||
if err := q.handler.deleteDirOnDisk(dir, q.scanChan); err == nil {
|
||||
deleted = append(deleted, dir)
|
||||
} else if err != nil && firstError == nil {
|
||||
firstError = err
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user