Compare commits

...

75 Commits

Author SHA1 Message Date
Jakob Borg
6809d38cde lib/protocol: Revert protobuf encoder changes in v0.14.17 (fixes #3855)
The protobuf encoder now produces packed arrays for things like []int32,
which is actually correct according to the proto3 spec. However
Syncthing v0.14.16 and earlier doesn't support this. This reverts the
encoding change, but keeps the updated decoder so that we are both more
compatible with other proto3 implementations and can move to the updated
encoder in the future.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3856
2017-01-01 17:19:00 +00:00
Mark Pulford
69ae4aa024 cmd/syncthing: Avoid Keepalive/GUI refresh race
This avoids unnecessary browser request failures and retries. Eg:
- Browser reuses existing HTTP connection for GUI refresh request
- Server closes connection with request in flight
- Browser retries GET request.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3854
2017-01-01 12:38:31 +00:00
Jakob Borg
8e8b867fba authors: Add mpx 2017-01-01 13:28:33 +01:00
Jakob Borg
0a118d2979 lib/config, lib/model: Temporarily disable bad tests (ref #3834, #3843) 2017-01-01 13:27:18 +01:00
Nathan Morrison
8daaa5d0d2 gui: Populate global changes on load
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3848
2016-12-30 01:33:27 +00:00
Jakob Borg
eb14f85a57 vendor: Update github.com/syndtr/goleveldb 2016-12-28 12:19:14 +01:00
Jakob Borg
c69c3c7c36 lib/sha256: Smoke test the implementation on startup (hello OpenSUSE!) 2016-12-28 12:15:51 +01:00
Jakob Borg
54911d44c5 gui: s/foldersendonly.html/foldertypes.html 2016-12-27 11:29:12 +01:00
Jakob Borg
c765f7be8d gui, man: Update docs and translations 2016-12-26 14:23:55 +01:00
Jakob Borg
44bdaf3ac2 cmd/syncthing: Add -reset-deltas option to reset delta index IDs
Also rename and clarify the description of -reset-database (formerly
-reset).
2016-12-26 13:49:51 +01:00
Jakob Borg
fc16e49cb0 Merge branch 'v0.14.16-hotfix'
* v0.14.16-hotfix:
  gui, man: Update docs & translations
  lib/model: Allow empty subdirs in scan request (fixes #3829)
  lib/model: Don't send symlinks to old devices that can't handle them (fixes #3802)
  lib/model: Accept scan requests of paths ending in slash (fixes #3804)
  gui: Avoid pause between event polls (ref #3527)
2016-12-24 20:12:53 +01:00
Jakob Borg
f5a310ad64 Revert "lib/model: Handle filename conflicts on Windows."
This reverts commit 01e50eb3fa.
2016-12-23 11:10:58 +01:00
Unrud
01e50eb3fa lib/model: Handle filename conflicts on Windows.
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3810
LGTM: calmh
2016-12-22 23:04:53 +00:00
Jakob Borg
722b81c6f0 gui, man: Update docs & translations 2016-12-21 19:46:28 +01:00
Jakob Borg
f0efa2b974 lib/model: Allow empty subdirs in scan request (fixes #3829)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3833
2016-12-21 19:45:38 +01:00
Audrius Butkevicius
bab7c8ebbf all: Add folder pause, make pauses permanent (fixes #3407, fixes #215, fixes #3001)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3520
2016-12-21 18:41:25 +00:00
Nathan Morrison
0725e3af38 all: Add a global change list
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3694
2016-12-21 16:35:20 +00:00
Jakob Borg
dd7bb6c4b8 lib/model: Fix tests, clean up pool usage in protocol 2016-12-21 14:53:45 +01:00
Jakob Borg
d41c131364 build: Enable gometalinter "gosimple" check, improve build.go 2016-12-21 14:53:45 +01:00
Jakob Borg
47f22ff3e5 build: Enable gometalinter "unconvert" check 2016-12-21 14:53:45 +01:00
Jakob Borg
744c2e82b5 build: Enable gometalinter "staticcheck" check 2016-12-21 14:53:45 +01:00
Jakob Borg
ead7281c20 build: Enable gometalinter "unused" check 2016-12-21 14:53:45 +01:00
AudriusButkevicius
aa3ef49dd7 lib/model: Fix lock order
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3823
2016-12-21 12:22:18 +00:00
Jakob Borg
5c067661f4 lib/model: Consistently show folder description in startup messages
Since we anyway need the folderConfig for this I'm skipping the copying
of all it's attributes that rwfolder did and just keeping the original
around instead.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3825
2016-12-21 11:23:20 +00:00
Jakob Borg
226da976dc lib/model: Allow empty subdirs in scan request (fixes #3829)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3833
2016-12-21 10:33:07 +00:00
AudriusButkevicius
ba17cc0a11 gui: Show introducedBy (fixes #3809) 2016-12-21 11:01:15 +01:00
AudriusButkevicius
9e0afb7d8a lib/connections: Support setting traffic class (fixes #3790) 2016-12-21 11:01:15 +01:00
AudriusButkevicius
9e7d50bc76 cmd/syncthing: Explain corruption panics (fixes #3689) 2016-12-21 11:01:15 +01:00
Jakob Borg
d7d5687faa lib/protocol: Warnln should have been Warnf 2016-12-21 10:43:12 +01:00
Jakob Borg
21eb098dd2 vendor: Update github.com/minio/sha256-simd, CPU detection (Linux) 2016-12-20 09:20:28 +01:00
Jakob Borg
2f770f8bfb lib/config, lib/protocol: Improve folder description with empty label 2016-12-19 10:12:06 +01:00
Jakob Borg
f09698d845 gui: Add missing strings to lang-en.json 2016-12-18 22:29:43 +01:00
Jakob Borg
3fbcb024a7 gui: Update link to send only documentation page 2016-12-18 22:29:43 +01:00
Jakob Borg
b8c1c0e048 cmd/syncthing: Enable better crypto, print negotiated cipher suite
This adds support for AES_256_GCM_SHA384 (in there since Go 1.5, a bit
of a shame we missed it) and ChaCha20-Poly1305 (if built with Go 1.8;
ignored on older Gos).

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3822
2016-12-18 21:07:44 +00:00
Jakob Borg
2d47242d54 jenkins: Don't clean out when building with old Go (nukes coverage data) 2016-12-18 19:58:46 +01:00
Jakob Borg
66a7829eee jenkins: Also try build with old Go version, if available 2016-12-18 19:25:27 +01:00
Jakob Borg
9c67bd2550 lib/connections: Fix port fixup in Go 1.8 (fixes #3817)
The test for the error string is fragile, and the error string changed
in Go 1.8 so the relevant part is no longer a prefix. This covers it
with a test though, so it should be fine in the future as well.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3818
2016-12-18 11:28:18 +00:00
Jakob Borg
f67c5a2fd6 lib/model: Don't send symlinks to old devices that can't handle them (fixes #3802)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3811
2016-12-17 19:48:33 +00:00
Jakob Borg
0437f6dd66 lib/model: Don't send symlinks to old devices that can't handle them (fixes #3802) 2016-12-17 12:39:22 +01:00
Jakob Borg
11b35d650d lib/model: Accept scan requests of paths ending in slash (fixes #3804)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3805
2016-12-17 12:39:22 +01:00
Jakob Borg
b279e261a1 gui: Avoid pause between event polls (ref #3527)
We lose important events due to the frequency of ItemStarted /
ItemFinished events.
2016-12-17 12:39:22 +01:00
Jakob Borg
263402f80a cmd/stcli: Add copyright headers to satisfy check 2016-12-17 12:28:59 +01:00
Jakob Borg
920a83ec7a cmd/stcli: Fix metalint ineffasign complaint 2016-12-17 10:51:48 +01:00
Jakob Borg
3c2ac3522c cmd/stcli: Update for new folder type 2016-12-17 01:44:18 +01:00
Jakob Borg
9fdaa637a8 vendor: Add github.com/AudriusButkevicius/cli 2016-12-17 01:33:17 +01:00
Jakob Borg
81a9d7f2b9 cmd/stcli: Make it build again 2016-12-17 01:33:17 +01:00
Audrius Butkevičius
d8d3f05164 cmd/stcli: Import from syncthing-cli repository 2016-12-17 01:33:17 +01:00
Jakob Borg
653be136ee gui: Update lang-en.json 2016-12-17 00:59:25 +01:00
Heiko Zuerker
398c356f22 lib/model: Clarify master terminology (fixes #2679)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3793
2016-12-16 22:23:35 +00:00
Audrius Butkevicius
542b76f687 lib/model: Moar sleep
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3807
2016-12-16 12:05:27 +00:00
Jakob Borg
abb8a1914a lib/model: Accept scan requests of paths ending in slash (fixes #3804)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3805
2016-12-16 11:21:22 +00:00
Jakob Borg
163d335078 gui: Update lang-en.json 2016-12-16 09:42:13 +01:00
Audrius Butkevicius
0582836820 lib/model, lib/scanner: Efficient inserts/deletes in the middle of the file
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3527
2016-12-14 23:30:29 +00:00
Jakob Borg
bb15776ae6 gui: Avoid pause between event polls (ref #3527)
We lose important events due to the frequency of ItemStarted /
ItemFinished events.
2016-12-14 10:31:16 +01:00
Jakob Borg
dde9d4c9eb lib/rc: Remove pause to aggregate events (ref #3527) 2016-12-14 10:24:36 +01:00
Jakob Borg
1ef75be1c6 gui, man: Update docs & translations 2016-12-13 11:29:40 +01:00
Jakob Borg
3582783972 lib/model, lib/osutil: Verify target directory before pulling / requesting
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3798
2016-12-13 10:24:10 +00:00
Jakob Borg
5070d52f2f lib/model: Add benchmark for model.Request() 2016-12-09 23:14:56 +01:00
Jakob Borg
7b07ed6580 lib/model, lib/protocol, lib/scanner: Include symlink target in index, pull symlinks synchronously
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3792
2016-12-09 18:02:18 +00:00
Han Boetes
f6a2b6252a gui: Tweak wording (fixes #3769)
Skip-check: authors

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3796
2016-12-09 17:16:29 +00:00
Jakob Borg
a9b03de99a gui, lib/db: Correct space accounting of symlinks, for "out of sync" status 2016-12-09 10:38:36 +01:00
Jakob Borg
a7f7058636 vendor: Update github.com/gobwas/glob (bugfix) 2016-12-07 09:25:58 +01:00
Lars K.W. Gohlke
8ce9b026e9 lib/model: Minor cleanups
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3765
2016-12-06 08:54:04 +00:00
Audrius Butkevicius
0dcf2f1bc8 cmd/strelaysrv: Use legacy dial (fixes #3753)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3784
2016-12-02 22:45:08 +00:00
Audrius Butkevicius
99922feb3b gui: Disable device removal when we know it will be reintroduced
Skip-check: pr-build-windows

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3762
2016-12-02 21:07:02 +00:00
Jakob Borg
2fd1dca905 lib/connections: Fix odd logging, forgot to call function 2016-12-02 12:56:14 +01:00
Jakob Borg
e3cf718998 lib/ignore: Add central check for internal files, used in scanning, pulling and requests
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3779
2016-12-01 14:00:11 +00:00
Stefan Kuntz
7157917a16 etc: Updated ufw firewall application preset with default GUI port
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3774
2016-12-01 12:36:15 +00:00
Jakob Borg
3266aae1c3 lib/protocol: Apply input filtering on file names
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3775
2016-12-01 12:35:32 +00:00
Jakob Borg
63194a37f6 lib/model: Double check results in filepath.Join where needed
Wherever we have untrusted relative paths, make sure they are not
escaping their folder root.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3776
2016-12-01 12:35:11 +00:00
Unrud
cabe94552a lib/model: Prevent collisions in the progressemitter registry
Using filepath.Join can cause collisions. The folder ID could be something
like ".." or "../..".

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3778
2016-12-01 12:34:20 +00:00
Jakob Borg
48a229a0cd lib/model: Temp names from all platforms should be recognized as such
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3777
LGTM: AudriusButkevicius
2016-11-30 21:23:24 +00:00
Jakob Borg
e4db86836b lib/model: Locking in the request test 2016-11-30 13:11:06 +01:00
Jakob Borg
913a85c571 lib/model: Add simple file syncing test
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3772
2016-11-30 09:32:28 +00:00
Jakob Borg
ed4f6fc4b3 lib/connections, lib/model: Connection service should expose a single interface
Makes testing easier, which we'll need

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3771
2016-11-30 07:54:20 +00:00
300 changed files with 15054 additions and 2244 deletions

1
.gitattributes vendored
View File

@@ -6,4 +6,3 @@ vendor/** -text=auto
# Diffs on these files are meaningless
*.svg -diff
*.pb.go -diff

View File

@@ -48,6 +48,7 @@ Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
Francois-Xavier Gsell (zukoo) <fxgsell@gmail.com>
Frank Isemann (fti7) <frank@isemann.name>
Gilli Sigurdsson (gillisig) <gilli@vx.is>
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
Jake Peterson (acogdev) <jake@acogdev.com>
@@ -71,6 +72,7 @@ Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
Marc Pujol (kilburn) <kilburn@la3.org>
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
Mark Pulford (mpx) <mark@kyne.com.au>
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>

2
NICKS
View File

@@ -76,6 +76,7 @@ mateon1 <matin1111@wp.pl>
mogwa1 <devriesb@gmail.com>
moshen <moshen.colin@gmail.com>
Moter8 <moter8@gmail.com>
mpx <mark@kyne.com.au>
mvdan <mvdan@mvdan.cc>
norgeous <daniel@harte.me>
norgeous <daniel@danielharte.co.uk>
@@ -101,6 +102,7 @@ sciurius <jvromans@squirrel.nl>
seehuhn <voss@seehuhn.de>
simplypeachy <aD@simplypeachy.co.uk>
simplypeachy <simplypeachy@users.noreply.github.com>
Smiley73 <heiko@zuerker.org>
snnd <dw@risu.io>
Stefan-Code <stefan.github@gmail.com>
Stefan-Code <Stefan.github@gmail.com>

135
build.go
View File

@@ -27,7 +27,6 @@ import (
"runtime"
"strconv"
"strings"
"syscall"
"text/template"
"time"
)
@@ -154,6 +153,40 @@ var targets = map[string]target{
},
}
var (
// fast linters complete in a fraction of a second and might as well be
// run always as part of the build
fastLinters = []string{
"deadcode",
"golint",
"ineffassign",
"vet",
}
// slow linters take several seconds and are run only as part of the
// "metalint" command.
slowLinters = []string{
"gosimple",
"staticcheck",
"structcheck",
"unused",
"varcheck",
}
// Which parts of the tree to lint
lintDirs = []string{".", "./lib/...", "./cmd/..."}
// Messages to ignore
lintExcludes = []string{
".pb.go",
"should have comment",
"protocol.Vector composite literal uses unkeyed fields",
"cli.Requires composite literal uses unkeyed fields",
"Use DialContext instead", // Go 1.7
"os.SEEK_SET is deprecated", // Go 1.7
}
)
func init() {
// The "syncthing" target includes a few more files found in the "etc"
// and "extra" dirs.
@@ -203,8 +236,6 @@ func main() {
// which is what you want for maximum error checking during development.
if flag.NArg() == 0 {
runCommand("install", targets["all"])
runCommand("vet", target{})
runCommand("lint", target{})
} else {
// with any command given but not a target, the target is
// "syncthing". So "go run build.go install" is "go run build.go install
@@ -242,6 +273,7 @@ func runCommand(cmd string, target target) {
tags = []string{"noupgrade"}
}
install(target, tags)
metalint(fastLinters, lintDirs)
case "build":
var tags []string
@@ -249,6 +281,7 @@ func runCommand(cmd string, target target) {
tags = []string{"noupgrade"}
}
build(target, tags)
metalint(fastLinters, lintDirs)
case "test":
test("./lib/...", "./cmd/...")
@@ -284,25 +317,14 @@ func runCommand(cmd string, target target) {
clean()
case "vet":
vet("build.go")
vet("cmd", "lib")
metalint(fastLinters, lintDirs)
case "lint":
lint(".")
lint("./cmd/...")
lint("./lib/...")
metalint(fastLinters, lintDirs)
case "metalint":
if isGometalinterInstalled() {
dirs := []string{".", "./cmd/...", "./lib/..."}
ok := gometalinter("deadcode", dirs, "test/util.go")
ok = gometalinter("structcheck", dirs) && ok
ok = gometalinter("varcheck", dirs) && ok
ok = gometalinter("ineffassign", dirs) && ok
if !ok {
os.Exit(1)
}
}
metalint(fastLinters, lintDirs)
metalint(slowLinters, lintDirs)
case "version":
fmt.Println(getVersion())
@@ -364,11 +386,15 @@ func setup() {
"github.com/FiloSottile/gvt",
"github.com/golang/lint/golint",
"github.com/gordonklaus/ineffassign",
"github.com/mdempsky/unconvert",
"github.com/mitchellh/go-wordwrap",
"github.com/opennota/check/cmd/...",
"github.com/tsenart/deadcode",
"golang.org/x/net/html",
"golang.org/x/tools/cmd/cover",
"honnef.co/go/simple/cmd/gosimple",
"honnef.co/go/staticcheck/cmd/staticcheck",
"honnef.co/go/unused/cmd/unused",
}
for _, pkg := range packages {
fmt.Println(pkg)
@@ -1003,48 +1029,6 @@ func zipFile(out string, files []archiveFile) {
}
}
func vet(dirs ...string) {
params := []string{"tool", "vet", "-all"}
params = append(params, dirs...)
bs, err := runError("go", params...)
if len(bs) > 0 {
log.Printf("%s", bs)
}
if err != nil {
if exitStatus(err) == 3 {
// Exit code 3, the "vet" tool is not installed
return
}
// A genuine error exit from the vet tool.
log.Fatal(err)
}
}
func lint(pkg string) {
bs, err := runError("golint", pkg)
if err != nil {
log.Println(`- No golint, not linting. Try "go get -u github.com/golang/lint/golint".`)
return
}
analCommentPolicy := regexp.MustCompile(`exported (function|method|const|type|var) [^\s]+ should have comment`)
for _, line := range strings.Split(string(bs), "\n") {
if line == "" {
continue
}
if analCommentPolicy.MatchString(line) {
continue
}
if strings.Contains(line, ".pb.go:") {
continue
}
log.Println(line)
}
}
func macosCodesign(file string) {
if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
bs, err := runError("security", "unlock-keychain", "-p", pass)
@@ -1064,14 +1048,16 @@ func macosCodesign(file string) {
}
}
func exitStatus(err error) int {
if err, ok := err.(*exec.ExitError); ok {
if ws, ok := err.ProcessState.Sys().(syscall.WaitStatus); ok {
return ws.ExitStatus()
func metalint(linters []string, dirs []string) {
ok := true
if isGometalinterInstalled() {
if !gometalinter(linters, dirs, lintExcludes...) {
ok = false
}
}
return -1
if !ok {
log.Fatal("Build succeeded, but there were lint warnings")
}
}
func isGometalinterInstalled() bool {
@@ -1082,10 +1068,12 @@ func isGometalinterInstalled() bool {
return true
}
func gometalinter(linter string, dirs []string, excludes ...string) bool {
params := []string{"--disable-all"}
params = append(params, fmt.Sprintf("--deadline=%ds", 60))
params = append(params, "--enable="+linter)
func gometalinter(linters []string, dirs []string, excludes ...string) bool {
params := []string{"--disable-all", "--concurrency=2", "--deadline=300s"}
for _, linter := range linters {
params = append(params, "--enable="+linter)
}
for _, exclude := range excludes {
params = append(params, "--exclude="+exclude)
@@ -1098,14 +1086,19 @@ func gometalinter(linter string, dirs []string, excludes ...string) bool {
bs, _ := runError("gometalinter", params...)
nerr := 0
lines := make(map[string]struct{})
for _, line := range strings.Split(string(bs), "\n") {
if line == "" {
continue
}
if strings.Contains(line, ".pb.go:") {
if _, ok := lines[line]; ok {
continue
}
log.Println(line)
if strings.Contains(line, "executable file not found") {
log.Println(` - Try "go run build.go setup" to install missing tools`)
}
lines[line] = struct{}{}
nerr++
}

19
cmd/stcli/LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright (C) 2014 Audrius Butkevičius
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
cmd/stcli/README.md Normal file
View File

@@ -0,0 +1,10 @@
syncthing-cli
=============
[![Latest Build](http://img.shields.io/jenkins/s/http/build.syncthing.net/syncthing-cli.svg?style=flat-square)](http://build.syncthing.net/job/syncthing-cli/lastBuild/)
A CLI that talks to the Syncthing REST interface.
`go get github.com/syncthing/syncthing-cli`
Or download the [latest build](http://build.syncthing.net/job/syncthing-cli/lastSuccessfulBuild/artifact/).

115
cmd/stcli/client.go Normal file
View File

@@ -0,0 +1,115 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"bytes"
"crypto/tls"
"net/http"
"strings"
"github.com/AudriusButkevicius/cli"
)
type APIClient struct {
httpClient http.Client
endpoint string
apikey string
username string
password string
id string
csrf string
}
var instance *APIClient
func getClient(c *cli.Context) *APIClient {
if instance != nil {
return instance
}
endpoint := c.GlobalString("endpoint")
if !strings.HasPrefix(endpoint, "http") {
endpoint = "http://" + endpoint
}
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: c.GlobalBool("insecure"),
},
},
}
client := APIClient{
httpClient: httpClient,
endpoint: endpoint,
apikey: c.GlobalString("apikey"),
username: c.GlobalString("username"),
password: c.GlobalString("password"),
}
if client.apikey == "" {
request, err := http.NewRequest("GET", client.endpoint, nil)
die(err)
response := client.handleRequest(request)
client.id = response.Header.Get("X-Syncthing-ID")
if client.id == "" {
die("Failed to get device ID")
}
for _, item := range response.Cookies() {
if item.Name == "CSRF-Token-"+client.id[:5] {
client.csrf = item.Value
goto csrffound
}
}
die("Failed to get CSRF token")
csrffound:
}
instance = &client
return &client
}
func (client *APIClient) handleRequest(request *http.Request) *http.Response {
if client.apikey != "" {
request.Header.Set("X-API-Key", client.apikey)
}
if client.username != "" || client.password != "" {
request.SetBasicAuth(client.username, client.password)
}
if client.csrf != "" {
request.Header.Set("X-CSRF-Token-"+client.id[:5], client.csrf)
}
response, err := client.httpClient.Do(request)
die(err)
if response.StatusCode == 404 {
die("Invalid endpoint or API call")
} else if response.StatusCode == 401 {
die("Invalid username or password")
} else if response.StatusCode == 403 {
if client.apikey == "" {
die("Invalid CSRF token")
}
die("Invalid API key")
} else if response.StatusCode != 200 {
body := strings.TrimSpace(string(responseToBArray(response)))
if body != "" {
die(body)
}
die("Unknown HTTP status returned: " + response.Status)
}
return response
}
func httpGet(c *cli.Context, url string) *http.Response {
client := getClient(c)
request, err := http.NewRequest("GET", client.endpoint+"/rest/"+url, nil)
die(err)
return client.handleRequest(request)
}
func httpPost(c *cli.Context, url string, body string) *http.Response {
client := getClient(c)
request, err := http.NewRequest("POST", client.endpoint+"/rest/"+url, bytes.NewBufferString(body))
die(err)
return client.handleRequest(request)
}

188
cmd/stcli/cmd_devices.go Normal file
View File

@@ -0,0 +1,188 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"fmt"
"strings"
"github.com/AudriusButkevicius/cli"
"github.com/syncthing/syncthing/lib/config"
)
func init() {
cliCommands = append(cliCommands, cli.Command{
Name: "devices",
HideHelp: true,
Usage: "Device command group",
Subcommands: []cli.Command{
{
Name: "list",
Usage: "List registered devices",
Requires: &cli.Requires{},
Action: devicesList,
},
{
Name: "add",
Usage: "Add a new device",
Requires: &cli.Requires{"device id", "device name?"},
Action: devicesAdd,
},
{
Name: "remove",
Usage: "Remove an existing device",
Requires: &cli.Requires{"device id"},
Action: devicesRemove,
},
{
Name: "get",
Usage: "Get a property of a device",
Requires: &cli.Requires{"device id", "property"},
Action: devicesGet,
},
{
Name: "set",
Usage: "Set a property of a device",
Requires: &cli.Requires{"device id", "property", "value..."},
Action: devicesSet,
},
},
})
}
func devicesList(c *cli.Context) {
cfg := getConfig(c)
first := true
writer := newTableWriter()
for _, device := range cfg.Devices {
if !first {
fmt.Fprintln(writer)
}
fmt.Fprintln(writer, "ID:\t", device.DeviceID, "\t")
fmt.Fprintln(writer, "Name:\t", device.Name, "\t(name)")
fmt.Fprintln(writer, "Address:\t", strings.Join(device.Addresses, " "), "\t(address)")
fmt.Fprintln(writer, "Compression:\t", device.Compression, "\t(compression)")
fmt.Fprintln(writer, "Certificate name:\t", device.CertName, "\t(certname)")
fmt.Fprintln(writer, "Introducer:\t", device.Introducer, "\t(introducer)")
first = false
}
writer.Flush()
}
func devicesAdd(c *cli.Context) {
nid := c.Args()[0]
id := parseDeviceID(nid)
newDevice := config.DeviceConfiguration{
DeviceID: id,
Name: nid,
Addresses: []string{"dynamic"},
}
if len(c.Args()) > 1 {
newDevice.Name = c.Args()[1]
}
if len(c.Args()) > 2 {
addresses := c.Args()[2:]
for _, item := range addresses {
if item == "dynamic" {
continue
}
validAddress(item)
}
newDevice.Addresses = addresses
}
cfg := getConfig(c)
for _, device := range cfg.Devices {
if device.DeviceID == id {
die("Device " + nid + " already exists")
}
}
cfg.Devices = append(cfg.Devices, newDevice)
setConfig(c, cfg)
}
func devicesRemove(c *cli.Context) {
nid := c.Args()[0]
id := parseDeviceID(nid)
if nid == getMyID(c) {
die("Cannot remove yourself")
}
cfg := getConfig(c)
for i, device := range cfg.Devices {
if device.DeviceID == id {
last := len(cfg.Devices) - 1
cfg.Devices[i] = cfg.Devices[last]
cfg.Devices = cfg.Devices[:last]
setConfig(c, cfg)
return
}
}
die("Device " + nid + " not found")
}
func devicesGet(c *cli.Context) {
nid := c.Args()[0]
id := parseDeviceID(nid)
arg := c.Args()[1]
cfg := getConfig(c)
for _, device := range cfg.Devices {
if device.DeviceID != id {
continue
}
switch strings.ToLower(arg) {
case "name":
fmt.Println(device.Name)
case "address":
fmt.Println(strings.Join(device.Addresses, "\n"))
case "compression":
fmt.Println(device.Compression.String())
case "certname":
fmt.Println(device.CertName)
case "introducer":
fmt.Println(device.Introducer)
default:
die("Invalid property: " + arg + "\nAvailable properties: name, address, compression, certname, introducer")
}
return
}
die("Device " + nid + " not found")
}
func devicesSet(c *cli.Context) {
nid := c.Args()[0]
id := parseDeviceID(nid)
arg := c.Args()[1]
config := getConfig(c)
for i, device := range config.Devices {
if device.DeviceID != id {
continue
}
switch strings.ToLower(arg) {
case "name":
config.Devices[i].Name = strings.Join(c.Args()[2:], " ")
case "address":
for _, item := range c.Args()[2:] {
if item == "dynamic" {
continue
}
validAddress(item)
}
config.Devices[i].Addresses = c.Args()[2:]
case "compression":
err := config.Devices[i].Compression.UnmarshalText([]byte(c.Args()[2]))
die(err)
case "certname":
config.Devices[i].CertName = strings.Join(c.Args()[2:], " ")
case "introducer":
config.Devices[i].Introducer = parseBool(c.Args()[2])
default:
die("Invalid property: " + arg + "\nAvailable properties: name, address, compression, certname, introducer")
}
setConfig(c, config)
return
}
die("Device " + nid + " not found")
}

67
cmd/stcli/cmd_errors.go Normal file
View File

@@ -0,0 +1,67 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/AudriusButkevicius/cli"
)
func init() {
cliCommands = append(cliCommands, cli.Command{
Name: "errors",
HideHelp: true,
Usage: "Error command group",
Subcommands: []cli.Command{
{
Name: "show",
Usage: "Show pending errors",
Requires: &cli.Requires{},
Action: errorsShow,
},
{
Name: "push",
Usage: "Push an error to active clients",
Requires: &cli.Requires{"error message..."},
Action: errorsPush,
},
{
Name: "clear",
Usage: "Clear pending errors",
Requires: &cli.Requires{},
Action: wrappedHTTPPost("system/error/clear"),
},
},
})
}
func errorsShow(c *cli.Context) {
response := httpGet(c, "system/error")
var data map[string][]map[string]interface{}
json.Unmarshal(responseToBArray(response), &data)
writer := newTableWriter()
for _, item := range data["errors"] {
time := item["time"].(string)[:19]
time = strings.Replace(time, "T", " ", 1)
err := item["error"].(string)
err = strings.TrimSpace(err)
fmt.Fprintln(writer, time+":\t"+err)
}
writer.Flush()
}
func errorsPush(c *cli.Context) {
err := strings.Join(c.Args(), " ")
response := httpPost(c, "system/error", strings.TrimSpace(err))
if response.StatusCode != 200 {
err = fmt.Sprint("Failed to push error\nStatus code: ", response.StatusCode)
body := string(responseToBArray(response))
if body != "" {
err += "\nBody: " + body
}
die(err)
}
}

351
cmd/stcli/cmd_folders.go Normal file
View File

@@ -0,0 +1,351 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"fmt"
"path/filepath"
"strings"
"github.com/AudriusButkevicius/cli"
"github.com/syncthing/syncthing/lib/config"
)
func init() {
cliCommands = append(cliCommands, cli.Command{
Name: "folders",
HideHelp: true,
Usage: "Folder command group",
Subcommands: []cli.Command{
{
Name: "list",
Usage: "List available folders",
Requires: &cli.Requires{},
Action: foldersList,
},
{
Name: "add",
Usage: "Add a new folder",
Requires: &cli.Requires{"folder id", "directory"},
Action: foldersAdd,
},
{
Name: "remove",
Usage: "Remove an existing folder",
Requires: &cli.Requires{"folder id"},
Action: foldersRemove,
},
{
Name: "override",
Usage: "Override changes from other nodes for a master folder",
Requires: &cli.Requires{"folder id"},
Action: foldersOverride,
},
{
Name: "get",
Usage: "Get a property of a folder",
Requires: &cli.Requires{"folder id", "property"},
Action: foldersGet,
},
{
Name: "set",
Usage: "Set a property of a folder",
Requires: &cli.Requires{"folder id", "property", "value..."},
Action: foldersSet,
},
{
Name: "unset",
Usage: "Unset a property of a folder",
Requires: &cli.Requires{"folder id", "property"},
Action: foldersUnset,
},
{
Name: "devices",
Usage: "Folder devices command group",
HideHelp: true,
Subcommands: []cli.Command{
{
Name: "list",
Usage: "List of devices which the folder is shared with",
Requires: &cli.Requires{"folder id"},
Action: foldersDevicesList,
},
{
Name: "add",
Usage: "Share a folder with a device",
Requires: &cli.Requires{"folder id", "device id"},
Action: foldersDevicesAdd,
},
{
Name: "remove",
Usage: "Unshare a folder with a device",
Requires: &cli.Requires{"folder id", "device id"},
Action: foldersDevicesRemove,
},
{
Name: "clear",
Usage: "Unshare a folder with all devices",
Requires: &cli.Requires{"folder id"},
Action: foldersDevicesClear,
},
},
},
},
})
}
func foldersList(c *cli.Context) {
cfg := getConfig(c)
first := true
writer := newTableWriter()
for _, folder := range cfg.Folders {
if !first {
fmt.Fprintln(writer)
}
fmt.Fprintln(writer, "ID:\t", folder.ID, "\t")
fmt.Fprintln(writer, "Path:\t", folder.RawPath, "\t(directory)")
fmt.Fprintln(writer, "Folder type:\t", folder.Type, "\t(type)")
fmt.Fprintln(writer, "Ignore permissions:\t", folder.IgnorePerms, "\t(permissions)")
fmt.Fprintln(writer, "Rescan interval in seconds:\t", folder.RescanIntervalS, "\t(rescan)")
if folder.Versioning.Type != "" {
fmt.Fprintln(writer, "Versioning:\t", folder.Versioning.Type, "\t(versioning)")
for key, value := range folder.Versioning.Params {
fmt.Fprintf(writer, "Versioning %s:\t %s \t(versioning-%s)\n", key, value, key)
}
}
first = false
}
writer.Flush()
}
func foldersAdd(c *cli.Context) {
cfg := getConfig(c)
abs, err := filepath.Abs(c.Args()[1])
die(err)
folder := config.FolderConfiguration{
ID: c.Args()[0],
RawPath: filepath.Clean(abs),
}
cfg.Folders = append(cfg.Folders, folder)
setConfig(c, cfg)
}
func foldersRemove(c *cli.Context) {
cfg := getConfig(c)
rid := c.Args()[0]
for i, folder := range cfg.Folders {
if folder.ID == rid {
last := len(cfg.Folders) - 1
cfg.Folders[i] = cfg.Folders[last]
cfg.Folders = cfg.Folders[:last]
setConfig(c, cfg)
return
}
}
die("Folder " + rid + " not found")
}
func foldersOverride(c *cli.Context) {
cfg := getConfig(c)
rid := c.Args()[0]
for _, folder := range cfg.Folders {
if folder.ID == rid && folder.Type == config.FolderTypeSendOnly {
response := httpPost(c, "db/override", "")
if response.StatusCode != 200 {
err := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode)
body := string(responseToBArray(response))
if body != "" {
err += "\nBody: " + body
}
die(err)
}
return
}
}
die("Folder " + rid + " not found or folder not master")
}
func foldersGet(c *cli.Context) {
cfg := getConfig(c)
rid := c.Args()[0]
arg := strings.ToLower(c.Args()[1])
for _, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
if strings.HasPrefix(arg, "versioning-") {
arg = arg[11:]
value, ok := folder.Versioning.Params[arg]
if ok {
fmt.Println(value)
return
}
die("Versioning property " + c.Args()[1][11:] + " not found")
}
switch arg {
case "directory":
fmt.Println(folder.RawPath)
case "type":
fmt.Println(folder.Type)
case "permissions":
fmt.Println(folder.IgnorePerms)
case "rescan":
fmt.Println(folder.RescanIntervalS)
case "versioning":
if folder.Versioning.Type != "" {
fmt.Println(folder.Versioning.Type)
}
default:
die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, type, permissions, versioning, versioning-<key>")
}
return
}
die("Folder " + rid + " not found")
}
func foldersSet(c *cli.Context) {
rid := c.Args()[0]
arg := strings.ToLower(c.Args()[1])
val := strings.Join(c.Args()[2:], " ")
cfg := getConfig(c)
for i, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
if strings.HasPrefix(arg, "versioning-") {
cfg.Folders[i].Versioning.Params[arg[11:]] = val
setConfig(c, cfg)
return
}
switch arg {
case "directory":
cfg.Folders[i].RawPath = val
case "type":
var t config.FolderType
if err := t.UnmarshalText([]byte(val)); err != nil {
die("Invalid folder type: " + err.Error())
}
cfg.Folders[i].Type = t
case "permissions":
cfg.Folders[i].IgnorePerms = parseBool(val)
case "rescan":
cfg.Folders[i].RescanIntervalS = parseInt(val)
case "versioning":
cfg.Folders[i].Versioning.Type = val
default:
die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, master, permissions, versioning, versioning-<key>")
}
setConfig(c, cfg)
return
}
die("Folder " + rid + " not found")
}
func foldersUnset(c *cli.Context) {
rid := c.Args()[0]
arg := strings.ToLower(c.Args()[1])
cfg := getConfig(c)
for i, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
if strings.HasPrefix(arg, "versioning-") {
arg = arg[11:]
if _, ok := folder.Versioning.Params[arg]; ok {
delete(cfg.Folders[i].Versioning.Params, arg)
setConfig(c, cfg)
return
}
die("Versioning property " + c.Args()[1][11:] + " not found")
}
switch arg {
case "versioning":
cfg.Folders[i].Versioning.Type = ""
cfg.Folders[i].Versioning.Params = make(map[string]string)
default:
die("Invalid property: " + c.Args()[1] + "\nAvailable properties: versioning, versioning-<key>")
}
setConfig(c, cfg)
return
}
die("Folder " + rid + " not found")
}
func foldersDevicesList(c *cli.Context) {
rid := c.Args()[0]
cfg := getConfig(c)
for _, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
for _, device := range folder.Devices {
fmt.Println(device.DeviceID)
}
return
}
die("Folder " + rid + " not found")
}
func foldersDevicesAdd(c *cli.Context) {
rid := c.Args()[0]
nid := parseDeviceID(c.Args()[1])
cfg := getConfig(c)
for i, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
for _, device := range folder.Devices {
if device.DeviceID == nid {
die("Device " + c.Args()[1] + " is already part of this folder")
}
}
for _, device := range cfg.Devices {
if device.DeviceID == nid {
cfg.Folders[i].Devices = append(folder.Devices, config.FolderDeviceConfiguration{
DeviceID: device.DeviceID,
})
setConfig(c, cfg)
return
}
}
die("Device " + c.Args()[1] + " not found in device list")
}
die("Folder " + rid + " not found")
}
func foldersDevicesRemove(c *cli.Context) {
rid := c.Args()[0]
nid := parseDeviceID(c.Args()[1])
cfg := getConfig(c)
for ri, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
for ni, device := range folder.Devices {
if device.DeviceID == nid {
last := len(folder.Devices) - 1
cfg.Folders[ri].Devices[ni] = folder.Devices[last]
cfg.Folders[ri].Devices = cfg.Folders[ri].Devices[:last]
setConfig(c, cfg)
return
}
}
die("Device " + c.Args()[1] + " not found")
}
die("Folder " + rid + " not found")
}
func foldersDevicesClear(c *cli.Context) {
rid := c.Args()[0]
cfg := getConfig(c)
for i, folder := range cfg.Folders {
if folder.ID != rid {
continue
}
cfg.Folders[i].Devices = []config.FolderDeviceConfiguration{}
setConfig(c, cfg)
return
}
die("Folder " + rid + " not found")
}

78
cmd/stcli/cmd_general.go Normal file
View File

@@ -0,0 +1,78 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"encoding/json"
"fmt"
"github.com/AudriusButkevicius/cli"
)
func init() {
cliCommands = append(cliCommands, []cli.Command{
{
Name: "id",
Usage: "Get ID of the Syncthing client",
Requires: &cli.Requires{},
Action: generalID,
},
{
Name: "status",
Usage: "Configuration status, whether or not a restart is required for changes to take effect",
Requires: &cli.Requires{},
Action: generalStatus,
},
{
Name: "restart",
Usage: "Restart syncthing",
Requires: &cli.Requires{},
Action: wrappedHTTPPost("system/restart"),
},
{
Name: "shutdown",
Usage: "Shutdown syncthing",
Requires: &cli.Requires{},
Action: wrappedHTTPPost("system/shutdown"),
},
{
Name: "reset",
Usage: "Reset syncthing deleting all folders and devices",
Requires: &cli.Requires{},
Action: wrappedHTTPPost("system/reset"),
},
{
Name: "upgrade",
Usage: "Upgrade syncthing (if a newer version is available)",
Requires: &cli.Requires{},
Action: wrappedHTTPPost("system/upgrade"),
},
{
Name: "version",
Usage: "Syncthing client version",
Requires: &cli.Requires{},
Action: generalVersion,
},
}...)
}
func generalID(c *cli.Context) {
fmt.Println(getMyID(c))
}
func generalStatus(c *cli.Context) {
response := httpGet(c, "system/config/insync")
var status struct{ ConfigInSync bool }
json.Unmarshal(responseToBArray(response), &status)
if !status.ConfigInSync {
die("Config out of sync")
}
fmt.Println("Config in sync")
}
func generalVersion(c *cli.Context) {
response := httpGet(c, "system/version")
version := make(map[string]interface{})
json.Unmarshal(responseToBArray(response), &version)
prettyPrintJSON(version)
}

127
cmd/stcli/cmd_gui.go Normal file
View File

@@ -0,0 +1,127 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"fmt"
"strings"
"github.com/AudriusButkevicius/cli"
)
func init() {
cliCommands = append(cliCommands, cli.Command{
Name: "gui",
HideHelp: true,
Usage: "GUI command group",
Subcommands: []cli.Command{
{
Name: "dump",
Usage: "Show all GUI configuration settings",
Requires: &cli.Requires{},
Action: guiDump,
},
{
Name: "get",
Usage: "Get a GUI configuration setting",
Requires: &cli.Requires{"setting"},
Action: guiGet,
},
{
Name: "set",
Usage: "Set a GUI configuration setting",
Requires: &cli.Requires{"setting", "value"},
Action: guiSet,
},
{
Name: "unset",
Usage: "Unset a GUI configuration setting",
Requires: &cli.Requires{"setting"},
Action: guiUnset,
},
},
})
}
func guiDump(c *cli.Context) {
cfg := getConfig(c).GUI
writer := newTableWriter()
fmt.Fprintln(writer, "Enabled:\t", cfg.Enabled, "\t(enabled)")
fmt.Fprintln(writer, "Use HTTPS:\t", cfg.UseTLS(), "\t(tls)")
fmt.Fprintln(writer, "Listen Addresses:\t", cfg.Address(), "\t(address)")
if cfg.User != "" {
fmt.Fprintln(writer, "Authentication User:\t", cfg.User, "\t(username)")
fmt.Fprintln(writer, "Authentication Password:\t", cfg.Password, "\t(password)")
}
if cfg.APIKey != "" {
fmt.Fprintln(writer, "API Key:\t", cfg.APIKey, "\t(apikey)")
}
writer.Flush()
}
func guiGet(c *cli.Context) {
cfg := getConfig(c).GUI
arg := c.Args()[0]
switch strings.ToLower(arg) {
case "enabled":
fmt.Println(cfg.Enabled)
case "tls":
fmt.Println(cfg.UseTLS())
case "address":
fmt.Println(cfg.Address())
case "user":
if cfg.User != "" {
fmt.Println(cfg.User)
}
case "password":
if cfg.User != "" {
fmt.Println(cfg.Password)
}
case "apikey":
if cfg.APIKey != "" {
fmt.Println(cfg.APIKey)
}
default:
die("Invalid setting: " + arg + "\nAvailable settings: enabled, tls, address, user, password, apikey")
}
}
func guiSet(c *cli.Context) {
cfg := getConfig(c)
arg := c.Args()[0]
val := c.Args()[1]
switch strings.ToLower(arg) {
case "enabled":
cfg.GUI.Enabled = parseBool(val)
case "tls":
cfg.GUI.RawUseTLS = parseBool(val)
case "address":
validAddress(val)
cfg.GUI.RawAddress = val
case "user":
cfg.GUI.User = val
case "password":
cfg.GUI.Password = val
case "apikey":
cfg.GUI.APIKey = val
default:
die("Invalid setting: " + arg + "\nAvailable settings: enabled, tls, address, user, password, apikey")
}
setConfig(c, cfg)
}
func guiUnset(c *cli.Context) {
cfg := getConfig(c)
arg := c.Args()[0]
switch strings.ToLower(arg) {
case "user":
cfg.GUI.User = ""
case "password":
cfg.GUI.Password = ""
case "apikey":
cfg.GUI.APIKey = ""
default:
die("Invalid setting: " + arg + "\nAvailable settings: user, password, apikey")
}
setConfig(c, cfg)
}

173
cmd/stcli/cmd_options.go Normal file
View File

@@ -0,0 +1,173 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"fmt"
"strings"
"github.com/AudriusButkevicius/cli"
)
func init() {
cliCommands = append(cliCommands, cli.Command{
Name: "options",
HideHelp: true,
Usage: "Options command group",
Subcommands: []cli.Command{
{
Name: "dump",
Usage: "Show all Syncthing option settings",
Requires: &cli.Requires{},
Action: optionsDump,
},
{
Name: "get",
Usage: "Get a Syncthing option setting",
Requires: &cli.Requires{"setting"},
Action: optionsGet,
},
{
Name: "set",
Usage: "Set a Syncthing option setting",
Requires: &cli.Requires{"setting", "value..."},
Action: optionsSet,
},
},
})
}
func optionsDump(c *cli.Context) {
cfg := getConfig(c).Options
writer := newTableWriter()
fmt.Fprintln(writer, "Sync protocol listen addresses:\t", strings.Join(cfg.ListenAddresses, " "), "\t(addresses)")
fmt.Fprintln(writer, "Global discovery enabled:\t", cfg.GlobalAnnEnabled, "\t(globalannenabled)")
fmt.Fprintln(writer, "Global discovery servers:\t", strings.Join(cfg.GlobalAnnServers, " "), "\t(globalannserver)")
fmt.Fprintln(writer, "Local discovery enabled:\t", cfg.LocalAnnEnabled, "\t(localannenabled)")
fmt.Fprintln(writer, "Local discovery port:\t", cfg.LocalAnnPort, "\t(localannport)")
fmt.Fprintln(writer, "Outgoing rate limit in KiB/s:\t", cfg.MaxSendKbps, "\t(maxsend)")
fmt.Fprintln(writer, "Incoming rate limit in KiB/s:\t", cfg.MaxRecvKbps, "\t(maxrecv)")
fmt.Fprintln(writer, "Reconnect interval in seconds:\t", cfg.ReconnectIntervalS, "\t(reconnect)")
fmt.Fprintln(writer, "Start browser:\t", cfg.StartBrowser, "\t(browser)")
fmt.Fprintln(writer, "Enable UPnP:\t", cfg.NATEnabled, "\t(nat)")
fmt.Fprintln(writer, "UPnP Lease in minutes:\t", cfg.NATLeaseM, "\t(natlease)")
fmt.Fprintln(writer, "UPnP Renewal period in minutes:\t", cfg.NATRenewalM, "\t(natrenew)")
fmt.Fprintln(writer, "Restart on Wake Up:\t", cfg.RestartOnWakeup, "\t(wake)")
reporting := "unrecognized value"
switch cfg.URAccepted {
case -1:
reporting = "false"
case 0:
reporting = "undecided/false"
case 1:
reporting = "true"
}
fmt.Fprintln(writer, "Anonymous usage reporting:\t", reporting, "\t(reporting)")
writer.Flush()
}
func optionsGet(c *cli.Context) {
cfg := getConfig(c).Options
arg := c.Args()[0]
switch strings.ToLower(arg) {
case "address":
fmt.Println(strings.Join(cfg.ListenAddresses, "\n"))
case "globalannenabled":
fmt.Println(cfg.GlobalAnnEnabled)
case "globalannservers":
fmt.Println(strings.Join(cfg.GlobalAnnServers, "\n"))
case "localannenabled":
fmt.Println(cfg.LocalAnnEnabled)
case "localannport":
fmt.Println(cfg.LocalAnnPort)
case "maxsend":
fmt.Println(cfg.MaxSendKbps)
case "maxrecv":
fmt.Println(cfg.MaxRecvKbps)
case "reconnect":
fmt.Println(cfg.ReconnectIntervalS)
case "browser":
fmt.Println(cfg.StartBrowser)
case "nat":
fmt.Println(cfg.NATEnabled)
case "natlease":
fmt.Println(cfg.NATLeaseM)
case "natrenew":
fmt.Println(cfg.NATRenewalM)
case "reporting":
switch cfg.URAccepted {
case -1:
fmt.Println("false")
case 0:
fmt.Println("undecided/false")
case 1:
fmt.Println("true")
default:
fmt.Println("unknown")
}
case "wake":
fmt.Println(cfg.RestartOnWakeup)
default:
die("Invalid setting: " + arg + "\nAvailable settings: address, globalannenabled, globalannserver, localannenabled, localannport, maxsend, maxrecv, reconnect, browser, upnp, upnplease, upnprenew, reporting, wake")
}
}
func optionsSet(c *cli.Context) {
config := getConfig(c)
arg := c.Args()[0]
val := c.Args()[1]
switch strings.ToLower(arg) {
case "address":
for _, item := range c.Args().Tail() {
validAddress(item)
}
config.Options.ListenAddresses = c.Args().Tail()
case "globalannenabled":
config.Options.GlobalAnnEnabled = parseBool(val)
case "globalannserver":
for _, item := range c.Args().Tail() {
validAddress(item)
}
config.Options.GlobalAnnServers = c.Args().Tail()
case "localannenabled":
config.Options.LocalAnnEnabled = parseBool(val)
case "localannport":
config.Options.LocalAnnPort = parsePort(val)
case "maxsend":
config.Options.MaxSendKbps = parseUint(val)
case "maxrecv":
config.Options.MaxRecvKbps = parseUint(val)
case "reconnect":
config.Options.ReconnectIntervalS = parseUint(val)
case "browser":
config.Options.StartBrowser = parseBool(val)
case "nat":
config.Options.NATEnabled = parseBool(val)
case "natlease":
config.Options.NATLeaseM = parseUint(val)
case "natrenew":
config.Options.NATRenewalM = parseUint(val)
case "reporting":
switch strings.ToLower(val) {
case "u", "undecided", "unset":
config.Options.URAccepted = 0
default:
boolvalue := parseBool(val)
if boolvalue {
config.Options.URAccepted = 1
} else {
config.Options.URAccepted = -1
}
}
case "wake":
config.Options.RestartOnWakeup = parseBool(val)
default:
die("Invalid setting: " + arg + "\nAvailable settings: address, globalannenabled, globalannserver, localannenabled, localannport, maxsend, maxrecv, reconnect, browser, upnp, upnplease, upnprenew, reporting, wake")
}
setConfig(c, config)
}

72
cmd/stcli/cmd_report.go Normal file
View File

@@ -0,0 +1,72 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"encoding/json"
"fmt"
"github.com/AudriusButkevicius/cli"
)
func init() {
cliCommands = append(cliCommands, cli.Command{
Name: "report",
HideHelp: true,
Usage: "Reporting command group",
Subcommands: []cli.Command{
{
Name: "system",
Usage: "Report system state",
Requires: &cli.Requires{},
Action: reportSystem,
},
{
Name: "connections",
Usage: "Report about connections to other devices",
Requires: &cli.Requires{},
Action: reportConnections,
},
{
Name: "usage",
Usage: "Usage report",
Requires: &cli.Requires{},
Action: reportUsage,
},
},
})
}
func reportSystem(c *cli.Context) {
response := httpGet(c, "system/status")
data := make(map[string]interface{})
json.Unmarshal(responseToBArray(response), &data)
prettyPrintJSON(data)
}
func reportConnections(c *cli.Context) {
response := httpGet(c, "system/connections")
data := make(map[string]map[string]interface{})
json.Unmarshal(responseToBArray(response), &data)
var overall map[string]interface{}
for key, value := range data {
if key == "total" {
overall = value
continue
}
value["Device ID"] = key
prettyPrintJSON(value)
fmt.Println()
}
if overall != nil {
fmt.Println("=== Overall statistics ===")
prettyPrintJSON(overall)
}
}
func reportUsage(c *cli.Context) {
response := httpGet(c, "svc/report")
report := make(map[string]interface{})
json.Unmarshal(responseToBArray(response), &report)
prettyPrintJSON(report)
}

31
cmd/stcli/labels.go Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
var jsonAttributeLabels = map[string]string{
"folderMaxMiB": "Largest folder size in MiB",
"folderMaxFiles": "Largest folder file count",
"longVersion": "Long version",
"totMiB": "Total size in MiB",
"totFiles": "Total files",
"uniqueID": "Unique ID",
"numFolders": "Folder count",
"numDevices": "Device count",
"memoryUsageMiB": "Memory usage in MiB",
"memorySize": "Total memory in MiB",
"sha256Perf": "SHA256 Benchmark",
"At": "Last contacted",
"Completion": "Percent complete",
"InBytesTotal": "Total bytes received",
"OutBytesTotal": "Total bytes sent",
"ClientVersion": "Client version",
"alloc": "Memory allocated in bytes",
"sys": "Memory using in bytes",
"cpuPercent": "CPU load in percent",
"extAnnounceOK": "External announcments working",
"goroutines": "Number of Go routines",
"myID": "Client ID",
"tilde": "Tilde expands to",
"arch": "Architecture",
"os": "OS",
}

63
cmd/stcli/main.go Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"sort"
"github.com/AudriusButkevicius/cli"
)
type ByAlphabet []cli.Command
func (a ByAlphabet) Len() int { return len(a) }
func (a ByAlphabet) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAlphabet) Less(i, j int) bool { return a[i].Name < a[j].Name }
var cliCommands []cli.Command
func main() {
app := cli.NewApp()
app.Name = "syncthing-cli"
app.Author = "Audrius Butkevičius"
app.Email = "audrius.butkevicius@gmail.com"
app.Usage = "Syncthing command line interface"
app.Version = "0.1"
app.HideHelp = true
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "endpoint, e",
Value: "http://127.0.0.1:8384",
Usage: "End point to connect to",
EnvVar: "STENDPOINT",
},
cli.StringFlag{
Name: "apikey, k",
Value: "",
Usage: "API Key",
EnvVar: "STAPIKEY",
},
cli.StringFlag{
Name: "username, u",
Value: "",
Usage: "Username",
EnvVar: "STUSERNAME",
},
cli.StringFlag{
Name: "password, p",
Value: "",
Usage: "Password",
EnvVar: "STPASSWORD",
},
cli.BoolFlag{
Name: "insecure, i",
Usage: "Do not verify SSL certificate",
EnvVar: "STINSECURE",
},
}
sort.Sort(ByAlphabet(cliCommands))
app.Commands = cliCommands
app.RunAndExitOnError()
}

165
cmd/stcli/utils.go Normal file
View File

@@ -0,0 +1,165 @@
// Copyright (C) 2014 Audrius Butkevičius
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"sort"
"strconv"
"strings"
"text/tabwriter"
"unicode"
"github.com/AudriusButkevicius/cli"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
)
func responseToBArray(response *http.Response) []byte {
defer response.Body.Close()
bytes, err := ioutil.ReadAll(response.Body)
if err != nil {
die(err)
}
return bytes
}
func die(vals ...interface{}) {
if len(vals) > 1 || vals[0] != nil {
os.Stderr.WriteString(fmt.Sprintln(vals...))
os.Exit(1)
}
}
func wrappedHTTPPost(url string) func(c *cli.Context) {
return func(c *cli.Context) {
httpPost(c, url, "")
}
}
func prettyPrintJSON(json map[string]interface{}) {
writer := newTableWriter()
remap := make(map[string]interface{})
for k, v := range json {
key, ok := jsonAttributeLabels[k]
if !ok {
key = firstUpper(k)
}
remap[key] = v
}
jsonKeys := make([]string, 0, len(remap))
for key := range remap {
jsonKeys = append(jsonKeys, key)
}
sort.Strings(jsonKeys)
for _, k := range jsonKeys {
var value string
rvalue := remap[k]
switch rvalue.(type) {
case int, int16, int32, int64, uint, uint16, uint32, uint64, float32, float64:
value = fmt.Sprintf("%.0f", rvalue)
default:
value = fmt.Sprint(rvalue)
}
if value == "" {
continue
}
fmt.Fprintln(writer, k+":\t"+value)
}
writer.Flush()
}
func firstUpper(str string) string {
for i, v := range str {
return string(unicode.ToUpper(v)) + str[i+1:]
}
return ""
}
func newTableWriter() *tabwriter.Writer {
writer := new(tabwriter.Writer)
writer.Init(os.Stdout, 0, 8, 0, '\t', 0)
return writer
}
func getMyID(c *cli.Context) string {
response := httpGet(c, "system/status")
data := make(map[string]interface{})
json.Unmarshal(responseToBArray(response), &data)
return data["myID"].(string)
}
func getConfig(c *cli.Context) config.Configuration {
response := httpGet(c, "system/config")
config := config.Configuration{}
json.Unmarshal(responseToBArray(response), &config)
return config
}
func setConfig(c *cli.Context, cfg config.Configuration) {
body, err := json.Marshal(cfg)
die(err)
response := httpPost(c, "system/config", string(body))
if response.StatusCode != 200 {
die("Unexpected status code", response.StatusCode)
}
}
func parseBool(input string) bool {
val, err := strconv.ParseBool(input)
if err != nil {
die(input + " is not a valid value for a boolean")
}
return val
}
func parseInt(input string) int {
val, err := strconv.ParseInt(input, 0, 64)
if err != nil {
die(input + " is not a valid value for an integer")
}
return int(val)
}
func parseUint(input string) int {
val, err := strconv.ParseUint(input, 0, 64)
if err != nil {
die(input + " is not a valid value for an unsigned integer")
}
return int(val)
}
func parsePort(input string) int {
port := parseUint(input)
if port < 1 || port > 65535 {
die(input + " is not a valid port\nExpected value between 1 and 65535")
}
return port
}
func validAddress(input string) {
tokens := strings.Split(input, ":")
if len(tokens) != 2 {
die(input + " is not a valid value for an address\nExpected format <ip or hostname>:<port>")
}
matched, err := regexp.MatchString("^[a-zA-Z0-9]+([-a-zA-Z0-9.]+[-a-zA-Z0-9]+)?$", tokens[0])
die(err)
if !matched {
die(input + " is not a valid value for an address\nExpected format <ip or hostname>:<port>")
}
parsePort(tokens[1])
}
func parseDeviceID(input string) protocol.DeviceID {
device, err := protocol.DeviceIDFromString(input)
if err != nil {
die(input + " is not a valid device id")
}
return device
}

View File

@@ -66,7 +66,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
}
func generateOneFile(fd io.ReadSeeker, p1 string, s int64) error {
src := io.LimitReader(&inifiteReader{fd}, int64(s))
src := io.LimitReader(&inifiteReader{fd}, s)
dst, err := os.Create(p1)
if err != nil {
return err
@@ -85,12 +85,7 @@ func generateOneFile(fd io.ReadSeeker, p1 string, s int64) error {
_ = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
err = os.Chtimes(p1, t, t)
if err != nil {
return err
}
return nil
return os.Chtimes(p1, t, t)
}
func randomName() string {

View File

@@ -249,13 +249,13 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
switch r.Method {
case "GET":
if limit(r.RemoteAddr, getLRUCache, getMut, getLimit, int64(getLimitBurst)) {
if limit(r.RemoteAddr, getLRUCache, getMut, getLimit, getLimitBurst) {
w.WriteHeader(429)
return
}
handleGetRequest(w, r)
case "POST":
if limit(r.RemoteAddr, postLRUCache, postMut, postLimit, int64(postLimitBurst)) {
if limit(r.RemoteAddr, postLRUCache, postMut, postLimit, postLimitBurst) {
w.WriteHeader(429)
return
}

View File

@@ -54,7 +54,6 @@ func init() {
var (
listen string
debug bool
proto string
sessionAddress []byte
sessionPort uint16
@@ -129,10 +128,10 @@ func main() {
laddr.Port = 0
transport, ok := http.DefaultTransport.(*http.Transport)
if ok {
transport.DialContext = (&net.Dialer{
transport.Dial = (&net.Dialer{
Timeout: 30 * time.Second,
LocalAddr: laddr,
}).DialContext
}).Dial
}
}

View File

@@ -216,7 +216,7 @@ done:
func (s *session) GetClientInvitationMessage() protocol.SessionInvitation {
return protocol.SessionInvitation{
From: s.serverid[:],
Key: []byte(s.clientkey),
Key: s.clientkey,
Address: sessionAddress,
Port: sessionPort,
ServerSocket: false,
@@ -226,7 +226,7 @@ func (s *session) GetClientInvitationMessage() protocol.SessionInvitation {
func (s *session) GetServerInvitationMessage() protocol.SessionInvitation {
return protocol.SessionInvitation{
From: s.clientid[:],
Key: []byte(s.serverkey),
Key: s.serverkey,
Address: sessionAddress,
Port: sessionPort,
ServerSocket: true,

View File

@@ -42,7 +42,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
status["goMaxProcs"] = runtime.GOMAXPROCS(-1)
status["goNumRoutine"] = runtime.NumGoroutine()
status["kbps10s1m5m15m30m60m"] = []int64{
rc.rate(10/10) * 8 / 1000,
rc.rate(1) * 8 / 1000, // each interval is 10s
rc.rate(60/10) * 8 / 1000,
rc.rate(5*60/10) * 8 / 1000,
rc.rate(15*60/10) * 8 / 1000,

View File

@@ -82,8 +82,6 @@ type modelIntf interface {
Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability
GetIgnores(folder string) ([]string, []string, error)
SetIgnores(folder string, content []string) error
PauseDevice(device protocol.DeviceID)
ResumeDevice(device protocol.DeviceID)
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
@@ -105,6 +103,7 @@ type configIntf interface {
Subscribe(c config.Committer)
Folders() map[string]config.FolderConfiguration
Devices() map[protocol.DeviceID]config.DeviceConfiguration
SetDevice(config.DeviceConfiguration) error
Save() error
ListenAddresses() []string
RequiresRestart() bool
@@ -258,21 +257,21 @@ func (s *apiService) Serve() {
// The POST handlers
postRestMux := http.NewServeMux()
postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
postRestMux.HandleFunc("/rest/system/pause", s.makeDevicePauseHandler(true)) // device
postRestMux.HandleFunc("/rest/system/resume", s.makeDevicePauseHandler(false)) // device
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
// Debug endpoints, not for general use
debugMux := http.NewServeMux()
@@ -327,8 +326,10 @@ func (s *apiService) Serve() {
handler = debugMiddleware(handler)
srv := http.Server{
Handler: handler,
ReadTimeout: 10 * time.Second,
Handler: handler,
// ReadTimeout must be longer than SyncthingController $scope.refresh
// interval to avoid HTTP keepalive/GUI refresh race.
ReadTimeout: 15 * time.Second,
}
s.fss = newFolderSummaryService(s.cfg, s.model)
@@ -381,10 +382,8 @@ func (s *apiService) String() string {
}
func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
if _, err := net.ResolveTCPAddr("tcp", to.GUI.Address()); err != nil {
return err
}
return nil
_, err := net.ResolveTCPAddr("tcp", to.GUI.Address())
return err
}
func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
@@ -1105,30 +1104,27 @@ func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
}
}
func (s *apiService) postSystemPause(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
func (s *apiService) makeDevicePauseHandler(paused bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
cfg, ok := s.cfg.Devices()[device]
if !ok {
http.Error(w, "not found", http.StatusNotFound)
}
cfg.Paused = paused
if err := s.cfg.SetDevice(cfg); err != nil {
http.Error(w, err.Error(), 500)
}
}
s.model.PauseDevice(device)
}
func (s *apiService) postSystemResume(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
s.model.ResumeDevice(device)
}
func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {

View File

@@ -199,7 +199,8 @@ var (
type RuntimeOptions struct {
confDir string
reset bool
resetDatabase bool
resetDeltaIdxs bool
showVersion bool
showPaths bool
doUpgrade bool
@@ -212,6 +213,7 @@ type RuntimeOptions struct {
auditEnabled bool
verbose bool
paused bool
unpaused bool
guiAddress string
guiAPIKey string
generateDir string
@@ -259,7 +261,8 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Do not restart; just exit")
flag.BoolVar(&options.reset, "reset", false, "Reset the database")
flag.BoolVar(&options.resetDatabase, "reset-database", false, "Reset the database, forcing a full rescan and resync")
flag.BoolVar(&options.resetDeltaIdxs, "reset-deltas", false, "Reset delta index IDs, forcing a full index exchange")
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
flag.BoolVar(&options.showVersion, "version", false, "Show version")
@@ -267,7 +270,8 @@ func parseCommandLineOptions() RuntimeOptions {
flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
flag.BoolVar(&options.verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices paused")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (use \"-\" for stdout)")
if runtime.GOOS == "windows" {
// Allow user to hide the console window
@@ -367,7 +371,7 @@ func main() {
return
}
if options.reset {
if options.resetDatabase {
resetDB()
return
}
@@ -554,8 +558,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.
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected), 1000)
diskSub := events.NewBufferedSubscription(events.Default.Subscribe(events.LocalChangeDetected), 1000)
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected&^events.RemoteChangeDetected), 1000)
diskSub := events.NewBufferedSubscription(events.Default.Subscribe(events.LocalChangeDetected|events.RemoteChangeDetected), 1000)
if len(os.Getenv("GOMAXPROCS")) == 0 {
runtime.GOMAXPROCS(runtime.NumCPU())
@@ -620,6 +624,10 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
0xCCA8, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, Go 1.8
0xCCA9, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, Go 1.8
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
@@ -663,6 +671,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
}
if runtimeOptions.resetDeltaIdxs {
l.Infoln("Reinitializing delta index IDs")
ldb.DropDeltaIndexIDs()
}
protectedFiles := []string{
locations[locDatabase],
locations[locConfigFile],
@@ -697,14 +710,17 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
m.StartDeadlockDetector(20 * time.Minute)
}
if runtimeOptions.paused {
for device := range cfg.Devices() {
m.PauseDevice(device)
}
if runtimeOptions.unpaused {
setPauseState(cfg, false)
} else if runtimeOptions.paused {
setPauseState(cfg, true)
}
// Add and start folders
for _, folderCfg := range cfg.Folders() {
if folderCfg.Paused {
continue
}
m.AddFolder(folderCfg)
m.StartFolder(folderCfg.ID)
}
@@ -1066,7 +1082,7 @@ func getFreePort(host string, ports ...int) (int, error) {
}
func standbyMonitor() {
restartDelay := time.Duration(60 * time.Second)
restartDelay := 60 * time.Second
now := time.Now()
for {
time.Sleep(10 * time.Second)
@@ -1199,3 +1215,16 @@ func showPaths() {
fmt.Printf("GUI override directory:\n\t%s\n\n", locations[locGUIAssets])
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations[locDefFolder])
}
func setPauseState(cfg *config.Wrapper, paused bool) {
raw := cfg.RawCopy()
for i := range raw.Devices {
raw.Devices[i].Paused = paused
}
for i := range raw.Folders {
raw.Folders[i].Paused = paused
}
if err := cfg.Replace(raw); err != nil {
l.Fatalln("Cannot adjust paused state:", err)
}
}

View File

@@ -45,6 +45,10 @@ func (c *mockedConfig) Devices() map[protocol.DeviceID]config.DeviceConfiguratio
return nil
}
func (c *mockedConfig) SetDevice(config.DeviceConfiguration) error {
return nil
}
func (c *mockedConfig) Save() error {
return nil
}

View File

@@ -202,8 +202,24 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
}
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
if strings.Contains(line, "leveldb") && strings.Contains(line, "corrupt") {
l.Warnln(`
*********************************************************************************
* Crash due to corrupt database. *
* *
* This crash usually occurs due to one of the following reasons: *
* - Syncthing being stopped abruptly (killed/loss of power) *
* - Bad hardware (memory/disk issues) *
* - Software that affects disk writes (SSD caching software and simillar) *
* *
* Please see the following URL for instructions on how to recover: *
* https://docs.syncthing.net/users/faq.html#my-syncthing-database-is-corrupt *
*********************************************************************************
`)
} else {
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
}
stdoutMut.Lock()
for _, line := range stdoutFirstLines {

View File

@@ -134,7 +134,7 @@ func reportData(cfg configIntf, m modelIntf) map[string]interface{} {
for _, cfg := range cfg.Folders() {
rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS)
if cfg.Type == config.FolderTypeReadOnly {
if cfg.Type == config.FolderTypeSendOnly {
folderUses["readonly"]++
}
if cfg.IgnorePerms {

View File

@@ -94,9 +94,12 @@ func (s *verboseService) formatEvent(ev events.Event) string {
case events.LocalChangeDetected:
data := ev.Data.(map[string]string)
// Local change detected in folder "foo": modified file /Users/jb/whatever
return fmt.Sprintf("Local change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"])
case events.RemoteChangeDetected:
data := ev.Data.(map[string]string)
return fmt.Sprintf("Remote change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"])
case events.RemoteIndexUpdated:
data := ev.Data.(map[string]interface{})
return fmt.Sprintf("Device %v sent an index update for %q with %d items", data["device"], data["folder"], data["items"])
@@ -163,6 +166,18 @@ func (s *verboseService) formatEvent(ev events.Event) string {
device := data["device"]
return fmt.Sprintf("Device %v was resumed", device)
case events.FolderPaused:
data := ev.Data.(map[string]string)
id := data["id"]
label := data["label"]
return fmt.Sprintf("Folder %v (%v) was paused", id, label)
case events.FolderResumed:
data := ev.Data.(map[string]string)
id := data["id"]
label := data["label"]
return fmt.Sprintf("Folder %v (%v) was resumed", id, label)
case events.ListenAddressesChanged:
data := ev.Data.(map[string]interface{})
address := data["address"]

View File

@@ -4,19 +4,28 @@ Installation
-----------
**Please note:** When you installed syncthing using the official deb package, you can skip the copying.
Copy the file `syncthing` to your ufw applications directory usually located at `/etc/ufw/applications.d/`. (root permissions required).
Copy the file `syncthing` to your ufw applications directory usually located at `/etc/ufw/applications.d/` (root permissions required).
In a terminal run
```
sudo ufw app update syncthing
sudo ufw app update syncthing-gui
```
to load the preset.
to load the presets.
To allow the syncthing ports, run
```
sudo ufw allow syncthing
```
You can then verify the opened ports
If you want to access the web gui from anywhere (not only from localhost), you can also allow the gui port.
This is step is **not** necessary for a "normal" installation!
```
sudo ufw allow syncthing-gui
```
Verification
----------
You can verify the opened ports by running
```
sudo ufw status verbose
```

View File

@@ -2,3 +2,8 @@
title=Syncthing
description=Syncthing file synchronisation
ports=22000/tcp|21027/udp
[syncthing-gui]
title=Syncthing-GUI
description=Syncthing web gui
ports=8384/tcp

View File

@@ -52,6 +52,8 @@
"Downloaded": "Изтеглен",
"Downloading": "Изтегляне",
"Edit": "Промени",
"Edit Device": "Промяна на устройството",
"Edit Folder": "Промяна на папката",
"Editing": "Променяне",
"Enable NAT traversal": "Разреши NAT traversal",
"Enable Relaying": "Разреши препращане",
@@ -95,6 +97,7 @@
"Last Scan": "Последно сканиран",
"Last seen": "Последно видяно",
"Later": "По-късно",
"Latest Change": "Последна промяна",
"Listeners": "Синхронизиращи устройства",
"Local Discovery": "Локално откриване",
"Local State": "Локално състояние",
@@ -153,6 +156,8 @@
"Scanning": "Сканиране",
"Select the devices to share this folder with.": "Изберете устройствата, с които да споделите папката.",
"Select the folders to share with this device.": "Изберете папките за споделяне с това устройство.",
"Send & Receive": "Изпращане & получаване",
"Send Only": "Само изпращане",
"Settings": "Настройки",
"Share": "Сподели",
"Share Folder": "Сподели папка",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Descarregat",
"Downloading": "Descarregant",
"Edit": "Editar",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Modificant",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "Vist per última vegada",
"Later": "Després",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "Descobriment Local",
"Local State": "Estat local",
@@ -153,6 +156,8 @@
"Scanning": "Escanejant",
"Select the devices to share this folder with.": "Selecciona els dispositius en els quals compartir aquesta carpeta.",
"Select the folders to share with this device.": "Selecciona la carpeta per a compartir en aquest dispositiu.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Preferències",
"Share": "Compartir",
"Share Folder": "Compartir carpeta",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Descarregat",
"Downloading": "Descarregant",
"Edit": "Editar",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Editant",
"Enable NAT traversal": "Permetre NAT transversal",
"Enable Relaying": "Permetre Transmissions",
@@ -95,6 +97,7 @@
"Last Scan": "Últim escaneig",
"Last seen": "Vist per última vegada",
"Later": "Més tard",
"Latest Change": "Latest Change",
"Listeners": "Escoltants",
"Local Discovery": "Descobriment local",
"Local State": "Estat local",
@@ -153,6 +156,8 @@
"Scanning": "Rastrejant",
"Select the devices to share this folder with.": "Selecciona els dispositius amb els que compartir aquesta carpeta.",
"Select the folders to share with this device.": "Selecciona les carpetes per a compartir amb aquest dispositiu.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Ajustos",
"Share": "Compartir",
"Share Folder": "Compartir carpeta",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Staženo",
"Downloading": "Stahuji",
"Edit": "Upravit",
"Edit Device": "Upravit zařízení",
"Edit Folder": "Upravit adresář",
"Editing": "Upravuje se",
"Enable NAT traversal": "Povolit NAT přenos",
"Enable Relaying": "Povolit přenašeče",
@@ -95,6 +97,7 @@
"Last Scan": "Poslední sken",
"Last seen": "Naposledy spatřen",
"Later": "Později",
"Latest Change": "Poslední změna",
"Listeners": "Naslouchající",
"Local Discovery": "Místní oznamování",
"Local State": "Místní status",
@@ -153,6 +156,8 @@
"Scanning": "Skenování",
"Select the devices to share this folder with.": "Vybrat přístroje, se kterými sdílet tento adresář.",
"Select the folders to share with this device.": "Vybrat adresáře sdílené s tímto přístrojem.",
"Send & Receive": "Odeslat a přijmout",
"Send Only": "Pouze odeslat",
"Settings": "Nastavení",
"Share": "Sdílet",
"Share Folder": "Sdílet adresář",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Downloadet",
"Downloading": "Downloader",
"Edit": "Rediger",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Redigerer",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "Sidst set",
"Later": "Senere",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "Lokal opslag",
"Local State": "Lokal tilstand",
@@ -153,6 +156,8 @@
"Scanning": "Opdaterer",
"Select the devices to share this folder with.": "Vælg hvilke enheder du vil dele denne mappe med",
"Select the folders to share with this device.": "Vælg hvilke mapper du vil dele med denne enhed.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Indstillinger",
"Share": "Del",
"Share Folder": "Delt mappe",

View File

@@ -1,15 +1,15 @@
{
"A device with that ID is already added.": "Ein Gerät mit dieser ID existiert bereits.",
"A device with that ID is already added.": "Ein Gerät mit dieser Kennung ist bereits hinzugefügt.",
"A negative number of days doesn't make sense.": "Eine negative Anzahl von Tagen ergibt keinen Sinn.",
"A new major version may not be compatible with previous versions.": "Die neue Hauptversion ist evtl. nicht mit vorherigen Versionen kompatibel.",
"API Key": "API-Schlüssel",
"About": "Über Syncthing",
"About": "Über",
"Actions": "Aktionen",
"Add": "Hinzufügen",
"Add Device": "Gerät hinzufügen",
"Add Folder": "Verzeichnis hinzufügen",
"Add Remote Device": "Remote-Gerät hinzufügen",
"Add new folder?": "Neues Verzeichnis hinzufügen?",
"Add Folder": "Ordner hinzufügen",
"Add Remote Device": "Fern-Gerät hinzufügen",
"Add new folder?": "Neuen Ordner hinzufügen?",
"Address": "Adresse",
"Addresses": "Adressen",
"Advanced": "Erweitert",
@@ -18,7 +18,7 @@
"All Data": "Alle Daten",
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
"Alphabetic": "Alphabetisch",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem Verzeichnis entfernen.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem Ordner entfernen.",
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteiler eingetragen sind, werden auch bei diesem Gerät eingetragen",
"Automatic upgrades": "Automatische Updates aktivieren",
@@ -40,8 +40,8 @@
"Danger!": "Achtung!",
"Deleted": "Gelöscht",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät \"{{name}}\" ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Device ID": "Geräte ID",
"Device Identification": "Geräte Identifikation",
"Device ID": "Gerätekennung",
"Device Identification": "Geräteidentifikation",
"Device Name": "Gerätename",
"Devices": "Geräte",
"Disconnected": "Getrennt",
@@ -52,6 +52,8 @@
"Downloaded": "Heruntergeladen",
"Downloading": "Lädt herunter",
"Edit": "Bearbeiten",
"Edit Device": "Gerät bearbeiten",
"Edit Folder": "Ordner bearbeiten",
"Editing": "Bearbeitet",
"Enable NAT traversal": "NAT-Durchdringung aktivieren",
"Enable Relaying": "Weiterleitung aktivieren",
@@ -63,15 +65,15 @@
"File Pull Order": "Dateiübertragungsreihenfolge",
"File Versioning": "Dateiversionierung",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Wenn Syncthing Dateien ersetzt oder löscht, werden sie in das .stversions Verzeichnis verschoben.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, datiert in das Verzeichnis .stversions verschoben.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Wenn Syncthing Dateien ersetzt oder löscht, werden sie in den Ordner .stversions verschoben.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, datiert in den Ordner .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind auf diesem Gerät schreibgeschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Folder": "Verzeichnis",
"Folder ID": "Verzeichnis ID",
"Folder Label": "Verzeichnisbezeichnung",
"Folder Path": "Verzeichnispfad",
"Folder Type": "Verzeichnistyp",
"Folders": "Verzeichnisse",
"Folder": "Ordner",
"Folder ID": "Ordnerkennung",
"Folder Label": "Ordnerbezeichnung",
"Folder Path": "Ordnerpfad",
"Folder Type": "Ordnertyp",
"Folders": "Ordner",
"GUI": "GUI",
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
"GUI Authentication User": "Nutzername für Zugang zur Benutzeroberfläche",
@@ -86,7 +88,7 @@
"Ignore Patterns": "Ignoriermuster",
"Ignore Permissions": "Berechtigungen ignorieren",
"Incoming Rate Limit (KiB/s)": "Limit Datenrate (eingehend) (KB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Eine falsche Konfiguration kann den Verzeichnisinhalt beschädigen und Syncthing in einen unausführbaren Zustand versetzen.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Eine falsche Konfiguration kann den Ordnerinhalt beschädigen und Syncthing in einen unausführbaren Zustand versetzen.",
"Introducer": "Verteilergerät",
"Inversion of the given condition (i.e. do not exclude)": "Umkehrung der angegebenen Bedingung (z.B. schließe nicht aus)",
"Keep Versions": "Versionen erhalten",
@@ -95,6 +97,7 @@
"Last Scan": "Letzter Scan",
"Last seen": "Zuletzt online",
"Later": "Später",
"Latest Change": "Letzte Änderung",
"Listeners": "Zuhörer",
"Local Discovery": "Lokale Gerätesuche",
"Local State": "Lokaler Status",
@@ -105,10 +108,10 @@
"Metadata Only": "Nur Metadaten",
"Minimum Free Disk Space": "Minimal freier Festplattenspeicher",
"Move to top of queue": "An den Anfang der Warteschlange setzen",
"Multi level wildcard (matches multiple directory levels)": "Verschachteltes Maskenzeichen (wird für verschachtelte Verzeichnisse verwendet)",
"Multi level wildcard (matches multiple directory levels)": "Verschachteltes Maskenzeichen (wird für verschachtelte Ordner verwendet)",
"Never": "Nie",
"New Device": "Neues Gerät",
"New Folder": "Neues Verzeichnis",
"New Folder": "Neuer Ordner",
"Newest First": "Neueste zuerst",
"No": "Nein",
"No File Versioning": "Keine Dateiversionierung",
@@ -117,14 +120,14 @@
"OK": "OK",
"Off": "Aus",
"Oldest First": "Älteste zuerst",
"Optional descriptive label for the folder. Can be different on each device.": "Optionale beschreibende Bezeichnung des Verzeichnisses. Kann auf jedem Gerät unterschiedlich sein.",
"Optional descriptive label for the folder. Can be different on each device.": "Optionale beschreibende Bezeichnung des Ordners. Kann auf jedem Gerät unterschiedlich sein.",
"Options": "Optionen",
"Out of Sync": "Nicht synchronisiert",
"Out of Sync Items": "Nicht synchronisierte Objekte",
"Outgoing Rate Limit (KiB/s)": "Limit Datenrate (ausgehend) (KB/s)",
"Override Changes": "Änderungen überschreiben",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Gerät. Verzeichnis wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem alte Dateiversionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem alte Dateiversionen gespeichert werden sollen (ohne Angabe wird der Ordner .stversions im Ordner verwendet).",
"Pause": "Pause",
"Paused": "Pausiert",
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
@@ -135,11 +138,11 @@
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"RAM Utilization": "RAM Auslastung",
"Random": "Zufall",
"Reduced by ignore patterns": "Beschränkt durch Ignoriermuster",
"Reduced by ignore patterns": "Durch Ignoriermuster reduziert",
"Release Notes": "Veröffentlichungsnotizen",
"Remote Devices": "Remote-Geräte",
"Remote Devices": "Fern-Geräte",
"Remove": "Entfernen",
"Required identifier for the folder. Must be the same on all cluster devices.": "Erforderliche ID für das Verzeichnis. Muss auf allen Verbund-Geräten gleich sein.",
"Required identifier for the folder. Must be the same on all cluster devices.": "Erforderlicher Bezeichner für den Ordner. Muss auf allen Verbund-Geräten gleich sein.",
"Rescan": "Neu scannen",
"Rescan All": "Alle neu scannen",
"Rescan Interval": "Scanintervall",
@@ -151,23 +154,25 @@
"Save": "Speichern",
"Scan Time Remaining": "Zeit für Scan verbleibend",
"Scanning": "Scannen",
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du dieses Verzeichnis teilen willst.",
"Select the folders to share with this device.": "Wähle die Verzeichnisse aus, die du mit diesem Gerät teilen möchtest",
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du diesen Ordner teilen willst.",
"Select the folders to share with this device.": "Wähle die Ordner aus, die Du mit diesem Gerät teilen möchtest",
"Send & Receive": "Senden & empfangen",
"Send Only": "Nur senden",
"Settings": "Einstellungen",
"Share": "Teilen",
"Share Folder": "Teile Verzeichnis",
"Share Folders With Device": "Teile Verzeichnisse mit diesem Gerät",
"Share Folder": "Ordner teilen",
"Share Folders With Device": "Ordner mit diesem Gerät teilen",
"Share With Devices": "Teile mit diesen Geräten",
"Share this folder?": "Dieses Verzeichnis teilen?",
"Share this folder?": "Diesen Ordner teilen?",
"Shared With": "Geteilt mit",
"Show ID": "ID anzeigen",
"Show ID": "Kennung anzeigen",
"Show QR": "Zeige QR Code",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients im Cluster weitergegeben.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Gerätekennung im Verbund-Status angezeigt. Wird als optionaler Standardname an andere Geräte bekannt gegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird auf diesem Gerät als Gerätename angezeigt und an die anderen Geräte im Geräte-Verbund weitergegeben. Wenn kein Gerätename anegegeben wird, wird der Name des entfernten Gerätes genommen.",
"Shutdown": "Herunterfahren",
"Shutdown Complete": "Vollständig Heruntergefahren",
"Simple File Versioning": "Einfache Dateiversionierung",
"Single level wildcard (matches within a directory only)": "Einzelnes Maskenzeichen (wird für ein einzelnes Verzeichnis verwendet)",
"Single level wildcard (matches within a directory only)": "Einzelnes Maskenzeichen (wird für einen einzelnen Ordner verwendet)",
"Smallest First": "Kleinstes zuerst",
"Source Code": "Quellcode",
"Staggered File Versioning": "Stufenweise Dateiversionierung",
@@ -186,14 +191,14 @@
"The Syncthing admin interface is configured to allow remote access without a password.": "Die Syncthing-Oberfläche erlaubt mit den jetzigen Einstellungen einen Zugriff ohne Passwort.",
"The aggregated statistics are publicly available at the URL below.": "Die gesammelten Statistiken sind öffentlich unter der nachfolgenden URL verfügbar.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber noch nicht aktiviert. Syncthing muss neugestartet werden, um die neue Konfiguration zu übernehmen.",
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Aktionen > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird verwendet, um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
"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.": "Die eingegebene Geräte ID scheint nicht gültig zu sein. Es sollte eine 52 oder 56 stellige Zeichenkette aus Buchstaben und Nummern sein. Leerzeichen und Bindestriche sind optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Der erste Kommandozeilenparameter ist der Verzeichnis-Pfad und der zweite Parameter ist der relative Pfad in diesem Verzeichnis.",
"The folder ID cannot be blank.": "Die Verzeichnis ID darf nicht leer sein.",
"The folder ID must be unique.": "Die Verzeichnis ID darf nur einmal existieren.",
"The folder path cannot be blank.": "Der Verzeichnispfad darf nicht leer sein.",
"The device ID cannot be blank.": "Die Gerätekennung darf nicht leer sein.",
"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).": "Die hier einzutragende Gerätekennung kann im Dialog \"Aktionen > Kennung anzeigen\" auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird verwendet, um Statistiken über verwendete Betriebssysteme, Ordnergrößen und Programmversionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
"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.": "Die eingegebene Gerätekennung scheint nicht gültig zu sein. Es sollte eine 52 oder 56 stellige Zeichenkette aus Buchstaben und Nummern sein. Leerzeichen und Bindestriche sind optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Der erste Kommandozeilenparameter ist der Ordnerpfad und der zweite Parameter ist der relative Pfad in diesem Ordner.",
"The folder ID cannot be blank.": "Die Ordnerkennung darf nicht leer sein.",
"The folder ID must be unique.": "Die Ordnerkennung muss eindeutig sein.",
"The folder path cannot be blank.": "Der Ordnerpfad darf nicht leer sein.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es wird in folgenden Abständen versioniert: In der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag. Danach wird bis zum angegebenen Höchstalter eine Version pro Woche behalten.",
"The following items could not be synchronized.": "Die folgenden Objekte konnten nicht synchronisiert werden.",
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
@@ -225,16 +230,16 @@
"Version": "Version",
"Versions Path": "Versionierungspfad",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Dateiversionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die angegebene Höchstzahl an Dateien erreicht ist.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warnung, dieser Pfad ist ein Unterverzeichnis des existierenden Verzeichnisses \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warnung, dieser Pfad ist ein Unterordner des existierenden Ordners \"{{otherFolder}}\".",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf den anderen Geräten hinzugefügt werden muss.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beachte bitte beim Hinzufügen eines neuen Verzeichnisses, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
"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",
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
"days": "Tage",
"directories": "Verzeichnisse",
"directories": "Ordner",
"files": "Dateien",
"full documentation": "Komplette Dokumentation",
"items": "Objekte",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte das Verzeichnis \"{{folderlabel}}\" ({{folder}}) teilen."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte den Ordner \"{{folder}}\" teilen.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte den Ordner \"{{folderlabel}}\" ({{folder}}) teilen."
}

View File

@@ -52,6 +52,8 @@
"Downloaded": "Έχει ληφθεί",
"Downloading": "Λήψη",
"Edit": "Επεξεργασία",
"Edit Device": "Επεξεργασία συσκευής",
"Edit Folder": "Επεξεργασία φακέλου",
"Editing": "Επεξεργασία σε εξέλιξη",
"Enable NAT traversal": "Ενεργοποίηση διάσχισης NAT",
"Enable Relaying": "Ενεργοποίηση αναμετάδοσης",
@@ -95,6 +97,7 @@
"Last Scan": "Τελευταία Σάρωση",
"Last seen": "Τελευταία φορά συνδεδεμένος",
"Later": "Αργότερα",
"Latest Change": "Τελευταία αλλαγή",
"Listeners": "Ακροατές",
"Local Discovery": "Τοπική ανεύρεση",
"Local State": "Τοπική κατάσταση",
@@ -153,6 +156,8 @@
"Scanning": "Έλεγχος για αλλαγές",
"Select the devices to share this folder with.": "Διάλεξε τις συσκευές προς τις οποίες θα διαμοιράζεται αυτός ο φάκελος.",
"Select the folders to share with this device.": "Διάλεξε ποιοι φάκελοι θα διαμοιράζονται προς αυτή τη συσκευή.",
"Send & Receive": "Αποστολή και λήψη",
"Send Only": "Μόνο αποστολή",
"Settings": "Ρυθμίσεις",
"Share": "Διαμοίραση",
"Share Folder": "Διαμοίραση φακέλου",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Edit": "Edit",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Editing",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "Last seen",
"Later": "Later",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
@@ -153,6 +156,8 @@
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Settings",
"Share": "Share",
"Share Folder": "Share Folder",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Edit": "Edit",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Editing",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "Last seen",
"Later": "Later",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
@@ -153,6 +156,8 @@
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Send \u0026 Receive": "Send \u0026 Receive",
"Send Only": "Send Only",
"Settings": "Settings",
"Share": "Share",
"Share Folder": "Share Folder",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Descargado",
"Downloading": "Descargando",
"Edit": "Editar",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Editando",
"Enable NAT traversal": "Permitir NAT transversal",
"Enable Relaying": "Habilitar Retransmisión",
@@ -95,6 +97,7 @@
"Last Scan": "Último escaneo",
"Last seen": "Visto por última vez",
"Later": "Más tarde",
"Latest Change": "Latest Change",
"Listeners": "Oyentes",
"Local Discovery": "Descubrimiento local",
"Local State": "Estado local",
@@ -153,6 +156,8 @@
"Scanning": "Analizando",
"Select the devices to share this folder with.": "Selecciona los dispositivos con los que compartir esta carpeta.",
"Select the folders to share with this device.": "Selecciona las carpetas para compartir con este dispositivo.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Ajustes",
"Share": "Compartir",
"Share Folder": "Compartir carpeta",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Descargado",
"Downloading": "Descargando",
"Edit": "Editar",
"Edit Device": "Cambiando dispositivo",
"Edit Folder": "Cambiando repositorio",
"Editing": "Editando",
"Enable NAT traversal": "Habilitar NAT trasversal",
"Enable Relaying": "Habilitar Retransmisión",
@@ -95,6 +97,7 @@
"Last Scan": "Último escaneo",
"Last seen": "Visto por ultima vez",
"Later": "Más tarde",
"Latest Change": "Último cambio",
"Listeners": "Receptor",
"Local Discovery": "Búsqueda en red local",
"Local State": "Estado local",
@@ -153,6 +156,8 @@
"Scanning": "Actualización",
"Select the devices to share this folder with.": "Seleccione los dispositivos con los cuales compartir este repositorio.",
"Select the folders to share with this device.": "Seleccione los repositorios para compartir con este dispositivo.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Configuración",
"Share": "Compartir",
"Share Folder": "Compartir repositorio",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Telekargatua",
"Downloading": "Deskargatze",
"Edit": "Aldatu",
"Edit Device": "Aldaketa tresna",
"Edit Folder": "Aldaketa partekatze",
"Editing": "Aldaketa",
"Enable NAT traversal": "Ahalbidetu NAT",
"Enable Relaying": "Ahalbidetu lekua hartu",
@@ -95,6 +97,7 @@
"Last Scan": "Azterketa azken",
"Last seen": "Azken agerraldia",
"Later": "Berantago",
"Latest Change": "Asken aldaketa",
"Listeners": "Entzungailuak",
"Local Discovery": "Lekuko aurkikuntza",
"Local State": "Lekuko egoera",
@@ -123,7 +126,7 @@
"Out of Sync Items": "Ez sinkronizatu elementuak",
"Outgoing Rate Limit (KiB/s)": "Bidaltze emari gehieneko (KiB/s)",
"Override Changes": "Aldaketak desegin",
"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": "Bertako tresnaren karpetari buruzko bidea. Ez balin bada, asmatu beharko da bat. Tildea (~, edo ~+Espazioa Windows XP+Azerty-n) erabil litzateke bide motz gisa.",
"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": "Bertako tresnaren karpetari buruzko bidea. Ez balin bada, asmatu beharko da bat. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Tildea (~, edo ~+Espazioa Windows XP+Azerty-n) erabil litzateke bide motz gisa",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kopiak kontserbatzeko bidea (hutsa utzezazu, .stversioen karpetaren bide ohituan).",
"Pause": "Pausa",
"Paused": "Geldirik",
@@ -153,6 +156,8 @@
"Scanning": "Etengabeko azterketa",
"Select the devices to share this folder with.": "Honekin sinkronizatua:",
"Select the folders to share with this device.": "Tresna hunek erabiltzen dituen banaketak hauta",
"Send & Receive": "Igorri & Artu",
"Send Only": "Bakarrik igorri",
"Settings": "Egokitzek",
"Share": "Banatu",
"Share Folder": "Banatu",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Ladattu",
"Downloading": "Ladataan",
"Edit": "Muokkaa",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Muokkaus",
"Enable NAT traversal": "Aktivoi osoitteenmuunnoksen kierto",
"Enable Relaying": "Aktivoi yhteyden välitys",
@@ -95,6 +97,7 @@
"Last Scan": "Viimeisin skannaus",
"Last seen": "Nähty viimeksi",
"Later": "Myöhemmin",
"Latest Change": "Latest Change",
"Listeners": "Kuuntelijat",
"Local Discovery": "Paikallinen etsintä",
"Local State": "Paikallinen tila",
@@ -153,6 +156,8 @@
"Scanning": "Skannataan",
"Select the devices to share this folder with.": "Valitse laitteet, joiden kanssa tämä kansio jaetaan.",
"Select the folders to share with this device.": "Valitse kansiot jaettavaksi tämän laitteen kanssa.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Asetukset",
"Share": "Jaa",
"Share Folder": "Jaa kansio",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Téléchargé",
"Downloading": "Téléchargement",
"Edit": "Modifier",
"Edit Device": "Modifier l'appareil",
"Edit Folder": "Modifier le partage",
"Editing": "Modifications",
"Enable NAT traversal": "Activer transfert d'adresses NAT",
"Enable Relaying": "Activer le relayage",
@@ -91,10 +93,11 @@
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
"Keep Versions": "Combien de versions conserver",
"Largest First": "Les plus volumineux d'abord",
"Last File Received": "Dernière mise à jour",
"Last File Received": "Dernier changement",
"Last Scan": "Dernière analyse",
"Last seen": "Dernière apparition",
"Later": "Plus tard",
"Latest Change": "Dernier changement",
"Listeners": "Systèmes à l'écoute",
"Local Discovery": "Découverte locale",
"Local State": "État local",
@@ -123,7 +126,7 @@
"Out of Sync Items": "Éléments non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
"Override Changes": "Écraser les changements",
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
"Pause": "Pause",
"Paused": "En pause",
@@ -153,6 +156,8 @@
"Scanning": "Analyse en cours",
"Select the devices to share this folder with.": "Synchroniser avec :",
"Select the folders to share with this device.": "Sélectionner les partages auxquels participe cet appareil.",
"Send & Receive": "Envoi & réception",
"Send Only": "Envoi seulement",
"Settings": "Configuration",
"Share": "Partager",
"Share Folder": "Partager",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Téléchargé",
"Downloading": "Téléchargement",
"Edit": "Modifier",
"Edit Device": "Modifier l'appareil",
"Edit Folder": "Modifier le partage",
"Editing": "Modifications",
"Enable NAT traversal": "Activer la translation d'adresses (NAT)",
"Enable Relaying": "Relayage possible",
@@ -91,10 +93,11 @@
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
"Keep Versions": "Combien de versions conserver",
"Largest First": "Les plus volumineux d'abord",
"Last File Received": "Dernière mise à jour",
"Last File Received": "Dernier changement",
"Last Scan": "Dernière analyse",
"Last seen": "Dernière apparition",
"Later": "Plus tard",
"Latest Change": "Dernier changement",
"Listeners": "Systèmes en écoute",
"Local Discovery": "Découverte locale",
"Local State": "État local",
@@ -123,7 +126,7 @@
"Out of Sync Items": "Éléments non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit d'émission (Ko/s)",
"Override Changes": "Écraser les changements",
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les copies doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
"Pause": "Pause",
"Paused": "En pause",
@@ -153,6 +156,8 @@
"Scanning": "Analyse en cours",
"Select the devices to share this folder with.": "Synchroniser avec :",
"Select the folders to share with this device.": "Sélectionner les partages auxquels participe cet appareil.",
"Send & Receive": "Envoyer & recevoir",
"Send Only": "Envoyer seulement",
"Settings": "Configuration",
"Share": "Partager",
"Share Folder": "Partager",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Ynladen",
"Downloading": "Oan it ynladen",
"Edit": "Bewurkje",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Bewurkjen",
"Enable NAT traversal": "NAT-trochkruse ynskeakelje",
"Enable Relaying": "Trochjaan tastean",
@@ -95,6 +97,7 @@
"Last Scan": "Lêst Skent",
"Last seen": "Lêst sjoen",
"Later": "Letter",
"Latest Change": "Latest Change",
"Listeners": "Harkers",
"Local Discovery": "Lokale ûntdekking",
"Local State": "Lokale tastân",
@@ -153,6 +156,8 @@
"Scanning": "Oan it skennen",
"Select the devices to share this folder with.": "Sykje de apparaten út om dizze map mei te dielen.",
"Select the folders to share with this device.": "Sykje de mappen út om mei dit apparaat te dielen.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Ynstellings",
"Share": "Diele",
"Share Folder": "Map diele",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Letöltve",
"Downloading": "Letöltés",
"Edit": "Szerkesztés",
"Edit Device": "Eszköz szerkesztése",
"Edit Folder": "Mappa szerkesztése",
"Editing": "Szerkesztés",
"Enable NAT traversal": "NAT bejárás engedélyezése",
"Enable Relaying": "Közvetítés engedélyezése",
@@ -95,6 +97,7 @@
"Last Scan": "Utolsó vizsgálat",
"Last seen": "Utoljára látva",
"Later": "Később",
"Latest Change": "Utolsó módosítás",
"Listeners": "Kapcsolatok",
"Local Discovery": "Helyi felfedezés",
"Local State": "Helyi állapot",
@@ -153,6 +156,8 @@
"Scanning": "Átnézés",
"Select the devices to share this folder with.": "Válaszd ki az eszközöket, amelyekkel meg szeretnéd osztani a mappát",
"Select the folders to share with this device.": "Válaszd ki a mappákat, amiket meg szeretnél osztani ezzel az eszközzel.",
"Send & Receive": "Küldés és fogadás",
"Send Only": "Csak küldés",
"Settings": "Beállítások",
"Share": "Megosztás",
"Share Folder": "Mappa megosztása",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Terunduh",
"Downloading": "Mengunduh",
"Edit": "Sunting",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Menyunting",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Aktifkan Relay",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "Last seen",
"Later": "Later",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
@@ -153,6 +156,8 @@
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Settings",
"Share": "Share",
"Share Folder": "Share Folder",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Scaricato",
"Downloading": "Scaricamento in corso",
"Edit": "Modifica",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Modifica di",
"Enable NAT traversal": "Abilita NAT traversal",
"Enable Relaying": "Abilita Reindirizzamento",
@@ -92,10 +94,11 @@
"Keep Versions": "Versioni Mantenute",
"Largest First": "Prima il più grande",
"Last File Received": "Ultimo File Ricevuto",
"Last Scan": "Ultima scansione",
"Last Scan": "Ultima Scansione",
"Last seen": "Ultima connessione",
"Later": "Più Tardi",
"Listeners": "In ascolto",
"Latest Change": "Ultima Modifica",
"Listeners": "In Ascolto",
"Local Discovery": "Individuazione Locale",
"Local State": "Stato Locale",
"Local State (Total)": "Stato Locale (Totale)",
@@ -153,6 +156,8 @@
"Scanning": "Scansione in corso",
"Select the devices to share this folder with.": "Seleziona i dispositivi con i quali condividere questa cartella.",
"Select the folders to share with this device.": "Seleziona le cartelle da condividere con questo dispositivo.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Impostazioni",
"Share": "Condividi",
"Share Folder": "Condividi la Cartella",
@@ -220,7 +225,7 @@
"Upgrade To {%version%}": "Aggiorna alla {{version}}",
"Upgrading": "Aggiornamento",
"Upload Rate": "Velocità Upload",
"Uptime": "Tempo di funzionamento",
"Uptime": "Tempo di Funzionamento",
"Use HTTPS for GUI": "Utilizza HTTPS per l'interfaccia grafica",
"Version": "Versione",
"Versions Path": "Percorso Cartella Versioni",

View File

@@ -31,7 +31,7 @@
"Command": "コマンド",
"Comment, when used at the start of a line": "行頭で使用された場合、コメント行",
"Compression": "圧縮",
"Configured": "Configured",
"Configured": "設定値",
"Connection Error": "接続エラー",
"Connection Type": "接続種別",
"Copied from elsewhere": "別ファイルからコピー済",
@@ -45,13 +45,15 @@
"Device Name": "デバイス名",
"Devices": "デバイス",
"Disconnected": "切断中",
"Discovered": "Discovered",
"Discovered": "探索結果",
"Discovery": "探索サーバー",
"Documentation": "マニュアル",
"Download Rate": "ダウンロード速度",
"Downloaded": "ダウンロード済",
"Downloading": "ダウンロード中",
"Edit": "編集",
"Edit Device": "デバイスの編集",
"Edit Folder": "フォルダーの編集",
"Editing": "編集中",
"Enable NAT traversal": "NATトラバーサルを有効にする",
"Enable Relaying": "中継サーバー経由の通信を有効にする",
@@ -95,6 +97,7 @@
"Last Scan": "最終スキャン時刻",
"Last seen": "最終接続日時",
"Later": "後で設定",
"Latest Change": "最終更新内容",
"Listeners": "待ち受けポート",
"Local Discovery": "LAN内で探索",
"Local State": "ローカル状態",
@@ -135,7 +138,7 @@
"Quick guide to supported patterns": "サポートされているパターンの簡易ガイド",
"RAM Utilization": "メモリ使用量",
"Random": "ランダム",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Reduced by ignore patterns": "無視パターン該当分を除く",
"Release Notes": "リリースノート",
"Remote Devices": "他のデバイス",
"Remove": "除去",
@@ -153,6 +156,8 @@
"Scanning": "スキャン中",
"Select the devices to share this folder with.": "このフォルダーを共有するデバイスを選択してください。",
"Select the folders to share with this device.": "このデバイスと共有するフォルダーを選択してください。",
"Send & Receive": "送受信",
"Send Only": "送信のみ",
"Settings": "設定",
"Share": "共有",
"Share Folder": "フォルダーを共有する",
@@ -231,8 +236,8 @@
"Yes": "はい",
"You must keep at least one version.": "少なくとも一つのバージョンを保存してください。",
"days": "日",
"directories": "directories",
"files": "files",
"directories": "ディレクトリ",
"files": "ファイル",
"full documentation": "詳細なマニュアル",
"items": "項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。",

View File

@@ -52,6 +52,8 @@
"Downloaded": "다운로드됨",
"Downloading": "다운로드 중",
"Edit": "편집",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "편집",
"Enable NAT traversal": "NAT traversal 활성화",
"Enable Relaying": "Relaying 활성화",
@@ -95,6 +97,7 @@
"Last Scan": "마지막 탐색",
"Last seen": "마지막 접속",
"Later": "나중에",
"Latest Change": "최신 변경",
"Listeners": "수신자",
"Local Discovery": "로컬 노드 검색",
"Local State": "로컬 상태",
@@ -153,6 +156,8 @@
"Scanning": "탐색중",
"Select the devices to share this folder with.": "이 폴더를 공유할 장치를 선택합니다.",
"Select the folders to share with this device.": "이 장치와 공유할 폴더를 선택합니다.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "설정",
"Share": "공유",
"Share Folder": "폴더 공유",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Parsisiųstas",
"Downloading": "Siunčiama",
"Edit": "Redaguoti",
"Edit Device": "Redaguoti įrenginį",
"Edit Folder": "Redaguoti aplanką",
"Editing": "Redagavimas",
"Enable NAT traversal": "Leisti kirsti NAT",
"Enable Relaying": "Įjungti retransliavimą",
@@ -82,7 +84,7 @@
"Global State": "Visuotinė būsena",
"Help": "Pagalba",
"Home page": "Pagrindinis puslapis",
"Ignore": "Ignoruoti",
"Ignore": "Nepaisyti",
"Ignore Patterns": "Nepaisyti šablonų",
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
"Incoming Rate Limit (KiB/s)": "Įeinančio srauto maksimalus greitis (KiB/s)",
@@ -95,6 +97,7 @@
"Last Scan": "Paskutinis nuskaitymas",
"Last seen": "Paskutinį kartą matytas",
"Later": "Vėliau",
"Latest Change": "Paskutinis pakeitimas",
"Listeners": "Klausytojai",
"Local Discovery": "Vietinis matomumas",
"Local State": "Vietinė būsena",
@@ -135,7 +138,7 @@
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
"RAM Utilization": "Atminties naudojimas",
"Random": "Atsitiktinė",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Reduced by ignore patterns": "Sumažinta pagal nepaisomus šablonus",
"Release Notes": "Laidos Informacija",
"Remote Devices": "Nuotoliniai įrenginiai",
"Remove": "Pašalinti",
@@ -153,6 +156,8 @@
"Scanning": "Skenuojama",
"Select the devices to share this folder with.": "Pasirinkite įrenginius, su kuriais dalinsitės šį aplanką.",
"Select the folders to share with this device.": "Pasirinkite aplankus kuriais norite dalintis su šiuo įrenginiu.",
"Send & Receive": "Siųsti ir gauti",
"Send Only": "Tik siųsti",
"Settings": "Nustatymai",
"Share": "Dalintis",
"Share Folder": "Dalintis aplanku",

View File

@@ -6,20 +6,20 @@
"About": "Om",
"Actions": "Handlinger",
"Add": "Legg til",
"Add Device": "Legg til Enhet",
"Add Folder": "Legg til Mappe",
"Add Device": "Legg til enhet",
"Add Folder": "Legg til mappe",
"Add Remote Device": "Legg til ekstern enhet",
"Add new folder?": "Legg til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"Advanced": "Avansert",
"Advanced Configuration": "Avanserte Innstillinger",
"Advanced Configuration": "Avanserte innstillinger",
"Advanced settings": "Avanserte innstillinger ",
"All Data": "Alle data",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
"Allow Anonymous Usage Reporting?": "Tillat anonym innsamling av brukerdata?",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the synced folder.": "En ekstern kommando håndterer versjonkontrollen. Den må fjerne filen fra den synkroniserte katalogen.",
"Anonymous Usage Reporting": "Anonym Innsamling Av Brukerdata",
"Anonymous Usage Reporting": "Anonym innsamling av brukerdata",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurert på en introduksjonsenhet vil også bli lagt til denne enheten.",
"Automatic upgrades": "Automatiske oppdateringer",
"Be careful!": "Vær forsiktig!",
@@ -31,7 +31,7 @@
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
"Compression": "Komprimering",
"Configured": "Configured",
"Configured": "Konfigurert",
"Connection Error": "Tilkoblingsfeil",
"Connection Type": "Tilkoblingstype",
"Copied from elsewhere": "Kopiert fra et annet sted",
@@ -42,23 +42,25 @@
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) ønsker å koble til. Legge til ny enhet?",
"Device ID": "Enhets ID",
"Device Identification": "Enhetskjennemerke",
"Device Name": "Navn på Enhet",
"Device Name": "Navn på enhet",
"Devices": "Enheter",
"Disconnected": "Frakoblet",
"Discovered": "Discovered",
"Discovered": "Oppdaget",
"Discovery": "Oppslag",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Lastet ned",
"Downloading": "Laster ned",
"Edit": "Rediger",
"Edit Device": "Rediger enhet",
"Edit Folder": "Rediger katalog",
"Editing": "Redigerer",
"Enable NAT traversal": "Slå på NAT traversering",
"Enable Relaying": "Aktiver relésending",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn kommaseparerte (\"tcp://ip:port\", \"tcp://host:port\") adresser, eller ordet \"dynamic\" for å gjøre automatisk oppslag for adressen.",
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelates, ett per linje.",
"Error": "Feilmelding",
"External File Versioning": "Ekstern Versjonskontroll",
"External File Versioning": "Ekstern versjonskontroll",
"Failed Items": "Elementsynkronisering som har feilet",
"File Pull Order": "Filenes Henterekkefølge",
"File Versioning": "Versjonskontroll",
@@ -79,39 +81,40 @@
"Generate": "Generer",
"Global Discovery": "Globalt oppslag",
"Global Discovery Servers": "Globale oppslagstjenere",
"Global State": "Global Tilstand",
"Global State": "Global tilstand",
"Help": "Hjelp",
"Home page": "Hjemmeside",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatelsesmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkommende Hastighetsbegrensning (KiB/s)",
"Incoming Rate Limit (KiB/s)": "Innkommende hastighetsbegrensning (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Feilaktige innstillinger kan skade innholdet i dine delte kataloger og hindre Syncthing i å fungere.",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikke ekskluder)",
"Keep Versions": "Behold Versjoner",
"Keep Versions": "Behold versjoner",
"Largest First": "Største fil",
"Last File Received": "Siste mottatte fil",
"Last Scan": "Siste gjennomsøking",
"Last seen": "Sist sett",
"Later": "Senere",
"Latest Change": "Sist endret",
"Listeners": "Lyttere",
"Local Discovery": "Lokalt oppslag",
"Local State": "Lokal Tilstand",
"Local State (Total)": "Lokal Tilstand (Total)",
"Major Upgrade": "Hovedoppgradering",
"Master": "Hoved",
"Maximum Age": "Maksimal Levetid",
"Local State": "Lokal tilstand",
"Local State (Total)": "Lokal tilstand (Total)",
"Major Upgrade": "Storoppgradering",
"Master": "Styrende",
"Maximum Age": "Maksimal levetid",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Nødvendig ledig diskplass",
"Move to top of queue": "Flytt fremst i køen",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
"New Device": "Ny Enhet",
"New Folder": "Ny Mappe",
"New Device": "Ny enhet",
"New Folder": "Ny mappe",
"Newest First": "Den nyeste først",
"No": "Nei",
"No File Versioning": "Ingen Versjonskontroll",
"No File Versioning": "Ingen versjonskontroll",
"Normal": "Normal",
"Notice": "Merknader",
"OK": "OK",
@@ -120,22 +123,22 @@
"Optional descriptive label for the folder. Can be different on each device.": "Valgfri merkelapp på katalogen. Denne kan være ulik på forskjellige enheter",
"Options": "Valg",
"Out of Sync": "Ikke synkronisert",
"Out of Sync Items": "Ikke Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgående Hastighetsbegrensning (KiB/s)",
"Override Changes": "Overstyr Endringer",
"Out of Sync Items": "Ikke synkroniserte element",
"Outgoing Rate Limit (KiB/s)": "Utgående hastighetsbegrensning (KiB/s)",
"Override Changes": "Overstyr endringer",
"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": "Plasseringen av mappen på datamaskinen. Denne vil bli opprettet dersom den ikke finnes. Krøllstrektegnet (~) kan brukes som forkortelse for",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringen for lagrede versjoner (la denne være tom for å bruke standard .stversions-mappen i mappen).",
"Pause": "Oppholde",
"Paused": "Oppholdt",
"Please consult the release notes before performing a major upgrade.": "Se \"release notes\" før en hovedoppgradering utføres.",
"Please consult the release notes before performing a major upgrade.": "Se \"release notes\" før en storoppgradering utføres.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Vennligst angi bruker og passord for GUI-autentisering i innstillingsvinduet.",
"Please wait": "Vennligst vent",
"Preview": "Forhåndsvisning",
"Preview Usage Report": "Forhåndsvisning Av Datainnsamling",
"Preview Usage Report": "Forhåndsvisning av datainnsamling",
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "RAM-utnyttelse",
"Random": "Tilfeldig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Reduced by ignore patterns": "Reduser med utelatelsesmønster",
"Release Notes": "Utgivelsesnotat",
"Remote Devices": "Andre enheter",
"Remove": "Fjern",
@@ -144,8 +147,8 @@
"Rescan All": "Gjennomsøk alt på nytt",
"Rescan Interval": "Intervall for gjennomsøking",
"Restart": "Omstart",
"Restart Needed": "Omstart Kreves",
"Restarting": "Starter På Ny",
"Restart Needed": "Omstart kreves",
"Restarting": "Starter på nytt",
"Resume": "Gjenoppta",
"Reused": "Gjenbrukt",
"Save": "Lagre",
@@ -153,29 +156,31 @@
"Scanning": "Gjennomsøker",
"Select the devices to share this folder with.": "Velg enhetene du vil dele denne mappen med.",
"Select the folders to share with this device.": "Velg hvilke mapper som skal deles med denne enheten.",
"Send & Receive": "Sende og motta",
"Send Only": "Bare sende",
"Settings": "Innstillinger",
"Share": "Del",
"Share Folder": "Del Mappe",
"Share Folders With Device": "Del Mapper Med Enhet",
"Share With Devices": "Del Med Enheter",
"Share Folder": "Del mappe",
"Share Folders With Device": "Del mapper med enhet",
"Share With Devices": "Del med enheter",
"Share this folder?": "Dele denne mappen?",
"Shared With": "Delt med",
"Show ID": "Vis ID",
"Show QR": "Vis QR kode",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vis i stedet for Eining ID i gruppestatus. Vil bli kringkastet til andre enheter som et valgfritt standardnavn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i stedet for Mappe-ID i gruppestatus. Vil bli oppdatert til navnet enheten kringkaster dersom tomt.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vis i stedet for enhets ID i gruppestatus. Vil bli kringkastet til andre enheter som et valgfritt standardnavn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i stedet for mappe ID i gruppestatus. Vil bli oppdatert til navnet enheten kringkaster dersom tomt.",
"Shutdown": "Avslutt",
"Shutdown Complete": "Avslutning fullført",
"Simple File Versioning": "Enkel Versjonskontroll",
"Simple File Versioning": "Enkel versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søker kun i en mappe)",
"Smallest First": "Den minste først",
"Source Code": "Kildekode",
"Staggered File Versioning": "Forskjøvet Versjonskontroll",
"Start Browser": "Start Nettleser",
"Start Browser": "Start nettleser",
"Statistics": "Statistikk",
"Stopped": "Stoppa",
"Support": "Brukerstøtte",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Sync Protocol Listen Addresses": "Lytteadresse for synkroniseringsprotokoll",
"Syncing": "Synkroniserer",
"Syncthing has been shut down.": "Syncthing har blitt slått av.",
"Syncthing includes the following software or portions thereof:": "Syncthing inkluderer helt eller delvis følgende programvare:",
@@ -209,21 +214,21 @@
"They are retried automatically and will be synced when the error is resolved.": "Disse hentes automatisk og vil synkroniseres når feilen er blitt utbedret.",
"This Device": "Denne enheten",
"This can easily give hackers access to read and change any files on your computer.": "Dette kan lett gi hackere tilgang til å lese og endre alle filer på datamaskinen din.",
"This is a major version upgrade.": "Dette er en hovedoppgradering",
"Trash Can File Versioning": "Papirkurv Versjonskontroll",
"This is a major version upgrade.": "Dette er en storoppgradering",
"Trash Can File Versioning": "Papirkurv versjonskontroll",
"Unknown": "Ukjent",
"Unshared": "Ikke delt",
"Unused": "Ikke i bruk",
"Up to Date": "Oppdatert",
"Updated": "Oppdatert",
"Upgrade": "Oppgradere",
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrade To {%version%}": "Oppgrader til {{version}}",
"Upgrading": "Oppgraderer",
"Upload Rate": "Opplastingsrate",
"Uptime": "Oppetid",
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
"Version": "Versjon",
"Versions Path": "Plassering Av Versjoner",
"Versions Path": "Plassering av versjoner",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versjoner blir automatisk slettet når maksimal levetid er nådd eller når antall filer er oversteget.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel, denne stien er en underkatalog i en eksisterende katalog \"{{otherFolder}}\".",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Merk at når en ny enhet blir lagt til må denne også legges til på andre siden.",
@@ -231,8 +236,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Du må beholde minst én versjon",
"days": "dager",
"directories": "directories",
"files": "files",
"directories": "kataloger",
"files": "filer",
"full documentation": "all dokumentasjon",
"items": "elementer",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\".",

View File

@@ -31,7 +31,7 @@
"Command": "Commando",
"Comment, when used at the start of a line": "Reageer indien gebruikt aan het begin van een lijn.",
"Compression": "Compressie",
"Configured": "Configured",
"Configured": "Geconfigureerd",
"Connection Error": "Verbindingsfout",
"Connection Type": "Soort verbinding",
"Copied from elsewhere": "Gekopieerd vanaf elders",
@@ -45,13 +45,15 @@
"Device Name": "Naam apparaat",
"Devices": "Apparaten",
"Disconnected": "Niet verbonden",
"Discovered": "Discovered",
"Discovered": "Ontdekt",
"Discovery": "Zoeken",
"Documentation": "Documentatie",
"Download Rate": "Downloadsnelheid",
"Downloaded": "Gedownload",
"Downloading": "Bezig met downloaden",
"Edit": "Bewerk",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Bezig met bewerken",
"Enable NAT traversal": "Activeer NAT traversal",
"Enable Relaying": "Activeer doorsturen",
@@ -95,6 +97,7 @@
"Last Scan": "Laatste scan",
"Last seen": "Laatst gezien op",
"Later": "Later",
"Latest Change": "Meest recente wijziging",
"Listeners": "Luisteraars",
"Local Discovery": "Lokaal zoeken",
"Local State": "Lokale status",
@@ -135,7 +138,7 @@
"Quick guide to supported patterns": "Snelgids voor ondersteunde patronen",
"RAM Utilization": "Geheugengebruik",
"Random": "Willekeurig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Reduced by ignore patterns": "Verminderd door negeerpatronen",
"Release Notes": "Release notes",
"Remote Devices": "Externe apparaten",
"Remove": "Verwijderen",
@@ -153,6 +156,8 @@
"Scanning": "Aan het zoeken",
"Select the devices to share this folder with.": "Selecteer de apparaten om deze map mee te delen.",
"Select the folders to share with this device.": "Selecteer de mappen om met dit apparaat te delen.",
"Send & Receive": "Verzenden & Ontvangen",
"Send Only": "Alleen verzenden",
"Settings": "Instellingen",
"Share": "Delen",
"Share Folder": "Deel map",
@@ -231,8 +236,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
"days": "dagen",
"directories": "directories",
"files": "files",
"directories": "Mappen",
"files": "Bestanden",
"full documentation": "volledige documentatie",
"items": "objecten",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de map \"{{folder}}\" delen.",

View File

@@ -1,6 +1,6 @@
{
"A device with that ID is already added.": "Ein eining med den ID er allerede lagt til.",
"A negative number of days doesn't make sense.": "Eit negativt tal dagar har ikkje meining.",
"A device with that ID is already added.": "Ei eining med den ID-en er allereie lagt til.",
"A negative number of days doesn't make sense.": "Eit negativt tal dagar gir ikkje meining.",
"A new major version may not be compatible with previous versions.": "Ein ny hovudversjon er ikkje nødvendigvis kompatibel med eldre versjonar. ",
"API Key": "API-nøkkel",
"About": "Om",
@@ -8,17 +8,17 @@
"Add": "Legg til",
"Add Device": "Legg Til Eining",
"Add Folder": "Legg Til Mappe",
"Add Remote Device": "Legg Til Ekstern Eining",
"Add Remote Device": "Legg til ekstern eining",
"Add new folder?": "Leggja til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"Advanced": "Avansert",
"Advanced Configuration": "Avansert konfigurasjon",
"Advanced settings": "Avansert innstillingar",
"Advanced settings": "Avanserte innstillingar",
"All Data": "Alle data",
"Allow Anonymous Usage Reporting?": "Tillata anonymisert bruksrapportering?",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein ekstern kommando håndterer filutgåver. Den må sørge for at fila blir fjerna frå den synkroniserte mappa.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein ekstern kommando handterer filutgåver. Den må sørga for at fila blir fjerna frå den synkroniserte mappa.",
"Anonymous Usage Reporting": "Anonymisert bruksrapportering",
"Any devices configured on an introducer device will be added to this device as well.": "Einingar konfigurert på ei introduksjonseining vil òg verta lagt til denne eininga.",
"Automatic upgrades": "Automatiske oppdateringar",
@@ -31,7 +31,7 @@
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, når brukt i starten av linja",
"Compression": "Komprimering",
"Configured": "Configured",
"Configured": "Konfigurert",
"Connection Error": "Tilkoplingsfeil",
"Connection Type": "Tilkoplingstype",
"Copied from elsewhere": "Kopiert frå ein annan stad",
@@ -39,36 +39,38 @@
"Copyright © 2014-2016 the following Contributors:": "Opphavsrett © 2014-2016 for følgjande bidragsyterar:",
"Danger!": "Fare!",
"Deleted": "Sletta",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Eininga \"{{name}}\" {{device}} ({{address}}) vil kopla seg til. Vil du leggja ho til?",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Eininga «{{name}}» {{device}} ({{address}}) vil kopla seg til. Vil du leggja ho til?",
"Device ID": "Eining ID",
"Device Identification": "Einingskjennemerke",
"Device Name": "Namn På Eining",
"Devices": "Einingar",
"Disconnected": "Fråkopla",
"Discovered": "Discovered",
"Discovered": "Oppdaga",
"Discovery": "Oppdaging",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsfart",
"Downloaded": "Lasta ned",
"Downloading": "Lastar ned",
"Edit": "Rediger",
"Edit Device": "Rediger enhet",
"Edit Folder": "Rediger katalog",
"Editing": "Redigerer",
"Enable NAT traversal": "Slå på NAT-gjennomgang",
"Enable Relaying": "Aktiver Reléer",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn adresser med komma mellom kvar adresse (\"tcp://ip:port\", \"tcp://host:port\"), eller \"dynamic\" for å automatisk søkja opp adressa.",
"Enable Relaying": "Aktiver relé",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn adresser med komma mellom kvar adresse («tcp://ip:port», «tcp://host:port»), eller «dynamic» for å automatisk søkja opp adressa.",
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelatast, eitt per linje.",
"Error": "Feilmelding",
"External File Versioning": "Ekstern filutgåvehandtering",
"Failed Items": "Feilande element",
"File Pull Order": "Henterekkefølge for filer",
"File Versioning": "Filutgåvekontroll",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Informasjon om filrettar vert ignorert når det blir leita etter endringar. Bruk på FAT filsystem. ",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Informasjon om filrettar vert ignorert når det blir leita etter endringar. Bruk på FAT-filsystem. ",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer som Syncthing slettar eller skriv over vert flytta til katalogen .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer som Syncthing oppdaterar eller sletter vert flytta til ein datostempla versjon i .stversions-katalogen.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer som Syncthing oppdaterer eller slettar vert flytta til ein datostempla versjon i .stversions-katalogen.",
"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 er beskytta mot endringar gjort på andre einingar, men endringar gjort på denne eininga vert sende til resten av klyngja.",
"Folder": "Mappe",
"Folder ID": "Mappe ID",
"Folder Label": "Merkelapp for Mappe",
"Folder Label": "Merkelapp for mappa",
"Folder Path": "Mappeplassering",
"Folder Type": "Mappetype",
"Folders": "Mapper",
@@ -78,7 +80,7 @@
"GUI Listen Addresses": "GUI Lytteadresse",
"Generate": "Generer",
"Global Discovery": "Global søking",
"Global Discovery Servers": "Globalsøkjetenarar",
"Global Discovery Servers": "Globale søkjetenarar",
"Global State": "Global Tilstand",
"Help": "Hjelp",
"Home page": "Heimeside",
@@ -86,15 +88,16 @@
"Ignore Patterns": "Utelatingsmønster",
"Ignore Permissions": "Ignorer tilgangar",
"Incoming Rate Limit (KiB/s)": "Innkomande hastigheitsgrense (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Feil innstillingar kan skade innhaldet i dine delte katalogar og hindre Syncthing i å fungere.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Feil innstillingar kan skada innhaldet i dine delte katalogar og hindra Syncthing i å fungera.",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Det motsette av den gitte tilstanden (dvs. ekskluder ikkje)",
"Keep Versions": "Behald Versjonar",
"Largest First": "Største fyrst",
"Last File Received": "Siste mottatte fila",
"Last Scan": "Siste Skanning",
"Last Scan": "Siste skanning",
"Last seen": "Sist sett",
"Later": "Seinare",
"Latest Change": "Siste endringar",
"Listeners": "Lyttarar",
"Local Discovery": "Lokal oppdaging",
"Local State": "Lokal Tilstand",
@@ -111,35 +114,35 @@
"New Folder": "Ny mappe",
"Newest First": "Nyaste fyrst",
"No": "Nei",
"No File Versioning": "Ingen filutgåvehandtering",
"No File Versioning": "Inga filutgåvehandtering",
"Normal": "Normal",
"Notice": "Merknad",
"OK": "OK",
"Off": "Av",
"Oldest First": "Elste fyrst",
"Optional descriptive label for the folder. Can be different on each device.": "Valfri merkelapp på katalogen. Denne kan være ulik på andre enheter.",
"Oldest First": "Eldste fyrst",
"Optional descriptive label for the folder. Can be different on each device.": "Valfri merkelapp på katalogen. Denne kan vera ulik på andre einingar.",
"Options": "Val",
"Out of Sync": "Ikkje synkronisert",
"Out of Sync Items": "Ikkje-synkroniserte element",
"Outgoing Rate Limit (KiB/s)": "Utgåande hastigheitsgrense (KiB/s)",
"Override Changes": "Overstyr endringar",
"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": "Plasseringa av mappa på datamaskinen. Vert oppretta om ho ikkje finst. Krøllstrekteiknet (~) kan brukast som forkorting for",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringa for lagra versjonar (la denne vera tom for å bruka standard .stversions-mappa i mappa).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringa for lagra versjonar (la denne vera tom for å bruka standardmappa .stversions i mappa).",
"Pause": "Stans",
"Paused": "Stansa",
"Please consult the release notes before performing a major upgrade.": "Sjå \"release notes\" før ei hovudoppgradering vert utført.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ver vennleg å set ein GUI brukar og passord i Innstillingar-dialogen.",
"Please consult the release notes before performing a major upgrade.": "Sjå utgjevingsmerknadene før ei hovudoppgradering vert utført.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ver vennleg å laga ein GUI-brukar og eit passord i Innstillingar-dialogen.",
"Please wait": "Gjer vel og vent",
"Preview": "Førehandsvisning",
"Preview Usage Report": "Førehandsvis bruksrapporten",
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "Minnebruk",
"Random": "Tilfeldig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Utgivingsnotat",
"Remote Devices": "Eksterne Einingar",
"Reduced by ignore patterns": "Reduser med utelatelsesmønster",
"Release Notes": "Utgjevingsnotat",
"Remote Devices": "Eksterne einingar",
"Remove": "Fjern",
"Required identifier for the folder. Must be the same on all cluster devices.": "Påkrevd identifikator for katalogen. Denne må være lik på alle einingar i samme klynge.",
"Required identifier for the folder. Must be the same on all cluster devices.": "Påkravd identifikator for katalogen. Denne må vera lik på alle einingane i same klynge.",
"Rescan": "Skann På Ny",
"Rescan All": "Skann alle på nytt",
"Rescan Interval": "Skanneintervall",
@@ -149,10 +152,12 @@
"Resume": "Start",
"Reused": "Gjenbrukt",
"Save": "Lagre",
"Scan Time Remaining": "Gjenståande Skannetid",
"Scan Time Remaining": "Gjenståande skannetid",
"Scanning": "Skannar",
"Select the devices to share this folder with.": "Vel einingane du vil dela denne mappa med.",
"Select the folders to share with this device.": "Vel mappene du vil dela med denne eininga.",
"Send & Receive": "Sende og motta",
"Send Only": "Bare sende",
"Settings": "Innstillingar",
"Share": "Del",
"Share Folder": "Del mappe",
@@ -182,35 +187,35 @@
"Syncthing is restarting.": "Syncthing startar på ny.",
"Syncthing is upgrading.": "Syncthing oppgraderer.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ut til å vera nede, eller så er det eit problem med nettilkoplinga di. Prøvar på ny …",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ser ut til å ha støtt på eit problem under behandling av din førespurnad. Vær vennleg å oppfrisk nettlesaren eller start Syncthing på nytt om problemet vedvarer.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing sitt administreringsgrensesnitt er sett opp til å tillate ekstern tilgang uten passord.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ser ut til å ha støtt på eit problem under behandling av førespurnaden din. Ver vennleg å oppfriska nettlesaren eller starta Syncthing på nytt om problemet varer ved.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Administreringsgrensesnittet til Syncthing er sett opp til å tillata ekstern tilgang utan passord.",
"The aggregated statistics are publicly available at the URL below.": "Samla statistikk er opent tilgjengeleg på URL-en nedanfor.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Instillingane har blitt lagra men ikkje aktivert. Syncthing må starta på ny for å aktivera dei nye instillingane.",
"The device ID cannot be blank.": "Eining ID kan ikkje vera tom.",
"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).": "Einings-IDen som skal oppgis her kan hentast fram via \"Rediger > Vis ID\"-dialogboksen på den andre eininga. Mellomrom og bindestrek er valfritt (blir ignorert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterte bruksrapporten vert send dagleg. Han vert nytta til å spora vanlege plattformer, mappestorleikar og programutgåvene. Om datasettet endrar seg, vil dette meldingsvindauget dukka opp att og du vil verta beden om å godkjenna det.",
"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).": "Einings-ID-en som skal oppgjevast her kan hentast fram via menyvalet «Handlingar > Vis ID» på den andre eininga. Mellomrom og bindestrek er valfritt (blir ignorert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterte bruksrapporten vert send dagleg. Han vert nytta til å spora vanlege plattformer, mappestorleikar og programutgåvene. Om datasettet endrar seg, vil dette meldingsvindauget dukka opp att, og du vil verta beden om å godkjenna det.",
"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.": "Einings-ID-en er ikkje gyldig. Han må vera på 52 eller 56 teikn og vera samansett av bokstavar og tal med valfrie mellomrom og bindestrekar.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den første kommandolinjeparameteren er mappebana og den andre syner den relative bana i mappa.",
"The folder ID cannot be blank.": "Mappe ID kan ikkje vera tom.",
"The folder ID must be unique.": "Mappe ID må vera unik.",
"The folder path cannot be blank.": "Mappeplasseringa kan ikkje vera 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.": "Desse intervalla vert nytta: den fyrste timen vert ei utgåve lagra kvart 30. sekund, den fyrste dagen vert ei utgåve lagra kvar time, dei fyrste 30 dagane vert ei utgåve lagra kvar dag, og inntil høgaste alderen vert ei utgåve lagra kvar veke.",
"The following items could not be synchronized.": "Fyljande filer kunne ikkje synkroniserast.",
"The following items could not be synchronized.": "Fylgjande filer kunne ikkje synkroniserast.",
"The maximum age must be a number and cannot be blank.": "Maksimal levetid må vera eit tal og kan ikkje vera tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Høgaste tidsrom å behalda ei utgåve (i dagar, set til 0 for å behalda versjonane for alltid).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Nødvendig ledig diskplass må vere eit tal mellom 0 og 100.",
"The number of days must be a number and cannot be blank.": "Tal på dagar må ver eit tal og kan ikkje vera tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Høgaste tidsrom å spara på ei utgåve (i dagar; set til 0 for å spara på versjonane for alltid).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Nødvendig ledig diskplass må vera eit tal mellom 0 og 100.",
"The number of days must be a number and cannot be blank.": "Tal på dagar må vera eit tal, og kan ikkje vera tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "Antall dagar å behalda filer i søppelkorga. Null betyr for alltid.",
"The number of old versions to keep, per file.": "Tal på gamle versjonar ein skal behalda, per fil.",
"The number of versions must be a number and cannot be blank.": "Tal på versjonar må vera eit tal og kan ikkje vera tomt.",
"The path cannot be blank.": "Bana kan ikkje vera tom.",
"The rate limit must be a non-negative number (0: no limit)": "Hastigheitsgrensa må ver eit positivt tall (0: ingen grensa)",
"The rate limit must be a non-negative number (0: no limit)": "Hastigheitsgrensa må vera eit positivt tall (0: inga grense)",
"The rescan interval must be a non-negative number of seconds.": "Talet på sekund i skanneintervallet kan ikkje vera negativt.",
"They are retried automatically and will be synced when the error is resolved.": "Desse vil bli prøvd på nytt automatisk og vil bli synkronisert når feilen har blitt utbetra.",
"This Device": "Denne Eininga",
"This can easily give hackers access to read and change any files on your computer.": "Dette kan lett gje dataekspertar tilgang til å lese og endre vilkårlege filer på denne maskina.",
"This Device": "Denne eininga",
"This can easily give hackers access to read and change any files on your computer.": "Dette kan lett gje datasnokar tilgang til å lesa og endra vilkårlege filer på denne maskina.",
"This is a major version upgrade.": "Dette er ei hovudoppgradering",
"Trash Can File Versioning": "Papirkorg filutgåvehandtering",
"Trash Can File Versioning": "Papirkorg-filutgåvehandtering",
"Unknown": "Ukjent",
"Unshared": "Ikkje delt",
"Unused": "Ubrukt",
@@ -225,16 +230,16 @@
"Version": "Versjon",
"Versions Path": "Utgåvebane",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Utgåver vert automatisk sletta når maksimal levetid er nådd eller når det høgaste tillate talet på filer innan eit intervall vert overskride.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Åtvaring, denne banen er ei undermappe av den eksisterande mappa \"{{otherFolder}}\".",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Hugs at når ei ny eining vert lagt til må ho òg leggjast til på andre sida.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Åtvaring, denne bana er ei undermappe av den eksisterande mappa «{{otherFolder}}».",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Hugs at når ei ny eining vert lagt til, må ho òg leggjast til på andre sida.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Hugs at når ei ny mappe vert lagt til, vert mappe-ID-en brukt til å binda saman mappene mellom einingane. Det er skilnad på store og små bokstavar, så ID-ane må vera identiske på alle einingane.",
"Yes": "Ja",
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"days": "dagar",
"directories": "directories",
"files": "files",
"directories": "katalogar",
"files": "filer",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønskjer å dela mappa \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønskjer å dela mappa \"{{folderLabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønskjer å dela mappa «{{folderLabel}}» ({{folder}})."
}

View File

@@ -52,6 +52,8 @@
"Downloaded": "Pobrane",
"Downloading": "Pobieranie",
"Edit": "Edytuj",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Edytowanie",
"Enable NAT traversal": "Włącz trawersowanie NAT",
"Enable Relaying": "Włącz przekazywanie",
@@ -95,6 +97,7 @@
"Last Scan": "Czas ostatniego skanu",
"Last seen": "Ostatnio widziany",
"Later": "Później",
"Latest Change": "Latest Change",
"Listeners": "Nasłuchujący",
"Local Discovery": "Lokalne odnajdywanie",
"Local State": "Status lokalny",
@@ -153,6 +156,8 @@
"Scanning": "Skanowanie",
"Select the devices to share this folder with.": "Wybierz urządzenie, któremu udostępnić folder.",
"Select the folders to share with this device.": "Wybierz foldery do współdzielenia z tym urządzeniem.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Ustawienia",
"Share": "Udostępnij",
"Share Folder": "Udostępnij folder",

View File

@@ -31,7 +31,7 @@
"Command": "Comando",
"Comment, when used at the start of a line": "Comentário, se usado no início de uma linha",
"Compression": "Compressão",
"Configured": "Configured",
"Configured": "Configurado",
"Connection Error": "Erro de conexão",
"Connection Type": "Tipo da conexão",
"Copied from elsewhere": "Copiado de outro lugar",
@@ -45,13 +45,15 @@
"Device Name": "Nome do dispositivo",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovered": "Discovered",
"Discovered": "Descoberto",
"Discovery": "Descoberta",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
"Downloading": "Recebendo",
"Edit": "Editar",
"Edit Device": "Editar dispositivo",
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Enable NAT traversal": "Habilitar NAT",
"Enable Relaying": "Habilitar retransmissão",
@@ -95,6 +97,7 @@
"Last Scan": "Última verificação",
"Last seen": "Visto por último em",
"Later": "Depois",
"Latest Change": "Última mudança",
"Listeners": "Escutadores",
"Local Discovery": "Descoberta local",
"Local State": "Estado local",
@@ -135,7 +138,7 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatória",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Reduced by ignore patterns": "Reduzido por filtros",
"Release Notes": "Notas de lançamento",
"Remote Devices": "Dispositivos remotos",
"Remove": "Remover",
@@ -153,6 +156,8 @@
"Scanning": "Verificando",
"Select the devices to share this folder with.": "Selecione os dispositivos com os quais esta pasta será compartilhada.",
"Select the folders to share with this device.": "Selecione as pastas a serem compartilhadas com este dispositivo.",
"Send & Receive": "Enviar e receber",
"Send Only": "Somente enviar",
"Settings": "Configurações",
"Share": "Compartilhar",
"Share Folder": "Compartilhar pasta",
@@ -231,8 +236,8 @@
"Yes": "Sim",
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
"days": "dias",
"directories": "directories",
"files": "files",
"directories": "direrios",
"files": "arquivos",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\".",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Recebido",
"Downloading": "Recebendo",
"Edit": "Editar",
"Edit Device": "Editar dispositivo",
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Enable NAT traversal": "Activar travessia de NAT",
"Enable Relaying": "Permitir retransmissão",
@@ -95,6 +97,7 @@
"Last Scan": "Última verificação",
"Last seen": "Última vez que foi verificado",
"Later": "Mais tarde",
"Latest Change": "Última alteração",
"Listeners": "Auscultadores",
"Local Discovery": "Pesquisa local",
"Local State": "Estado local",
@@ -153,6 +156,8 @@
"Scanning": "Verificando",
"Select the devices to share this folder with.": "Seleccione os dispositivos com os quais vai partilhar esta pasta.",
"Select the folders to share with this device.": "Seleccione as pastas a partilhar com este dispositivo.",
"Send & Receive": "Enviar e receber",
"Send Only": "Enviar apenas",
"Settings": "Configurações",
"Share": "Partilhar",
"Share Folder": "Partilhar pasta",

View File

@@ -31,7 +31,7 @@
"Command": "Команда",
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
"Compression": "Сжатие",
"Configured": "Configured",
"Configured": "Сконфигурировано",
"Connection Error": "Ошибка подключения",
"Connection Type": "Тип соединения",
"Copied from elsewhere": "Скопировано из другого места",
@@ -45,13 +45,15 @@
"Device Name": "Имя устройства",
"Devices": "Устройства",
"Disconnected": "Нет соединения",
"Discovered": "Discovered",
"Discovered": "Обнаружено",
"Discovery": "Обнаружение",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
"Downloaded": "Загружено",
"Downloading": "Загрузка",
"Edit": "Редактировать",
"Edit Device": "Редактирование устройства",
"Edit Folder": "Редактирование папки",
"Editing": "Редактирование",
"Enable NAT traversal": "Включить NAT traversal",
"Enable Relaying": "Включить релеи",
@@ -95,6 +97,7 @@
"Last Scan": "Последнее сканирование",
"Last seen": "Был доступен",
"Later": "Позже",
"Latest Change": "Последнее изменение",
"Listeners": "Прослушиватель",
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
@@ -135,7 +138,7 @@
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
"RAM Utilization": "Использование памяти",
"Random": "Случайно",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Reduced by ignore patterns": "Уменьшено шаблонами игнорирования",
"Release Notes": "Примечания к выпуску",
"Remote Devices": "Удалённые устройства",
"Remove": "Удалить",
@@ -153,6 +156,8 @@
"Scanning": "Сканирование",
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
"Select the folders to share with this device.": "Выберите папки, которые будут доступны этому устройству.",
"Send & Receive": "Отправить и получить",
"Send Only": "Только отправить",
"Settings": "Настройки",
"Share": "Предоставить доступ",
"Share Folder": "Предоставить доступ к папке",
@@ -231,8 +236,8 @@
"Yes": "Да",
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"days": "дней",
"directories": "directories",
"files": "files",
"directories": "папок",
"files": "файлов",
"full documentation": "полная документация",
"items": "элементы",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой «{{folder}}».",

View File

@@ -41,8 +41,8 @@
"Deleted": "Borttaget",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
"Device ID": "Enhet-ID",
"Device Identification": "Enhet identifikation",
"Device Name": "Enhets namn",
"Device Identification": "Enhetsidentifikation",
"Device Name": "Enhetsnamn",
"Devices": "Enheter",
"Disconnected": "Frånkopplad",
"Discovered": "Upptäckt",
@@ -52,6 +52,8 @@
"Downloaded": "Hämtat",
"Downloading": "Hämtar",
"Edit": "Redigera",
"Edit Device": "Redigera enhet",
"Edit Folder": "Redigera katalog",
"Editing": "Redigerar",
"Enable NAT traversal": "Aktivera NAT traversering",
"Enable Relaying": "Aktivera reläa",
@@ -95,6 +97,7 @@
"Last Scan": "Senaste skanning",
"Last seen": "Senast sedd",
"Later": "Senare",
"Latest Change": "Senaste ändring",
"Listeners": "Lyssnare",
"Local Discovery": "Lokal annonsering",
"Local State": "Lokal status",
@@ -153,6 +156,8 @@
"Scanning": "Skannar",
"Select the devices to share this folder with.": "Ange enheterna som den här katalogen ska delas med.",
"Select the folders to share with this device.": "Välj kataloger att dela med den här enheten.",
"Send & Receive": "Skicka & ta emot",
"Send Only": "Skicka endast",
"Settings": "Inställningar",
"Share": "Dela",
"Share Folder": "Dela katalog",
@@ -181,14 +186,14 @@
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:",
"Syncthing is restarting.": "Syncthing startar om.",
"Syncthing is upgrading.": "Syncthing uppgraderas.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller finns det problem med din Internetanslutning. Försöker igen...",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd eller så är det problem med din Internetanslutning. Försöker igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing verkar ha drabbats av ett problem med behandlingen av din begäran. Uppdatera sidan eller starta om Syncthing om problemet kvarstår.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administratör gränssnittet är konfigurerat för att tillåta fjärrtillträde utan ett lösenord.",
"The aggregated statistics are publicly available at the URL below.": "Den aggregerade statistiken är offentligt tillgängliga på webbadressen nedan.",
"The aggregated statistics are publicly available at the URL below.": "Den aggregerade statistiken är offentligt tillgänglig på webbadressen nedan.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
"The device ID cannot be blank.": "Enhet-ID kan inte vara tomt.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhet-ID som behövs här kan du hitta i \"Åtgärder > Visa ID\"-dialogrutan på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datat som rapporteras ändras så kommer du att bli tillfrågad igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhet-ID verkar inte vara korrekt. Det ska vara en 52 eller 56 teckensträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den första kommandoparametern är sökvägen till mappen och den andra parametern är den relativa sökvägen i mappen.",
"The folder ID cannot be blank.": "Katalog-ID får inte vara tomt.",
@@ -215,7 +220,7 @@
"Unshared": "Inte delad",
"Unused": "Oanvänd",
"Up to Date": "Uppdaterad",
"Updated": "Uppdaterad",
"Updated": "Uppdaterade",
"Upgrade": "Uppgradering",
"Upgrade To {%version%}": "Uppgradera till {{version}}",
"Upgrading": "Uppgraderar",
@@ -223,8 +228,8 @@
"Uptime": "Drifttid",
"Use HTTPS for GUI": "Använd HTTPS för GUI",
"Version": "Version",
"Versions Path": "Versioner sökväg",
"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 sitt interval.",
"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.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en underkatalog till en befintlig katalog \"{{otherFolder}}\".",
"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 katalog, tänk på att katalog-ID knyter ihop kataloger mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",

View File

@@ -1,28 +1,28 @@
{
"A device with that ID is already added.": "Bu ID'yi taşıyan cihaz zaten eklendi.",
"A device with that ID is already added.": "Bu ID'yi taşıyan aygıt zaten eklendi.",
"A negative number of days doesn't make sense.": "Eksi gün sayısı mantıklı bir ifade değil.",
"A new major version may not be compatible with previous versions.": "Yeni birincil sürümler önceki sürümlerle uyumlu olmayabilir.",
"A new major version may not be compatible with previous versions.": "Yeni ana sürüm önceki sürümlerle uyumlu olmayabilir.",
"API Key": "API Anahtarı",
"About": "Hakkında",
"Actions": "Eylemler",
"Add": "Ekle",
"Add Device": "Cihaz Ekle",
"Add Device": "Aygıt Ekle",
"Add Folder": "Klasör Ekle",
"Add Remote Device": "Add Remote Device",
"Add Remote Device": "Uzak Aygıt Ekle",
"Add new folder?": "Yeni klasör ekle?",
"Address": "Adres",
"Addresses": "Adresler",
"Advanced": "Gelişmiş Düzey",
"Advanced Configuration": "Gelişmiş Yapılandırma",
"Advanced settings": "Advanced settings",
"All Data": "Bütün Veriler",
"Advanced settings": "Gelişmiş ayarlar",
"All Data": "Tüm Veriler",
"Allow Anonymous Usage Reporting?": "Anonim kullanımın raporlanmasına izin veriyor musun ?",
"Alphabetic": "Alfabetik",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Sürümlendirme işlemini harici bir komut yürütüyor. Dosyayı eşzamanlama klasöründen kaldırmak zorunda.",
"Anonymous Usage Reporting": "Anonim Kullanım Raporlama",
"Any devices configured on an introducer device will be added to this device as well.": "Tanıtıcı bir cihazda yapılandırılan cihazlar bu cihaza da eklenecektir.",
"Automatic upgrades": "Otomatik güncellemeler",
"Be careful!": "Dikkatli Ol!",
"Be careful!": "Dikkatli ol!",
"Bugs": "Hatalar",
"CPU Utilization": "İşlemci Kullanımı",
"Changelog": "Değişim Günlüğü",
@@ -31,27 +31,29 @@
"Command": "Komut",
"Comment, when used at the start of a line": "Satır başında kullanıldığında açıklama özelliği taşır",
"Compression": "Sıkıştırma",
"Configured": "Configured",
"Configured": "Yapılandırıldı",
"Connection Error": "Bağlantı hatası",
"Connection Type": "Connection Type",
"Connection Type": "Bağlantı Türü",
"Copied from elsewhere": "Başka bir yerden kopyalanmış",
"Copied from original": "Aslından kopyalanmış",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
"Danger!": "Tehlike!",
"Deleted": "Silindi",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Cihaz ID",
"Device Identification": "Cihaz Kimliği",
"Device Name": "Cihaz Adı",
"Devices": "Cihazlar",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" aygıtı ({{address}} adresindeki {{device}}) bağlanmak istiyor. Yeni aygıtı ekle?",
"Device ID": "Aygıt ID",
"Device Identification": "Aygıt Kimliği",
"Device Name": "Aygıt Adı",
"Devices": "Aygıtlar",
"Disconnected": "Bağlantı Kesik",
"Discovered": "Discovered",
"Discovery": "Discovery",
"Discovered": "Keşfedildi",
"Discovery": "Keşif",
"Documentation": "Belgeleme",
"Download Rate": "İndirme Hızı",
"Downloaded": "İndirilmiş",
"Downloading": "İndiriliyor",
"Edit": "Düzenle",
"Edit Device": "Aygıtı Düzenle",
"Edit Folder": "Klasörü Düzenle",
"Editing": "Düzenleniyor",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
@@ -59,68 +61,69 @@
"Enter ignore patterns, one per line.": "Yoksayılacak/ihmal edilecek kalıp dizilerini her satıra bir tane olacak şekilde girin.",
"Error": "Hata",
"External File Versioning": "Harici Dosya Sürümlendirme",
"Failed Items": "Başarısız olunan Öğeler",
"File Pull Order": "File Pull Order",
"Failed Items": "Başarısız Olunan Ögeler",
"File Pull Order": "Dosya Koyma Düzeni",
"File Versioning": "Dosya Sürümlendirme",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Değişimleri yoklarken dosya izin bilgilerini ihmal et. FAT dosya sistemlerinde kullanın.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dosyalar Syncthing tarafından yeri değiştirildiğinde ya da silindiğinde .stversions klasörüne taşınır.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dosyalar Syncthing tarafından yeri değiştirildiğinde ya da silindiğinde, tarih damgalı sürümleri .stversions klasörüne taşınır.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosyalar diğer cihazlarda yapılan değişikliklerden korunur, ancak bu cihazdaki değişiklikler kümedeki diğer cihazlara gönderilir.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosyalar diğer aygıtlarda yapılan değişikliklerden korunur, ancak bu aygıttaki değişiklikler kümedeki diğer aygıtlara gönderilir.",
"Folder": "Klasör",
"Folder ID": "Klasör ID",
"Folder Label": "Folder Label",
"Folder Label": "Klasör Etiketi",
"Folder Path": "Klasör Yolu",
"Folder Type": "Folder Type",
"Folder Type": "Klasör Türü",
"Folders": "Klasörler",
"GUI": "GUI / Kullanıcı Grafik Arayüzü",
"GUI": "GUI / Grafiksel Kullanıcı Arayüzü",
"GUI Authentication Password": "GUI Kimlik Doğrulaması için Kullanıcı Parolası",
"GUI Authentication User": "GUI Kimlik Doğrulaması için Kullanıcı Adı",
"GUI Listen Addresses": "GUI Dinleme/Bağlantı Adresleri",
"Generate": "Oluştur",
"Global Discovery": "Küresel Discovery",
"Global Discovery Servers": "Global Discovery Servers",
"Global Discovery Servers": "Küresel Keşif Sunucuları",
"Global State": "Küresel Durum",
"Help": "Yardım",
"Home page": "Ana Sayfa",
"Home page": "Ana sayfa",
"Ignore": "Yoksay",
"Ignore Patterns": "Kalıpları Yoksay",
"Ignore Permissions": "İzinleri yoksay",
"Incoming Rate Limit (KiB/s)": "İndirme Oranı Limiti (KiB/s)",
"Incoming Rate Limit (KiB/s)": "İndirme Oranı Sınırı (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Yanlış yapılandırma klasör içeriğine zarar verebilir ve Syncthing'i çalışamaz hale getirebilir.",
"Introducer": "Tanıtıcı",
"Inversion of the given condition (i.e. do not exclude)": "Verilen koşulun ters çevirilmesi (örneğin: yok sayma)",
"Keep Versions": "Sürümleri Tut",
"Largest First": "En büyük olan önce",
"Last File Received": "Alınan Son Dosya",
"Last Scan": "Last Scan",
"Last seen": "Son Görülen",
"Last Scan": "Son Tarama",
"Last seen": "Son görülme",
"Later": "Sonra",
"Listeners": "Listeners",
"Latest Change": "Son Değişim",
"Listeners": "Dinleyiciler",
"Local Discovery": "Yerel Discovery",
"Local State": "Yerel Durum",
"Local State (Total)": "Yerel Durum (Toplamı)",
"Major Upgrade": "Birincil Yükseltme",
"Master": "Master",
"Master": "Ana",
"Maximum Age": "Azami Süre",
"Metadata Only": "Sadece Üstveri",
"Metadata Only": "Yalnızca Üstveri",
"Minimum Free Disk Space": "En Az Boş Disk Alanı",
"Move to top of queue": "Kuyruğun başına taşı",
"Multi level wildcard (matches multiple directory levels)": "Çoklu düzey wildcard (çok sayıda dizin düzeyinde eşleşme)",
"Never": "Asla",
"New Device": "Yeni Cihaz",
"New Device": "Yeni Aygıt",
"New Folder": "Yeni Klasör",
"Newest First": "En yeni olan önce",
"No": "Hayır",
"No File Versioning": "Dosya Sürümlendirmesi Yok",
"Normal": "Normal",
"Normal": "Olağan",
"Notice": "Uyarı",
"OK": "Tamam",
"Off": "Kapalı",
"Oldest First": "En eski olan önce",
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
"Optional descriptive label for the folder. Can be different on each device.": "Klasör için isteğe bağlııklayıcı etiket. Her aygıtta başka olabilir.",
"Options": "Seçenekler",
"Out of Sync": "Eşzamanlama Dışı",
"Out of Sync Items": "Eşzamanlama dışında kalan Öğeler",
"Out of Sync Items": "Eşzamanlama Dışında Kalan Ögeler",
"Outgoing Rate Limit (KiB/s)": "Yükleme hız sınırı (KB/sn)",
"Override Changes": "Değişiklikleri Geçersiz kıl",
"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": "Yerel bilgisayardaki klasöre ulaşım yolu. Klasör yoksa yaratılacak. Tilde (~) karakterinin kısayol olarak kullanılabileceği yol",
@@ -137,9 +140,9 @@
"Random": "Rastgele",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Sürüm Notları",
"Remote Devices": "Remote Devices",
"Remote Devices": "Uzak Aygıtlar",
"Remove": "Kaldır",
"Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.",
"Required identifier for the folder. Must be the same on all cluster devices.": "Klasör için tanımlayıcı gereklidir. Tüm küme cihazlarda aynı olmalıdır.",
"Rescan": "Tekrar Tara",
"Rescan All": "Tümünü Tekrar Tara",
"Rescan Interval": "Tarama Aralığı",
@@ -151,23 +154,25 @@
"Save": "Kaydet",
"Scan Time Remaining": "Kalan Tarama Zamanı",
"Scanning": "Taranıyor",
"Select the devices to share this folder with.": "Bu klasörü paylaşacağın cihazları seç.",
"Select the folders to share with this device.": "Bu cihazla paylaşılacak klasörleri seç.",
"Select the devices to share this folder with.": "Bu klasörü paylaşacağın aygıtları seç.",
"Select the folders to share with this device.": "Bu aygıtla paylaşılacak klasörleri seç.",
"Send & Receive": "Gönder & Al",
"Send Only": "Yalnızca Gönder",
"Settings": "Ayarlar",
"Share": "Paylaş",
"Share Folder": "Paylaşım Klasörü",
"Share Folders With Device": "Klasörü Cihazla Paylaş",
"Share With Devices": "Cihazlar İle Paylaş",
"Share this folder?": "Bu klasörü paylaşmak istiyor musun?",
"Share Folders With Device": "Klasörü Aygıtla Paylaş",
"Share With Devices": "Aygıtlar İle Paylaş",
"Share this folder?": "Bu klasörü paylaş?",
"Shared With": "Paylaşılan düğümler",
"Show ID": "ID Göster",
"Show QR": "QR Göster",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Küme durumunda Cihaz ID yerine bunu göster. Varsayılan isim isteğe bağlı olarak diğer cihazlara ilan edilecektir.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Küme durumunda Cihaz ID yerine bunu göster. Eğer düğüm ismi boş bırakılırsa düğüm ismi güncellenip ilan edilecektir.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Küme durumunda Aygıt ID yerine bunu göster. Varsayılan ad isteğe bağlı olarak diğer aygıtlara ilan edilecektir.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Küme durumunda Aygıt ID yerine bunu göster. Eğer düğüm adı boş bırakılırsa düğüm adı güncellenip ilan edilecektir.",
"Shutdown": "Kapat",
"Shutdown Complete": "Kapatma İşlemi Tamamlandı",
"Simple File Versioning": "Basit Dosya Sürümlendirme",
"Single level wildcard (matches within a directory only)": "Tekli düzey wildcard (sadece bir dizin içinde eşleşme)",
"Single level wildcard (matches within a directory only)": "Tekli düzey wildcard (yalnızca bir dizin içinde eşleşme)",
"Smallest First": "En küçük olan önce",
"Source Code": "Kaynak Kodu",
"Staggered File Versioning": "Aşamalı Dosya Sürümlendirme",
@@ -184,18 +189,18 @@
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing uygulaması çökmüş olabilir, veya internet bağlantınızda bir sorun var. Tekrar deniyor....",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing isteminizi işleme alırken bir sorunla karşılaştı. Lütfen sayfanızı yenileyin veya sorun devam ediyorsa Syncthing'i yeniden başlatın.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing yönetici arayüzü parolasız olarak uzaktan erişime izin verilecek şekilde yapılandırıldı.",
"The aggregated statistics are publicly available at the URL below.": "The aggregated statistics are publicly available at the URL below.",
"The aggregated statistics are publicly available at the URL below.": "Toplanan istatistikler halka açık biçimde aşağıdaki adrestedir.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Yapılandırma kaydedildi ancak etkinleştirilmedi. Etkinleştirmek için Syncthing yeniden başlatılmalı.",
"The device ID cannot be blank.": "Cihaz ID boş olamaz.",
"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).": "Buraya girilecek olan aygıt ID'si diğer cihazlarda, \"Eylemler > ID Göster\" penceresinde bulunabilir. Boşluklar ve çizgiler isteğe bağlıdır (yoksayılmış).",
"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.": "Şifrelenmiş kullanım bilgisi günlük olarak gönderilir. Platform, klasör büyüklüğü ve uygulama sürümü hakkında bilgi toplanır. Toplanan bilgi çeşidi değişecek olursa, sizden tekrar onay istenecek.",
"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.": "Girilen cihaz ID'si geçerli gibi gözükmüyor. 52 ya da 56 karakter uzunluğunda, harf ve rakamlardan oluşmalı. Boşlukların ve kısa çizgilerin olup olmaması önemli değildir.",
"The device ID cannot be blank.": "Aygıt ID boş olamaz.",
"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).": "Buraya girilecek olan aygıt ID'si diğer aygıtlarda, \"Eylemler > ID Göster\" penceresinde bulunabilir. Boşluklar ve çizgiler isteğe bağlıdır (yoksayılmış).",
"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.": "Şifrelenmiş kullanım bilgisi günlük olarak gönderilir. Platform, klasör büyüklüğü ve uygulama sürümü hakkında bilgi toplanır. Toplanan bilgi türü değişecek olursa, sizden yeniden onay istenecek.",
"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.": "Girilen aygıt ID'si geçerli gibi gözükmüyor. 52 ya da 56 karakter uzunluğunda, harf ve rakamlardan oluşmalı. Boşlukların ve kısa çizgilerin olup olmaması önemli değildir.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "İlk komut satırı parametresi klasör yoludur; ikinci parametre ise klasördeki göreceli yoldur. ",
"The folder ID cannot be blank.": "Klasör ID boş olamaz.",
"The folder ID must be unique.": "Klasör ID benzersiz olmalıdır.",
"The folder path cannot be blank.": "Klasör dizini boş bırakılamaz.",
"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.": "Kullanılan zaman aralıkları: ilk bir saat zarfında her 30 saniyede bir, ilk gün zarfında saatte bir, ilk 30 gün zarfında her gün, azami süreye kadar geçen zamanda ise her hafta yeni bir sürüm değeri oluşturulur/tutulur.",
"The following items could not be synchronized.": "Aşağıdaki öğelerin eşzamanlama işlemi gerçekleştirilemedi.",
"The following items could not be synchronized.": "Aşağıdaki ögelerin eşzamanlama işlemi gerçekleştirilemedi.",
"The maximum age must be a number and cannot be blank.": "Azami süre tanımı boş bırakılmamalı ve bir sayı olarak tanımlanmalıdır.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Bir sürümün tutulması için belirlenen azami süre (sürümleri sürekli olarak tutabilmek için 0 değeri atayın)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "En az boş disk alanı yüzde olarak 0 ve 100 (dahil) arasında kalan pozitif bir sayıyla tanımlanmalıdır.",
@@ -203,11 +208,11 @@
"The number of days to keep files in the trash can. Zero means forever.": "Dosyaları çöp kutusunda tutma süresini tanımlayan gün sayısı. Sıfır devamlı/sürekli anlamına gelir.",
"The number of old versions to keep, per file.": "Dosya başına saklanacak/tutulacak eski sürüm sayısı.",
"The number of versions must be a number and cannot be blank.": "Sürümlerin sayısı sayı olmalı ve boş bırakılamaz.",
"The path cannot be blank.": "Dizin yolu boş bırakılamaz.",
"The path cannot be blank.": "Yol boş bırakılamaz.",
"The rate limit must be a non-negative number (0: no limit)": "Hız sınırı pozitif bir sayı olmalıdır. (0: sınırsız)",
"The rescan interval must be a non-negative number of seconds.": "Tarama zaman aralığı, saniye cinsinden negatif olmayan bir sayı olmalıdır.",
"They are retried automatically and will be synced when the error is resolved.": "Otomatik olarak yeniden deneniyor; hata giderildiğinde eşzamanlama gerçekleştirilecek.",
"This Device": "This Device",
"They are retried automatically and will be synced when the error is resolved.": "Kendiliğinden yeniden deneniyor; hata giderildiğinde eşzamanlama gerçekleştirilecek.",
"This Device": "Bu Aygıt",
"This can easily give hackers access to read and change any files on your computer.": "Hacker'ların bilgisayarındaki dosyaları okuma ve değiştirme yetkisine kolayca erişebilmelerini sağlayabilir.",
"This is a major version upgrade.": "Birincil sürüm yükseltmesidir.",
"Trash Can File Versioning": "Çöp Kutusu Dosya Sürümleme",
@@ -225,16 +230,16 @@
"Version": "Sürüm",
"Versions Path": "Sürüm Dizin Yolu",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Sürümler, tanımlı azami süre veya belirlenen zaman aralığı için izin verilen dosya sayısıılmışsa kendiliğinden silinir.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a subdirectory of an existing folder \"{{otherFolder}}\".",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Yeni bir cihaz eklendiğinde, bu cihazın karşı tarafa da eklenmesi gerektiğini unutmayın.",
"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.": "Yeni bir klasör eklendiğinde, Klasör ID'nin klasörleri cihazlar arasında bağlantılandırmak için kullanıldığını unutmayın. Klasör ID'ler büyük - küçük harf duyarlıdır ve bütün cihazlarda tamı tamına eşleşmelidir.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Uyarı, bu yol var olan bir klasörün \"{{otherFolder}}\" alt klasörüdür.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Yeni bir aygıt eklendiğinde, bu aygıtın karşı tarafa da eklenmesi gerektiğini unutmayın.",
"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.": "Yeni bir klasör eklendiğinde, Klasör ID'nin klasörleri aygıtlar arasında bağlantılandırmak için kullanıldığını unutmayın. Klasör ID'ler büyük - küçük harf duyarlıdır ve tüm aygıtlarda tamı tamına eşleşmelidir.",
"Yes": "Evet",
"You must keep at least one version.": "En az bir sürümü tutmalısınız.",
"days": "günler",
"directories": "directories",
"files": "files",
"days": "gün",
"directories": "dizin",
"files": "dosya",
"full documentation": "belgelendirme içeriğinin tümü",
"items": "öğel",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} \"{{folder}}\" klasörünü paylaşmak istiyor.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}}, \"{{folderlabel}}\" ({{folder}}) klasörünü paylaşmak istiyor."
}

View File

@@ -52,6 +52,8 @@
"Downloaded": "Завантажено",
"Downloading": "Завантаження",
"Edit": "Редагувати",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Редагування",
"Enable NAT traversal": "Увімкнути NAT traversal",
"Enable Relaying": "Увімкнути ретрансляцію (relaying)",
@@ -95,6 +97,7 @@
"Last Scan": "Останнє сканування",
"Last seen": "З’являвся останній раз",
"Later": "Пізніше",
"Latest Change": "Latest Change",
"Listeners": "Приймачі (TCP & Relay)",
"Local Discovery": "Локальне виявлення (LAN)",
"Local State": "Локальний статус",
@@ -153,6 +156,8 @@
"Scanning": "Сканування",
"Select the devices to share this folder with.": "Оберіть пристрої, які матимуть доступ до цієї директорії.",
"Select the folders to share with this device.": "Оберіть директорії до яких матиме доступ цей пристрій.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Налаштування",
"Share": "Розповсюдити ",
"Share Folder": "Розповсюдити каталог",

View File

@@ -52,6 +52,8 @@
"Downloaded": "Đã tải xuống",
"Downloading": "Đang tải xuống",
"Edit": "Chỉnh sửa",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Đang ch.sửa",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Bật chế độ ch.tiếp",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "Thấy lần cuối",
"Later": "Để sau",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "Dò tìm cục bộ",
"Local State": "Tr.thái cục bộ",
@@ -153,6 +156,8 @@
"Scanning": "Đang quét",
"Select the devices to share this folder with.": "Chọn các thiết bị để chia sẻ thư mục này.",
"Select the folders to share with this device.": "Chọn các thư mục để chia sẻ với thiết bị này.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Cài đặt",
"Share": "Chia sẻ",
"Share Folder": "Chia sẻ th.mục",

View File

@@ -52,6 +52,8 @@
"Downloaded": "已下载",
"Downloading": "下载中",
"Edit": "选项",
"Edit Device": "编辑设备",
"Edit Folder": "编辑文件夹",
"Editing": "正在编辑",
"Enable NAT traversal": "启用 NAT 遍历",
"Enable Relaying": "开启中继",
@@ -95,6 +97,7 @@
"Last Scan": "最后扫描",
"Last seen": "最后可见",
"Later": "稍后",
"Latest Change": "最后更改",
"Listeners": "侦听程序",
"Local Discovery": "在局域网上寻找设备",
"Local State": "本地状态",
@@ -153,6 +156,8 @@
"Scanning": "扫描中",
"Select the devices to share this folder with.": "选择将本文件夹共享给哪些设备",
"Select the folders to share with this device.": "选择与该设备共享的文件夹。",
"Send & Receive": "发送与接收",
"Send Only": "仅发送",
"Settings": "设置",
"Share": "共享",
"Share Folder": "共享文件夹",

View File

@@ -52,6 +52,8 @@
"Downloaded": "已下載",
"Downloading": "正在下載",
"Edit": "編輯",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "正在編輯",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "啟用中繼",
@@ -95,6 +97,7 @@
"Last Scan": "Last Scan",
"Last seen": "最後發現時間",
"Later": "稍後",
"Latest Change": "Latest Change",
"Listeners": "Listeners",
"Local Discovery": "本機探索",
"Local State": "本機狀態",
@@ -153,6 +156,8 @@
"Scanning": "正在掃描",
"Select the devices to share this folder with.": "選擇要共享這個資料夾的裝置。",
"Select the folders to share with this device.": "選擇要共享這個資料夾的裝置。",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "設定",
"Share": "分享",
"Share Folder": "分享資料夾",

View File

@@ -271,6 +271,7 @@
<span class="fa fa-fw" ng-class="[folder.type == 'readonly' ? 'fa-lock' : 'fa-folder']"></span>
</div>
<div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">&#9724;</span></span>
@@ -307,11 +308,11 @@
<span tooltip data-original-title="{{folder.path}}">{{folder.path}}</span>
</td>
</tr>
<tr ng-if="model[folder.id].invalid || model[folder.id].error">
<tr ng-if="!folder.paused && (model[folder.id].invalid || model[folder.id].error)">
<th><span class="fa fa-fw fa-exclamation-triangle"></span>&nbsp;<span translate>Error</span></th>
<td class="text-right">{{model[folder.id].invalid || model[folder.id].error}}</td>
</tr>
<tr>
<tr ng-if="!folder.paused">
<th><span class="fa fa-fw fa-globe"></span>&nbsp;<span translate>Global State</span></th>
<td class="text-right">
<span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
@@ -321,7 +322,7 @@
</span>
</td>
</tr>
<tr>
<tr ng-if="!folder.paused">
<th><span class="fa fa-fw fa-home"></span>&nbsp;<span translate>Local State</span></th>
<td class="text-right">
<span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
@@ -354,7 +355,7 @@
<tr ng-if="folder.type != 'readwrite'">
<th><span class="fa fa-fw fa-lock"></span>&nbsp;<span translate>Folder Type</span></th>
<td class="text-right">
<span ng-if="folder.type == 'readonly'" translate>Master</span>
<span ng-if="folder.type == 'readonly'" translate>Send Only</span>
<span ng-if="folder.type != 'readonly'">{{ folder.type.charAt(0).toUpperCase() + folder.type.slice(1) }}</span>
</td>
</tr>
@@ -400,7 +401,7 @@
</td>
</tr>
<tr ng-if="folder.type != 'readonly' && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename">
<th><span class="fa fa-fw fa-exchange"></span>&nbsp;<span translate>Last File Received</span></th>
<th><span class="fa fa-fw fa-exchange"></span>&nbsp;<span translate>Latest Change</span></th>
<td class="text-right">
<span tooltip data-original-title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
<span translate ng-if="!folderStats[folder.id].lastFile.deleted">Updated</span>
@@ -417,6 +418,12 @@
<span class="fa fa-arrow-circle-up"></span>&nbsp;<span translate>Override Changes</span>
</button>
<span class="pull-right">
<button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
<span class="fa fa-pause"></span>&nbsp;<span translate>Pause</span>
</button>
<button ng-if="folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, false)">
<span class="fa fa-play"></span>&nbsp;<span translate>Resume</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-show="['idle', 'stopped', 'unshared'].indexOf(folderStatus(folder)) > -1">
<span class="fa fa-refresh"></span>&nbsp;<span translate>Rescan</span>
</button>
@@ -579,6 +586,10 @@
<th><span class="fa fa-fw fa-thumbs-o-up"></span>&nbsp;<span translate>Introducer</span></th>
<td translate class="text-right">Yes</td>
</tr>
<tr ng-if="deviceCfg.introducedBy">
<th><span class="fa fa-fw fa-meh-o"></span>&nbsp;<span translate>Introduced By</span></th>
<td translate class="text-right">{{ deviceName(findDevice(deviceCfg.introducedBy)) || deviceCfg.introducedBy.substring(0, 5) }}</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
<th><span class="fa fa-fw fa-tag"></span>&nbsp;<span translate>Version</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
@@ -597,10 +608,10 @@
</div>
<div class="panel-footer">
<span class="pull-right">
<button ng-if="!connections[deviceCfg.deviceID].paused" type="button" class="btn btn-sm btn-default" ng-click="pauseDevice(deviceCfg.deviceID)">
<button ng-if="!deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, true)">
<span class="fa fa-pause"></span>&nbsp;<span translate>Pause</span>
</button>
<button ng-if="connections[deviceCfg.deviceID].paused" type="button" class="btn btn-sm btn-default" ng-click="resumeDevice(deviceCfg.deviceID)">
<button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
<span class="fa fa-play"></span>&nbsp;<span translate>Resume</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
@@ -613,9 +624,14 @@
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="addDevice()">
<span class="fa fa-plus"></span>&nbsp;<span translate>Add Remote Device</span>
</button>
<span class="pull-right">
<button type="button" class="btn btn-sm btn-default" ng-click="globalChanges()">
<span class="fa fa-fw fa-history"></span>&nbsp;<span translate>Global Changes</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="addDevice()">
<span class="fa fa-plus"></span>&nbsp;<span translate>Add Remote Device</span>
</button>
</span>
<div class="clearfix"></div>
</div>
</div>
@@ -647,6 +663,7 @@
<ng-include src="'syncthing/core/shutdownDialogView.html'"></ng-include>
<ng-include src="'syncthing/device/idqrModalView.html'"></ng-include>
<ng-include src="'syncthing/device/editDeviceModalView.html'"></ng-include>
<ng-include src="'syncthing/device/globalChangesModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/editFolderModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/editIgnoresModalView.html'"></ng-include>
<ng-include src="'syncthing/settings/settingsModalView.html'"></ng-include>

View File

@@ -12,7 +12,7 @@
<p translate>Copyright &copy; 2014-2016 the following Contributors:</p>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Alexandre Viau, Andrew Dunham, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Laurent Etiemble, Leo Arias, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Roman Zaynetdinov, Scott Klupfel, Simon Frei, Stefan Kuntz, Tim Abell, Tim Howes, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Xavier O., Yannic A.
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Alexandre Viau, Andrew Dunham, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Heiko Zuerker, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Laurent Etiemble, Leo Arias, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Roman Zaynetdinov, Scott Klupfel, Simon Frei, Stefan Kuntz, Tim Abell, Tim Howes, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Xavier O., Yannic A.
</div>
</div>
<hr/>

View File

@@ -33,11 +33,9 @@ angular.module('syncthing.core')
lastID = lastEvent.id;
}
$timeout(function () {
$http.get(urlbase + '/events?since=' + lastID)
.success(successFn)
.error(errorFn);
}, 500, false);
$http.get(urlbase + '/events?since=' + lastID)
.success(successFn)
.error(errorFn);
}
function errorFn (dummy) {
@@ -79,6 +77,8 @@ angular.module('syncthing.core')
STATE_CHANGED: 'StateChanged', // Emitted when a folder changes state
FOLDER_ERRORS: 'FolderErrors', // Emitted when a folder has errors preventing a full sync
FOLDER_SCAN_PROGRESS: 'FolderScanProgress', // Emitted every ScanProgressIntervalS seconds, indicating how far into the scan it is at.
FOLDER_PAUSED: 'FolderPaused', // Emitted when a folder is paused
FOLDER_RESUMED: 'FolderResumed', // Emitted when a folder is resumed
start: function() {
$http.get(urlbase + '/events?limit=1')

View File

@@ -51,6 +51,7 @@ angular.module('syncthing.core')
$scope.failedPageSize = 10;
$scope.scanProgress = {};
$scope.themes = [];
$scope.globalChangeEvents = {};
$scope.localStateTotal = {
bytes: 0,
@@ -91,6 +92,7 @@ angular.module('syncthing.core')
refreshConnectionStats();
refreshDeviceStats();
refreshFolderStats();
refreshGlobalChanges();
refreshThemes();
$http.get(urlbase + '/system/version').success(function (data) {
@@ -186,6 +188,7 @@ angular.module('syncthing.core')
$scope.$on(Events.LOCAL_INDEX_UPDATED, function (event, arg) {
refreshFolderStats();
refreshGlobalChanges();
});
$scope.$on(Events.DEVICE_DISCONNECTED, function (event, arg) {
@@ -233,14 +236,6 @@ angular.module('syncthing.core')
$scope.deviceRejections[arg.data.device] = arg;
});
$scope.$on(Events.DEVICE_PAUSED, function (event, arg) {
$scope.connections[arg.data.device].paused = true;
});
$scope.$on(Events.DEVICE_RESUMED, function (event, arg) {
$scope.connections[arg.data.device].paused = false;
});
$scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
$scope.folderRejections[arg.data.folder + "-" + arg.data.device] = arg;
});
@@ -629,6 +624,15 @@ angular.module('syncthing.core')
}).error($scope.emitHTTPError);
}, 2500);
var refreshGlobalChanges = debounce(function () {
$http.get(urlbase + "/events/disk?limit=25").success(function (data) {
data = data.reverse();
$scope.globalChangeEvents = data;
console.log("refreshGlobalChanges", data);
}).error($scope.emitHTTPError);
}, 2500);
$scope.refresh = function () {
refreshSystem();
refreshDiscoveryCache();
@@ -641,6 +645,10 @@ angular.module('syncthing.core')
return 'unknown';
}
if (folderCfg.paused) {
return 'paused';
}
// after restart syncthing process state may be empty
if (!$scope.model[folderCfg.id].state) {
return 'unknown';
@@ -654,7 +662,7 @@ angular.module('syncthing.core')
if (state === 'error') {
return 'stopped'; // legacy, the state is called "stopped" in the GUI
}
if (state === 'idle' && $scope.model[folderCfg.id].needFiles + $scope.model[folderCfg.id].needDeletes > 0) {
if (state === 'idle' && $scope.neededItems(folderCfg.id) > 0) {
return 'outofsync';
}
if (state === 'scanning') {
@@ -674,6 +682,9 @@ angular.module('syncthing.core')
if (status === 'idle') {
return 'success';
}
if (status == 'paused') {
return 'default';
}
if (status === 'syncing' || status === 'scanning') {
return 'primary';
}
@@ -790,7 +801,7 @@ angular.module('syncthing.core')
return 'unknown';
}
if ($scope.connections[deviceCfg.deviceID].paused) {
if (deviceCfg.paused) {
return 'paused';
}
@@ -816,7 +827,7 @@ angular.module('syncthing.core')
return 'info';
}
if ($scope.connections[deviceCfg.deviceID].paused) {
if (deviceCfg.paused) {
return 'default';
}
@@ -912,6 +923,16 @@ angular.module('syncthing.core')
return '';
};
$scope.friendlyNameFromShort = function (shortID) {
var matches = $scope.devices.filter(function (n) {
return n.deviceID.substr(0, 7) === shortID;
});
if (matches.length !== 1) {
return shortID;
}
return matches[0].name;
};
$scope.findDevice = function (deviceID) {
var matches = $scope.devices.filter(function (n) {
return n.deviceID === deviceID;
@@ -943,12 +964,23 @@ angular.module('syncthing.core')
return device.deviceID.substr(0, 6);
};
$scope.pauseDevice = function (device) {
$http.post(urlbase + "/system/pause?device=" + device);
$scope.setDevicePause = function (device, pause) {
$scope.devices.forEach(function (cfg) {
if (cfg.deviceID == device) {
cfg.paused = pause;
}
});
$scope.config.devices = $scope.devices;
$scope.saveConfig();
};
$scope.resumeDevice = function (device) {
$http.post(urlbase + "/system/resume?device=" + device);
$scope.setFolderPause = function (folder, pause) {
var cfg = $scope.folders[folder];
if (cfg) {
cfg.paused = pause;
$scope.config.folders = folderList($scope.folders);
$scope.saveConfig();
}
};
$scope.editSettings = function () {
@@ -1071,6 +1103,13 @@ angular.module('syncthing.core')
$scope.editDevice = function (deviceCfg) {
$scope.currentDevice = $.extend({}, deviceCfg);
$scope.editingExisting = true;
$scope.willBeReintroducedBy = undefined;
if (deviceCfg.introducedBy) {
var introducerDevice = $scope.findDevice(deviceCfg.introducedBy);
if (introducerDevice && introducerDevice.introducer) {
$scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
}
}
$scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
$scope.currentDevice.selectedFolders = {};
$scope.deviceFolders($scope.currentDevice).forEach(function (folder) {
@@ -1261,7 +1300,11 @@ angular.module('syncthing.core')
$scope.folderEditor = form;
break;
}
}
};
$scope.globalChanges = function () {
$('#globalChanges').modal();
};
$scope.editFolder = function (folderCfg) {
$scope.currentFolder = angular.copy(folderCfg);

View File

@@ -75,8 +75,13 @@
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>Close</span>
</button>
<button type="button" class="btn btn-warning pull-left btn-sm" ng-click="deleteDevice()" ng-if="editingExisting">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
<div ng-if="editingExisting" class="pull-left">
<button type="button" class="btn btn-warning btn-sm disabled" ng-if="willBeReintroducedBy" tooltip data-original-title="This device will be reintroduced by {{ willBeReintroducedBy }}">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
<button type="button" class="btn btn-warning btn-sm" ng-click="deleteDevice()" ng-if="!willBeReintroducedBy">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
</div>
</div>
</modal>

View File

@@ -0,0 +1,27 @@
<style> th, td { padding: 6px; } </style>
<modal id="globalChanges" status="default" icon="{{'history'}}" heading="{{'Global Changes' | translate}}" large="yes" closeable="yes">
<div class="modal-body">
<table>
<tr>
<th translate>Device</th>
<th translate>Action</th>
<th translate>Type</th>
<th translate>Path</th>
<th translate>Time</th>
</tr>
<tr ng-repeat="changeEvent in globalChangeEvents">
<td ng-if="changeEvent.data.modifiedBy">{{friendlyNameFromShort(changeEvent.data.modifiedBy)}}</td>
<td ng-if="!changeEvent.data.modifiedBy"><span translate>Unknown</span></td>
<td>{{changeEvent.data.action}}</td>
<td>{{changeEvent.data.type}}</td>
<td>{{changeEvent.data.path}}</td>
<td>{{changeEvent.time | date:'medium'}}</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</model>

View File

@@ -84,10 +84,10 @@
<div class="col-md-6">
<div class="form-group">
<label translate>Folder Type</label>
&nbsp;<a href="https://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
&nbsp;<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.type">
<option value="readwrite" translate>Normal</option>
<option value="readonly" translate>Master</option>
<option value="readwrite" translate>Send &amp; Receive</option>
<option value="readonly" translate>Send Only</option>
</select>
<p ng-if="currentFolder.type == 'readonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
</div>

View File

@@ -55,3 +55,15 @@ go run build.go -goarch armhf snap
go run build.go -goarch arm64 snap
mv *.snap "$WORKSPACE"
if [[ -d /usr/local/oldgo ]]; then
echo
echo Building with minimum supported Go version
export GOROOT=/usr/local/oldgo
export PATH="$GOROOT/bin:$PATH"
go version
echo
rm -rf "$GOPATH/pkg"
go run build.go install all # only compile, don't run lints and stuff
fi

View File

@@ -42,6 +42,8 @@ func (validationError) String() string {
}
func TestReplaceCommit(t *testing.T) {
t.Skip("broken, fails randomly, #3834")
w := Wrap("/dev/null", Configuration{Version: 0})
if w.RawCopy().Version != 0 {
t.Fatal("Config incorrect")

View File

@@ -407,9 +407,9 @@ func convertV13V14(cfg *Configuration) {
for i, fcfg := range cfg.Folders {
if fcfg.DeprecatedReadOnly {
cfg.Folders[i].Type = FolderTypeReadOnly
cfg.Folders[i].Type = FolderTypeSendOnly
} else {
cfg.Folders[i].Type = FolderTypeReadWrite
cfg.Folders[i].Type = FolderTypeSendReceive
}
cfg.Folders[i].DeprecatedReadOnly = false
}

View File

@@ -96,7 +96,7 @@ func TestDeviceConfig(t *testing.T) {
ID: "test",
RawPath: "testdata",
Devices: []FolderDeviceConfiguration{{DeviceID: device1}, {DeviceID: device4}},
Type: FolderTypeReadOnly,
Type: FolderTypeSendOnly,
RescanIntervalS: 600,
Copiers: 0,
Pullers: 0,

View File

@@ -17,6 +17,7 @@ type DeviceConfiguration struct {
Introducer bool `xml:"introducer,attr" json:"introducer"`
SkipIntroductionRemovals bool `xml:"skipIntroductionRemovals,attr" json:"skipIntroductionRemovals"`
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
Paused bool `xml:"paused" json:"paused"`
}
func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {

View File

@@ -40,6 +40,8 @@ type FolderConfiguration struct {
DisableSparseFiles bool `xml:"disableSparseFiles" json:"disableSparseFiles"`
DisableTempIndexes bool `xml:"disableTempIndexes" json:"disableTempIndexes"`
Fsync bool `xml:"fsync" json:"fsync"`
DisableWeakHash bool `xml:"disableWeakHash" json:"disableWeakHash"`
Paused bool `xml:"paused" json:"paused"`
cachedPath string
@@ -98,13 +100,13 @@ func (f *FolderConfiguration) CreateMarker() error {
func (f *FolderConfiguration) HasMarker() bool {
_, err := os.Stat(filepath.Join(f.Path(), ".stfolder"))
if err != nil {
return false
}
return true
return err == nil
}
func (f FolderConfiguration) Description() string {
if f.Label == "" {
return f.ID
}
return fmt.Sprintf("%q (%s)", f.Label, f.ID)
}

View File

@@ -9,15 +9,15 @@ package config
type FolderType int
const (
FolderTypeReadWrite FolderType = iota // default is readwrite
FolderTypeReadOnly
FolderTypeSendReceive FolderType = iota // default is sendreceive
FolderTypeSendOnly
)
func (t FolderType) String() string {
switch t {
case FolderTypeReadWrite:
case FolderTypeSendReceive:
return "readwrite"
case FolderTypeReadOnly:
case FolderTypeSendOnly:
return "readonly"
default:
return "unknown"
@@ -30,12 +30,12 @@ func (t FolderType) MarshalText() ([]byte, error) {
func (t *FolderType) UnmarshalText(bs []byte) error {
switch string(bs) {
case "readwrite":
*t = FolderTypeReadWrite
case "readonly":
*t = FolderTypeReadOnly
case "readwrite", "sendreceive":
*t = FolderTypeSendReceive
case "readonly", "sendonly":
*t = FolderTypeSendOnly
default:
*t = FolderTypeReadWrite
*t = FolderTypeSendReceive
}
return nil
}

View File

@@ -41,6 +41,7 @@ type OptionsConfiguration struct {
OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`

View File

@@ -323,6 +323,18 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
return DeviceConfiguration{}, false
}
// Folder returns the configuration for the given folder and an "ok" bool.
func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
w.mut.Lock()
defer w.mut.Unlock()
for _, folder := range w.cfg.Folders {
if folder.ID == id {
return folder, true
}
}
return FolderConfiguration{}, false
}
// Save writes the configuration to disk, and generates a ConfigSaved event.
func (w *Wrapper) Save() error {
fd, err := osutil.CreateAtomic(w.path)

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package connections
import "testing"
import "net/url"
func TestFixupPort(t *testing.T) {
cases := [][2]string{
{"tcp://1.2.3.4:5", "tcp://1.2.3.4:5"},
{"tcp://1.2.3.4:", "tcp://1.2.3.4:22000"},
{"tcp://1.2.3.4", "tcp://1.2.3.4:22000"},
}
for _, tc := range cases {
u0, _ := url.Parse(tc[0])
u1 := fixupPort(u0).String()
if u1 != tc[1] {
t.Errorf("fixupPort(%q) => %q, expected %q", tc[0], u1, tc[1])
}
}
}

View File

@@ -1,10 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// The existence of this file means we get 0% test coverage rather than no
// test coverage at all. Remove when implementing an actual test.
package connections

View File

@@ -28,21 +28,26 @@ type relayDialer struct {
tlsCfg *tls.Config
}
func (d *relayDialer) Dial(id protocol.DeviceID, uri *url.URL) (IntermediateConnection, error) {
func (d *relayDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
inv, err := client.GetInvitationFromRelay(uri, id, d.tlsCfg.Certificates, 10*time.Second)
if err != nil {
return IntermediateConnection{}, err
return internalConn{}, err
}
conn, err := client.JoinSession(inv)
if err != nil {
return IntermediateConnection{}, err
return internalConn{}, err
}
err = dialer.SetTCPOptions(conn)
if err != nil {
conn.Close()
return IntermediateConnection{}, err
return internalConn{}, err
}
err = dialer.SetTrafficClass(conn, d.cfg.Options().TrafficClass)
if err != nil {
l.Debugf("failed to set traffic class: %s", err)
}
var tc *tls.Conn
@@ -55,10 +60,10 @@ func (d *relayDialer) Dial(id protocol.DeviceID, uri *url.URL) (IntermediateConn
err = tlsTimedHandshake(tc)
if err != nil {
tc.Close()
return IntermediateConnection{}, err
return internalConn{}, err
}
return IntermediateConnection{tc, "Relay (Client)", relayPriority}, nil
return internalConn{tc, connTypeRelayClient, relayPriority}, nil
}
func (relayDialer) Priority() int {

View File

@@ -29,8 +29,9 @@ type relayListener struct {
onAddressesChangedNotifier
uri *url.URL
cfg *config.Wrapper
tlsCfg *tls.Config
conns chan IntermediateConnection
conns chan internalConn
factory listenerFactory
err error
@@ -79,6 +80,11 @@ func (t *relayListener) Serve() {
l.Infoln(err)
}
err = dialer.SetTrafficClass(conn, t.cfg.Options().TrafficClass)
if err != nil {
l.Debugf("failed to set traffic class: %s", err)
}
var tc *tls.Conn
if inv.ServerSocket {
tc = tls.Server(conn, t.tlsCfg)
@@ -93,7 +99,7 @@ func (t *relayListener) Serve() {
continue
}
t.conns <- IntermediateConnection{tc, "Relay (Server)", relayPriority}
t.conns <- internalConn{tc, connTypeRelayServer, relayPriority}
// Poor mans notifier that informs the connection service that the
// relay URI has changed. This can only happen when we connect to a
@@ -167,9 +173,10 @@ func (t *relayListener) String() string {
type relayListenerFactory struct{}
func (f *relayListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan IntermediateConnection, 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,
tlsCfg: tlsCfg,
conns: conns,
factory: f,

View File

@@ -41,6 +41,32 @@ const (
tlsHandshakeTimeout = 10 * time.Second
)
// From go/src/crypto/tls/cipher_suites.go
var tlsCipherSuiteNames = map[uint16]string{
0x0005: "TLS_RSA_WITH_RC4_128_SHA",
0x000a: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
0x002f: "TLS_RSA_WITH_AES_128_CBC_SHA",
0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA",
0x003c: "TLS_RSA_WITH_AES_128_CBC_SHA256",
0x009c: "TLS_RSA_WITH_AES_128_GCM_SHA256",
0x009d: "TLS_RSA_WITH_AES_256_GCM_SHA384",
0xc007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
0xc009: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
0xc00a: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
0xc011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
0xc012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
0xc013: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
0xc014: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
0xc023: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
0xc027: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
0xc02f: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
0xc02b: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
0xc030: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
0xc02c: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
0xcca8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
0xcca9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}
// Service listens and dials all configured unconnected devices, via supported
// dialers. Successful connections are handed to the model.
type Service struct {
@@ -50,7 +76,7 @@ type Service struct {
model Model
tlsCfg *tls.Config
discoverer discover.Finder
conns chan IntermediateConnection
conns chan internalConn
bepProtocolName string
tlsDefaultCommonName string
lans []*net.IPNet
@@ -65,7 +91,7 @@ type Service struct {
listenerSupervisor *suture.Supervisor
curConMut sync.Mutex
currentConnection map[protocol.DeviceID]Connection
currentConnection map[protocol.DeviceID]completeConn
}
func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
@@ -82,7 +108,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
model: mdl,
tlsCfg: tlsCfg,
discoverer: discoverer,
conns: make(chan IntermediateConnection),
conns: make(chan internalConn),
bepProtocolName: bepProtocolName,
tlsDefaultCommonName: tlsDefaultCommonName,
lans: lans,
@@ -105,7 +131,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
}),
curConMut: sync.NewMutex(),
currentConnection: make(map[protocol.DeviceID]Connection),
currentConnection: make(map[protocol.DeviceID]completeConn),
}
cfg.Subscribe(service)
@@ -204,7 +230,7 @@ next:
// The Model will return an error for devices that we don't want to
// have a connection with for whatever reason, for example unknown devices.
if err := s.model.OnHello(remoteID, c.RemoteAddr(), hello); err != nil {
l.Infof("Connection from %s at %s (%s) rejected: %v", remoteID, c.RemoteAddr(), c.Type, err)
l.Infof("Connection from %s at %s (%s) rejected: %v", remoteID, c.RemoteAddr(), c.Type(), err)
c.Close()
continue
}
@@ -218,7 +244,7 @@ next:
priorityKnown := ok && connected
// Lower priority is better, just like nice etc.
if priorityKnown && ct.Priority > c.Priority {
if priorityKnown && ct.internalConn.priority > c.priority {
l.Debugln("Switching connections", remoteID)
} else if connected {
// We should not already be connected to the other party. TODO: This
@@ -268,12 +294,11 @@ next:
rd = NewReadLimiter(c, s.readRateLimit)
}
name := fmt.Sprintf("%s-%s (%s)", c.LocalAddr(), c.RemoteAddr(), c.Type)
name := fmt.Sprintf("%s-%s (%s)", c.LocalAddr(), c.RemoteAddr(), c.Type())
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression)
modelConn := Connection{c, protoConn}
modelConn := completeConn{c, protoConn}
l.Infof("Established secure connection to %s at %s", remoteID, name)
l.Debugf("cipher suite: %04X in lan: %t", c.ConnectionState().CipherSuite, !limit)
l.Infof("Established secure connection to %s at %s (%s)", remoteID, name, tlsCipherSuiteNames[c.ConnectionState().CipherSuite])
s.model.AddConnection(modelConn, hello)
s.curConMut.Lock()
@@ -318,8 +343,7 @@ func (s *Service) connect() {
continue
}
paused := s.model.IsPaused(deviceID)
if paused {
if deviceCfg.Paused {
continue
}
@@ -329,7 +353,7 @@ func (s *Service) connect() {
s.curConMut.Unlock()
priorityKnown := ok && connected
if priorityKnown && ct.Priority == bestDialerPrio {
if priorityKnown && ct.internalConn.priority == bestDialerPrio {
// Things are already as good as they can get.
continue
}
@@ -377,8 +401,8 @@ func (s *Service) connect() {
continue
}
if priorityKnown && dialerFactory.Priority() >= ct.Priority {
l.Debugf("Not dialing using %s as priority is less than current connection (%d >= %d)", dialerFactory, dialerFactory.Priority(), ct.Priority)
if priorityKnown && dialerFactory.Priority() >= ct.internalConn.priority {
l.Debugf("Not dialing using %s as priority is less than current connection (%d >= %d)", dialerFactory, dialerFactory.Priority(), ct.internalConn.priority)
continue
}

View File

@@ -9,6 +9,7 @@ package connections
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/url"
"time"
@@ -18,19 +19,61 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
)
type IntermediateConnection struct {
*tls.Conn
Type string
Priority int
// Connection is what we expose to the outside. It is a protocol.Connection
// that can be closed and has some metadata.
type Connection interface {
protocol.Connection
io.Closer
Type() string
RemoteAddr() net.Addr
}
type Connection struct {
IntermediateConnection
// completeConn is the aggregation of an internalConn and the
// protocol.Connection running on top of it. It implements the Connection
// interface.
type completeConn struct {
internalConn
protocol.Connection
}
func (c Connection) String() string {
return fmt.Sprintf("%s-%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type)
// internalConn is the raw TLS connection plus some metadata on where it
// came from (type, priority).
type internalConn struct {
*tls.Conn
connType connType
priority int
}
type connType int
const (
connTypeRelayClient connType = iota
connTypeRelayServer
connTypeTCPClient
connTypeTCPServer
)
func (t connType) String() string {
switch t {
case connTypeRelayClient:
return "relay-client"
case connTypeRelayServer:
return "relay-server"
case connTypeTCPClient:
return "tcp-client"
case connTypeTCPServer:
return "tcp-server"
default:
return "unknown-type"
}
}
func (c internalConn) Type() string {
return c.connType.String()
}
func (c internalConn) String() string {
return fmt.Sprintf("%s-%s/%s", c.LocalAddr(), c.RemoteAddr(), c.connType.String())
}
type dialerFactory interface {
@@ -41,12 +84,12 @@ type dialerFactory interface {
}
type genericDialer interface {
Dial(protocol.DeviceID, *url.URL) (IntermediateConnection, error)
Dial(protocol.DeviceID, *url.URL) (internalConn, error)
RedialFrequency() time.Duration
}
type listenerFactory interface {
New(*url.URL, *config.Wrapper, *tls.Config, chan IntermediateConnection, *nat.Service) genericListener
New(*url.URL, *config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
Enabled(config.Configuration) bool
}
@@ -73,7 +116,6 @@ type Model interface {
protocol.Model
AddConnection(conn Connection, hello protocol.HelloResult)
ConnectedTo(remoteID protocol.DeviceID) bool
IsPaused(remoteID protocol.DeviceID) bool
OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error
GetHello(protocol.DeviceID) protocol.HelloIntf
}

View File

@@ -30,23 +30,33 @@ type tcpDialer struct {
tlsCfg *tls.Config
}
func (d *tcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (IntermediateConnection, error) {
func (d *tcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
uri = fixupPort(uri)
conn, err := dialer.DialTimeout(uri.Scheme, uri.Host, 10*time.Second)
if err != nil {
l.Debugln(err)
return IntermediateConnection{}, err
return internalConn{}, err
}
err = dialer.SetTCPOptions(conn)
if err != nil {
l.Infoln(err)
}
err = dialer.SetTrafficClass(conn, d.cfg.Options().TrafficClass)
if err != nil {
l.Debugf("failed to set traffic class: %s", err)
}
tc := tls.Client(conn, d.tlsCfg)
err = tlsTimedHandshake(tc)
if err != nil {
tc.Close()
return IntermediateConnection{}, err
return internalConn{}, err
}
return IntermediateConnection{tc, "TCP (Client)", tcpPriority}, nil
return internalConn{tc, connTypeTCPClient, tcpPriority}, nil
}
func (d *tcpDialer) RedialFrequency() time.Duration {

View File

@@ -30,9 +30,10 @@ type tcpListener struct {
onAddressesChangedNotifier
uri *url.URL
cfg *config.Wrapper
tlsCfg *tls.Config
stop chan struct{}
conns chan IntermediateConnection
conns chan internalConn
factory listenerFactory
natService *nat.Service
@@ -107,6 +108,11 @@ func (t *tcpListener) Serve() {
l.Infoln(err)
}
err = dialer.SetTrafficClass(conn, t.cfg.Options().TrafficClass)
if err != nil {
l.Debugf("failed to set traffic class: %s", err)
}
tc := tls.Server(conn, t.tlsCfg)
err = tlsTimedHandshake(tc)
if err != nil {
@@ -115,7 +121,7 @@ func (t *tcpListener) Serve() {
continue
}
t.conns <- IntermediateConnection{tc, "TCP (Server)", tcpPriority}
t.conns <- internalConn{tc, connTypeTCPServer, tcpPriority}
}
}
@@ -173,9 +179,10 @@ func (t *tcpListener) Factory() listenerFactory {
type tcpListenerFactory struct{}
func (f *tcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan IntermediateConnection, 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),
cfg: cfg,
tlsCfg: tlsCfg,
conns: conns,
natService: natService,
@@ -192,7 +199,7 @@ func fixupPort(uri *url.URL) *url.URL {
copyURI := *uri
host, port, err := net.SplitHostPort(uri.Host)
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
if err != nil && strings.Contains(err.Error(), "missing port") {
// addr is on the form "1.2.3.4"
copyURI.Host = net.JoinHostPort(uri.Host, "22000")
} else if err == nil && port == "" {

View File

@@ -209,7 +209,7 @@ func blockKeyInto(o, hash []byte, folder uint32, file string) []byte {
}
o[0] = KeyTypeBlock
binary.BigEndian.PutUint32(o[keyPrefixLen:], folder)
copy(o[keyPrefixLen+keyFolderLen:], []byte(hash))
copy(o[keyPrefixLen+keyFolderLen:], hash)
copy(o[keyPrefixLen+keyFolderLen+keyHashLen:], []byte(file))
return o
}

View File

@@ -58,10 +58,7 @@ func setup() (*Instance, *BlockFinder) {
func dbEmpty(db *Instance) bool {
iter := db.NewIterator(util.BytesPrefix([]byte{KeyTypeBlock}), nil)
defer iter.Release()
if iter.Next() {
return false
}
return true
return !iter.Next()
}
func TestBlockMapAddUpdateWipe(t *testing.T) {

View File

@@ -4,7 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build ignore // this is a really tedious test for an old issue
// this is a really tedious test for an old issue
// +build ignore
package db_test

View File

@@ -635,7 +635,7 @@ func (db *Instance) deviceKeyInto(k []byte, folder, device, file []byte) []byte
k[0] = KeyTypeDevice
binary.BigEndian.PutUint32(k[keyPrefixLen:], db.folderIdx.ID(folder))
binary.BigEndian.PutUint32(k[keyPrefixLen+keyFolderLen:], db.deviceIdx.ID(device))
copy(k[keyPrefixLen+keyFolderLen+keyDeviceLen:], []byte(file))
copy(k[keyPrefixLen+keyFolderLen+keyDeviceLen:], file)
return k[:reqLen]
}
@@ -670,7 +670,7 @@ func (db *Instance) globalKey(folder, file []byte) []byte {
k := make([]byte, keyPrefixLen+keyFolderLen+len(file))
k[0] = KeyTypeGlobal
binary.BigEndian.PutUint32(k[keyPrefixLen:], db.folderIdx.ID(folder))
copy(k[keyPrefixLen+keyFolderLen:], []byte(file))
copy(k[keyPrefixLen+keyFolderLen:], file)
return k
}

View File

@@ -73,7 +73,7 @@ func (s *sizeTracker) addFile(f FileIntf) {
switch {
case f.IsDeleted():
s.Deleted++
case f.IsDirectory():
case f.IsDirectory() && !f.IsSymlink():
s.Directories++
case f.IsSymlink():
s.Symlinks++
@@ -93,7 +93,7 @@ func (s *sizeTracker) removeFile(f FileIntf) {
switch {
case f.IsDeleted():
s.Deleted--
case f.IsDirectory():
case f.IsDirectory() && !f.IsSymlink():
s.Directories--
case f.IsSymlink():
s.Symlinks--

View File

@@ -50,7 +50,7 @@ func (f FileInfoTruncated) FileSize() int64 {
if f.Deleted {
return 0
}
if f.IsDirectory() {
if f.IsDirectory() || f.IsSymlink() {
return protocol.SyntheticDirectorySize
}
return f.Size

View File

@@ -58,11 +58,13 @@ type FileInfoTruncated struct {
Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions,omitempty"`
ModifiedS int64 `protobuf:"varint,5,opt,name=modified_s,json=modifiedS,proto3" json:"modified_s,omitempty"`
ModifiedNs int32 `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3" json:"modified_ns,omitempty"`
ModifiedBy protocol.ShortID `protobuf:"varint,12,opt,name=modified_by,json=modifiedBy,proto3,customtype=protocol.ShortID" json:"modified_by"`
Deleted bool `protobuf:"varint,6,opt,name=deleted,proto3" json:"deleted,omitempty"`
Invalid bool `protobuf:"varint,7,opt,name=invalid,proto3" json:"invalid,omitempty"`
NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"`
Version protocol.Vector `protobuf:"bytes,9,opt,name=version" json:"version"`
Sequence int64 `protobuf:"varint,10,opt,name=sequence,proto3" json:"sequence,omitempty"`
SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlink_target,omitempty"`
}
func (m *FileInfoTruncated) Reset() { *m = FileInfoTruncated{} }
@@ -225,6 +227,19 @@ func (m *FileInfoTruncated) MarshalTo(data []byte) (int, error) {
i++
i = encodeVarintStructs(data, i, uint64(m.ModifiedNs))
}
if m.ModifiedBy != 0 {
data[i] = 0x60
i++
i = encodeVarintStructs(data, i, uint64(m.ModifiedBy))
}
if len(m.SymlinkTarget) > 0 {
data[i] = 0x8a
i++
data[i] = 0x1
i++
i = encodeVarintStructs(data, i, uint64(len(m.SymlinkTarget)))
i += copy(data[i:], m.SymlinkTarget)
}
return i, nil
}
@@ -315,6 +330,13 @@ func (m *FileInfoTruncated) ProtoSize() (n int) {
if m.ModifiedNs != 0 {
n += 1 + sovStructs(uint64(m.ModifiedNs))
}
if m.ModifiedBy != 0 {
n += 1 + sovStructs(uint64(m.ModifiedBy))
}
l = len(m.SymlinkTarget)
if l > 0 {
n += 2 + l + sovStructs(uint64(l))
}
return n
}
@@ -785,6 +807,54 @@ func (m *FileInfoTruncated) Unmarshal(data []byte) error {
break
}
}
case 12:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ModifiedBy", wireType)
}
m.ModifiedBy = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
m.ModifiedBy |= (protocol.ShortID(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 17:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field SymlinkTarget", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.SymlinkTarget = string(data[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipStructs(data[iNdEx:])
@@ -912,32 +982,33 @@ var (
)
var fileDescriptorStructs = []byte{
// 425 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x4f, 0x8b, 0xd3, 0x40,
0x1c, 0xed, 0xb4, 0xd9, 0xfe, 0xf9, 0xc5, 0xae, 0x3a, 0xc8, 0x32, 0x14, 0x4c, 0x43, 0x41, 0x08,
0x82, 0xa9, 0x56, 0xbc, 0x78, 0xdc, 0xc3, 0x82, 0x20, 0x22, 0x51, 0xd6, 0xe3, 0xd2, 0xcc, 0xfc,
0xda, 0x1d, 0x48, 0x67, 0x62, 0x66, 0x52, 0xa8, 0x9f, 0xc4, 0xe3, 0x7e, 0x9c, 0x1e, 0xfd, 0x04,
0xa2, 0xf5, 0x4b, 0x78, 0x94, 0x4e, 0xd2, 0x98, 0xe3, 0xde, 0xde, 0x9b, 0xdf, 0x7b, 0xbf, 0xf7,
0x66, 0x06, 0xc6, 0xc6, 0x16, 0x25, 0xb7, 0x26, 0xce, 0x0b, 0x6d, 0x35, 0xed, 0x8a, 0x74, 0xf2,
0x62, 0x2d, 0xed, 0x6d, 0x99, 0xc6, 0x5c, 0x6f, 0xe6, 0x6b, 0xbd, 0xd6, 0x73, 0x37, 0x4a, 0xcb,
0x95, 0x63, 0x8e, 0x38, 0x54, 0x59, 0x26, 0x6f, 0x5a, 0x72, 0xb3, 0x53, 0xdc, 0xde, 0x4a, 0xb5,
0x6e, 0xa1, 0x4c, 0xa6, 0xd5, 0x06, 0xae, 0xb3, 0x79, 0x8a, 0x79, 0x65, 0x9b, 0x7d, 0x01, 0xff,
0x4a, 0x66, 0x78, 0x8d, 0x85, 0x91, 0x5a, 0xd1, 0x97, 0x30, 0xd8, 0x56, 0x90, 0x91, 0x90, 0x44,
0xfe, 0xe2, 0x51, 0x7c, 0x32, 0xc5, 0xd7, 0xc8, 0xad, 0x2e, 0x2e, 0xbd, 0xfd, 0xcf, 0x69, 0x27,
0x39, 0xc9, 0xe8, 0x05, 0xf4, 0x05, 0x6e, 0x25, 0x47, 0xd6, 0x0d, 0x49, 0xf4, 0x20, 0xa9, 0xd9,
0xec, 0x0a, 0xfc, 0x7a, 0xe9, 0x7b, 0x69, 0x2c, 0x7d, 0x05, 0xc3, 0xda, 0x61, 0x18, 0x09, 0x7b,
0x91, 0xbf, 0x78, 0x18, 0x8b, 0x34, 0x6e, 0x65, 0xd7, 0x8b, 0x1b, 0xd9, 0x5b, 0xef, 0xfb, 0xdd,
0xb4, 0x33, 0xfb, 0xdb, 0x85, 0xc7, 0x47, 0xd5, 0x3b, 0xb5, 0xd2, 0x9f, 0x8b, 0x52, 0xf1, 0xa5,
0x45, 0x41, 0x29, 0x78, 0x6a, 0xb9, 0x41, 0x57, 0x72, 0x94, 0x38, 0x4c, 0x9f, 0x83, 0x67, 0x77,
0x79, 0xd5, 0xe3, 0x7c, 0x71, 0xf1, 0xbf, 0x78, 0x63, 0xdf, 0xe5, 0x98, 0x38, 0xcd, 0xd1, 0x6f,
0xe4, 0x37, 0x64, 0xbd, 0x90, 0x44, 0xbd, 0xc4, 0x61, 0x1a, 0x82, 0x9f, 0x63, 0xb1, 0x91, 0xa6,
0x6a, 0xe9, 0x85, 0x24, 0x1a, 0x27, 0xed, 0x23, 0xfa, 0x14, 0x60, 0xa3, 0x85, 0x5c, 0x49, 0x14,
0x37, 0x86, 0x9d, 0x39, 0xef, 0xe8, 0x74, 0xf2, 0x89, 0x32, 0x18, 0x08, 0xcc, 0xd0, 0xa2, 0x60,
0xfd, 0x90, 0x44, 0xc3, 0xe4, 0x44, 0x8f, 0x13, 0xa9, 0xb6, 0xcb, 0x4c, 0x0a, 0x36, 0xa8, 0x26,
0x35, 0xa5, 0xcf, 0xe0, 0x5c, 0xe9, 0x9b, 0x76, 0xee, 0xd0, 0x09, 0xc6, 0x4a, 0x7f, 0x6c, 0x25,
0xb7, 0xfe, 0x65, 0x74, 0xbf, 0x7f, 0x99, 0xc0, 0xd0, 0xe0, 0xd7, 0x12, 0x15, 0x47, 0x06, 0xae,
0x69, 0xc3, 0xe9, 0x14, 0xfc, 0xe6, 0x1e, 0xca, 0x30, 0x3f, 0x24, 0xd1, 0x59, 0xd2, 0x5c, 0xed,
0x43, 0xfd, 0xf4, 0x97, 0x4f, 0xf6, 0xbf, 0x83, 0xce, 0xfe, 0x10, 0x90, 0x1f, 0x87, 0x80, 0xfc,
0x3a, 0x04, 0x9d, 0xbb, 0x3f, 0x01, 0x49, 0xfb, 0x2e, 0xf8, 0xf5, 0xbf, 0x00, 0x00, 0x00, 0xff,
0xff, 0x2a, 0xae, 0x24, 0x77, 0xb3, 0x02, 0x00, 0x00,
// 442 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x5d, 0x8b, 0xd3, 0x40,
0x14, 0x6d, 0xb7, 0xd9, 0x7e, 0xdc, 0xd8, 0xd5, 0x1d, 0x64, 0x19, 0x0a, 0xb6, 0x4b, 0x41, 0x10,
0xc1, 0x54, 0x57, 0x7c, 0xf1, 0x71, 0x1f, 0x16, 0x04, 0x11, 0x19, 0x97, 0xf5, 0xb1, 0x34, 0x99,
0xdb, 0xec, 0x60, 0x32, 0x53, 0x33, 0x93, 0x42, 0xfd, 0x25, 0xbe, 0xb9, 0x3f, 0xa7, 0x8f, 0xfe,
0x02, 0xd1, 0xfa, 0x47, 0x9c, 0xce, 0xa4, 0x31, 0x8f, 0xfb, 0x10, 0xb8, 0xe7, 0x9e, 0x73, 0xee,
0x3d, 0x93, 0x0b, 0x43, 0x6d, 0x8a, 0x32, 0x31, 0x3a, 0x5a, 0x15, 0xca, 0x28, 0x72, 0xc4, 0xe3,
0xd1, 0x8b, 0x54, 0x98, 0xdb, 0x32, 0x8e, 0x12, 0x95, 0xcf, 0x52, 0x95, 0xaa, 0x99, 0xa3, 0xe2,
0x72, 0xe9, 0x90, 0x03, 0xae, 0xf2, 0x96, 0xd1, 0x9b, 0x86, 0x5c, 0x6f, 0x64, 0x62, 0x6e, 0x85,
0x4c, 0x1b, 0x55, 0x26, 0x62, 0x3f, 0x21, 0x51, 0xd9, 0x2c, 0xc6, 0x95, 0xb7, 0x4d, 0x3f, 0x43,
0x78, 0x25, 0x32, 0xbc, 0xc1, 0x42, 0x0b, 0x25, 0xc9, 0x4b, 0xe8, 0xad, 0x7d, 0x49, 0xdb, 0xe7,
0xed, 0x67, 0xe1, 0xc5, 0xa3, 0xe8, 0x60, 0x8a, 0x6e, 0x30, 0x31, 0xaa, 0xb8, 0x0c, 0xb6, 0xbf,
0x26, 0x2d, 0x76, 0x90, 0x91, 0x33, 0xe8, 0x72, 0x5c, 0x8b, 0x04, 0xe9, 0x91, 0x35, 0x3c, 0x60,
0x15, 0x9a, 0x5e, 0x41, 0x58, 0x0d, 0x7d, 0x2f, 0xb4, 0x21, 0xaf, 0xa0, 0x5f, 0x39, 0xb4, 0x9d,
0xdc, 0xb1, 0x93, 0x1f, 0x46, 0x3c, 0x8e, 0x1a, 0xbb, 0xab, 0xc1, 0xb5, 0xec, 0x6d, 0xf0, 0xfd,
0x6e, 0xd2, 0x9a, 0xfe, 0xe8, 0xc0, 0xe9, 0x5e, 0xf5, 0x4e, 0x2e, 0xd5, 0x75, 0x51, 0xca, 0x64,
0x61, 0x90, 0x13, 0x02, 0x81, 0x5c, 0xe4, 0xe8, 0x42, 0x0e, 0x98, 0xab, 0xc9, 0x73, 0x08, 0xcc,
0x66, 0xe5, 0x73, 0x9c, 0x5c, 0x9c, 0xfd, 0x0f, 0x5e, 0xdb, 0x2d, 0xcb, 0x9c, 0x66, 0xef, 0xd7,
0xe2, 0x1b, 0xd2, 0x8e, 0xd5, 0x76, 0x98, 0xab, 0xc9, 0x39, 0x84, 0x2b, 0x2c, 0x72, 0xa1, 0x7d,
0xca, 0xc0, 0x52, 0x43, 0xd6, 0x6c, 0x91, 0x27, 0x00, 0xb9, 0xe2, 0x62, 0x29, 0x90, 0xcf, 0x35,
0x3d, 0x76, 0xde, 0xc1, 0xa1, 0xf3, 0x89, 0x50, 0xe8, 0x71, 0xcc, 0xd0, 0xe6, 0xa3, 0x5d, 0xcb,
0xf5, 0xd9, 0x01, 0xee, 0x19, 0x21, 0xd7, 0x8b, 0x4c, 0x70, 0xda, 0xf3, 0x4c, 0x05, 0xc9, 0x53,
0x38, 0x91, 0x6a, 0xde, 0xdc, 0xdb, 0x77, 0x82, 0xa1, 0x54, 0x1f, 0x1b, 0x9b, 0x1b, 0x77, 0x19,
0xdc, 0xef, 0x2e, 0x23, 0xe8, 0x6b, 0xfc, 0x5a, 0xa2, 0xb4, 0x97, 0x01, 0x97, 0xb4, 0xc6, 0x64,
0x02, 0x61, 0xfd, 0x0e, 0xbb, 0x31, 0xb4, 0xf4, 0x31, 0xab, 0x9f, 0xf6, 0x41, 0xef, 0x53, 0xe9,
0x4d, 0x9e, 0x09, 0xf9, 0x65, 0x6e, 0x16, 0x45, 0x8a, 0x86, 0x9e, 0xba, 0x1f, 0x3d, 0xac, 0xba,
0xd7, 0xae, 0xe9, 0x2f, 0x74, 0xf9, 0x78, 0xfb, 0x67, 0xdc, 0xda, 0xee, 0xc6, 0xed, 0x9f, 0xf6,
0xfb, 0xbd, 0x1b, 0xb7, 0xee, 0xfe, 0x8e, 0xdb, 0x71, 0xd7, 0xe5, 0x7b, 0xfd, 0x2f, 0x00, 0x00,
0xff, 0xff, 0xb1, 0x2f, 0x12, 0xb6, 0xda, 0x02, 0x00, 0x00,
}

View File

@@ -28,9 +28,11 @@ message FileInfoTruncated {
uint32 permissions = 4;
int64 modified_s = 5;
int32 modified_ns = 11;
uint64 modified_by = 12 [(gogoproto.customtype) = "protocol.ShortID", (gogoproto.nullable) = false];
bool deleted = 6;
bool invalid = 7;
bool no_permissions = 8;
protocol.Vector version = 9 [(gogoproto.nullable) = false];
int64 sequence = 10;
string symlink_target = 17;
}

View File

@@ -10,6 +10,9 @@ import (
"fmt"
"net"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// Dial tries dialing via proxy if a proxy is configured, and falls back to
@@ -75,3 +78,22 @@ func SetTCPOptions(conn net.Conn) error {
return fmt.Errorf("unknown connection type %T", conn)
}
}
func SetTrafficClass(conn net.Conn, class int) error {
switch conn := conn.(type) {
case *net.TCPConn:
e1 := ipv4.NewConn(conn).SetTOS(class)
e2 := ipv6.NewConn(conn).SetTrafficClass(class)
if e1 != nil {
return e1
}
return e2
case dialerConn:
return SetTrafficClass(conn.Conn, class)
default:
return fmt.Errorf("unknown connection type %T", conn)
}
}

View File

@@ -51,7 +51,7 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister) (Finder
Supervisor: suture.NewSimple("local"),
myID: id,
addrList: addrList,
localBcastTick: time.Tick(BroadcastInterval),
localBcastTick: time.NewTicker(BroadcastInterval).C,
forcedBcastTick: make(chan time.Time),
localBcastStart: time.Now(),
cache: newCache(),

View File

@@ -382,21 +382,20 @@ var (
)
var fileDescriptorLocal = []byte{
// 241 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x4f, 0x4e, 0x84, 0x30,
0x14, 0xc6, 0x29, 0x24, 0x66, 0xa6, 0x63, 0x5c, 0x10, 0x17, 0xc4, 0x98, 0x42, 0x5c, 0xb1, 0x11,
0x16, 0x7a, 0x01, 0x09, 0x9b, 0x6e, 0xb9, 0x80, 0x81, 0xb6, 0x32, 0x2f, 0xc1, 0x3e, 0x43, 0x61,
0x12, 0x6f, 0xe3, 0x05, 0xbc, 0x07, 0x4b, 0xd7, 0x2e, 0x1a, 0xad, 0x17, 0x31, 0xe9, 0x68, 0x86,
0xdd, 0xf7, 0xfd, 0xf2, 0x7b, 0x7f, 0xe8, 0x6e, 0x40, 0xd1, 0x0e, 0xc5, 0xcb, 0x88, 0x13, 0xc6,
0x1b, 0x09, 0x46, 0xe0, 0x41, 0x8d, 0x57, 0xb7, 0x3d, 0x4c, 0xfb, 0xb9, 0x2b, 0x04, 0x3e, 0x97,
0x3d, 0xf6, 0x58, 0x7a, 0xa1, 0x9b, 0x9f, 0x7c, 0xf3, 0xc5, 0xa7, 0xe3, 0xe0, 0xcd, 0x3b, 0xa1,
0x9b, 0x07, 0xad, 0x71, 0xd6, 0x42, 0xc5, 0x0d, 0x0d, 0x41, 0x26, 0x24, 0x23, 0xf9, 0x79, 0x55,
0x2d, 0x36, 0x0d, 0x3e, 0x6d, 0x7a, 0xbf, 0xda, 0x67, 0x5e, 0xb5, 0x98, 0xf6, 0xa0, 0xfb, 0x55,
0x1a, 0xa0, 0x3b, 0x9e, 0x10, 0x38, 0x14, 0xb5, 0x3a, 0x80, 0x50, 0xbc, 0x76, 0x36, 0x0d, 0x79,
0xdd, 0x84, 0x20, 0xe3, 0x6b, 0xba, 0x6d, 0xa5, 0x1c, 0x95, 0x31, 0xca, 0x24, 0x61, 0x16, 0xe5,
0xdb, 0xe6, 0x04, 0xe2, 0x92, 0xee, 0x40, 0x9b, 0xa9, 0xd5, 0x42, 0x3d, 0x82, 0x4c, 0xa2, 0x8c,
0xe4, 0x51, 0x75, 0xe1, 0x6c, 0x4a, 0xf9, 0x1f, 0xe6, 0x75, 0x43, 0xff, 0x15, 0x2e, 0xab, 0xcb,
0xe5, 0x9b, 0x05, 0x8b, 0x63, 0xe4, 0xc3, 0x31, 0xf2, 0xe5, 0x58, 0xf0, 0xf6, 0xc3, 0x48, 0x77,
0xe6, 0x3f, 0xb8, 0xfb, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00,
0x00,
// 235 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0xc9, 0x4f, 0x4e,
0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x48, 0xc9, 0x2c, 0x4e, 0xce, 0x2f, 0x4b,
0x2d, 0x92, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf,
0x4f, 0xcf, 0xd7, 0x07, 0x2b, 0x48, 0x2a, 0x4d, 0x03, 0xf3, 0xc0, 0x1c, 0x30, 0x0b, 0xa2, 0x51,
0x69, 0x2d, 0x23, 0x17, 0x87, 0x63, 0x5e, 0x5e, 0x7e, 0x69, 0x5e, 0x72, 0xaa, 0x50, 0x10, 0x17,
0x53, 0x66, 0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xd3, 0x89, 0x7b, 0xf2, 0x0c, 0xb7,
0xee, 0xc9, 0x9b, 0x20, 0x99, 0x57, 0x5c, 0x99, 0x97, 0x5c, 0x92, 0x91, 0x99, 0x97, 0x8e, 0xc4,
0xca, 0xc9, 0x4c, 0x82, 0x58, 0x91, 0x9c, 0x9f, 0xa3, 0xe7, 0x92, 0x5a, 0x96, 0x99, 0x9c, 0xea,
0xe9, 0xf2, 0xe8, 0x9e, 0x3c, 0x93, 0xa7, 0x4b, 0x10, 0xd0, 0x34, 0x21, 0x19, 0x2e, 0xce, 0xc4,
0x94, 0x94, 0xa2, 0xd4, 0xe2, 0xe2, 0xd4, 0x62, 0x09, 0x26, 0x05, 0x66, 0x0d, 0xce, 0x20, 0x84,
0x80, 0x90, 0x3e, 0x17, 0x77, 0x66, 0x5e, 0x71, 0x49, 0x22, 0xd0, 0xf6, 0x78, 0xa0, 0xd5, 0xcc,
0x40, 0xab, 0x99, 0x9d, 0xf8, 0x80, 0xda, 0xb9, 0x3c, 0xa1, 0xc2, 0x40, 0x63, 0xb8, 0x60, 0x4a,
0x3c, 0x53, 0x9c, 0x44, 0x4e, 0x3c, 0x94, 0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0x02, 0x10, 0x3f,
0x78, 0x24, 0xc7, 0xb0, 0xe0, 0xb1, 0x1c, 0x63, 0x12, 0x1b, 0xd8, 0x05, 0xc6, 0x80, 0x00, 0x00,
0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00, 0x00,
}

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