mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-14 00:39:13 -05:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8b43ae598 | ||
|
|
567f19bf68 | ||
|
|
5cd4cd2271 | ||
|
|
4180569443 | ||
|
|
5f4a92c8e6 | ||
|
|
ccf3fed950 | ||
|
|
3626003f68 | ||
|
|
11cb040ad1 | ||
|
|
c1761cab49 | ||
|
|
25b25b5434 | ||
|
|
8bdf66d9c0 | ||
|
|
ccebdd142a | ||
|
|
e952da7f91 | ||
|
|
5bd1e4a167 | ||
|
|
6d3de41751 | ||
|
|
f11bac6705 | ||
|
|
c23a601cc6 | ||
|
|
36d4c69fd6 | ||
|
|
ca7e7fa0c4 | ||
|
|
1f6dd5dbb9 | ||
|
|
db52646655 | ||
|
|
8bb18fa988 | ||
|
|
f0edaf2f8c | ||
|
|
d632e3aa1b | ||
|
|
f0e58fa804 | ||
|
|
ceced09d02 | ||
|
|
7d48115b90 | ||
|
|
d2205228fb | ||
|
|
5417fb7287 | ||
|
|
3f59d6daff | ||
|
|
0d659933aa | ||
|
|
3e8eabe833 | ||
|
|
badfc77339 | ||
|
|
c51d3e59ea | ||
|
|
c0d02a65c3 | ||
|
|
3a203b8d83 | ||
|
|
feecdcc7a4 | ||
|
|
51ad533be6 | ||
|
|
29da0bc8f5 | ||
|
|
bccf7fc2a8 | ||
|
|
7b6b5981c4 | ||
|
|
9463192224 | ||
|
|
d1689f0012 | ||
|
|
8ed67fe349 | ||
|
|
90a1d99785 | ||
|
|
e215cf6fb8 | ||
|
|
e827c0bd94 | ||
|
|
8dd7e4e6b5 | ||
|
|
a2b94f4e06 | ||
|
|
8b0037ffab | ||
|
|
4ab03f3bef | ||
|
|
3c3db52f49 | ||
|
|
5b1e884659 | ||
|
|
7ec6740e26 | ||
|
|
f12b8c19be | ||
|
|
76174d31ce | ||
|
|
5042248260 | ||
|
|
1df6589533 | ||
|
|
12f76b448c | ||
|
|
f112ef34f6 | ||
|
|
e4b57a978f | ||
|
|
b51e09e0e8 | ||
|
|
a3ba3f895c | ||
|
|
947a129e12 | ||
|
|
f3fe6a6cbd | ||
|
|
9d150bef9f | ||
|
|
65be18cc93 | ||
|
|
e27ea63900 | ||
|
|
aa96f7b660 | ||
|
|
053690d885 | ||
|
|
b9fc6397a3 | ||
|
|
19b9f15da3 | ||
|
|
0b9441e1a4 | ||
|
|
2324d7420c | ||
|
|
c6b2ca8b19 | ||
|
|
83ea8dc577 | ||
|
|
d898277f62 | ||
|
|
a289cfb986 | ||
|
|
d603998617 | ||
|
|
8bf9f4f5ab | ||
|
|
2c4e6f2926 | ||
|
|
21fbbc50cd | ||
|
|
99512418da | ||
|
|
c2f2d8771f | ||
|
|
179c9ee8cc | ||
|
|
eb8a505287 | ||
|
|
a7482a3644 | ||
|
|
a692348336 | ||
|
|
285dcc30cf | ||
|
|
10021c97a6 | ||
|
|
f6416285db |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ coverage.xml
|
||||
syncthing.md5
|
||||
syncthing.exe.md5
|
||||
RELEASE
|
||||
deb
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -5,6 +5,7 @@ Alexander Graf <register-github@alex-graf.de>
|
||||
Andrew Dunham <andrew@du.nham.ca>
|
||||
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
Bart De Vries <devriesb@gmail.com>
|
||||
Ben Curthoys <ben@bencurthoys.com>
|
||||
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||
Ben Sidhom <bsidhom@gmail.com>
|
||||
@@ -13,6 +14,7 @@ Brendan Long <self@brendanlong.com>
|
||||
Caleb Callaway <enlightened.despot@gmail.com>
|
||||
Carsten Hagemann <moter8@gmail.com>
|
||||
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
|
||||
Chris Howie <me@chrishowie.com>
|
||||
Chris Joel <chris@scriptolo.gy>
|
||||
Colin Kennedy <moshen.colin@gmail.com>
|
||||
Daniel Martí <mvdan@mvdan.cc>
|
||||
|
||||
@@ -33,7 +33,7 @@ latest info on Transifex.
|
||||
|
||||
Every contribution is welcome. If you want to contribute but are unsure
|
||||
where to start, any open issues are fair game! Be prepared for a
|
||||
[certain amount of review](https://github.com/syncthing/syncthing/wiki/FAQ#why-are-you-being-so-hard-on-my-pull-request);
|
||||
[certain amount of review](http://docs.syncthing.net/dev/intro.html#why-are-you-being-so-hard-on-my-pull-request);
|
||||
it's all in the name of quality. :) Following the points below will make this
|
||||
a smoother process.
|
||||
|
||||
@@ -100,12 +100,12 @@ International License. You retain the copyright to code you have
|
||||
written.
|
||||
|
||||
When accepting your first contribution, the maintainer of the project
|
||||
will ensure that you are added to the AUTHORS file. You are welcome to
|
||||
add yourself as a separate commit in your first pull request.
|
||||
will ensure that you are added to the AUTHORS file, the NICKS file and
|
||||
the list of authors in the about box.
|
||||
|
||||
## Building
|
||||
|
||||
[See the documentation](https://github.com/syncthing/syncthing/wiki/Building)
|
||||
[See the documentation](http://docs.syncthing.net/dev/building.html)
|
||||
on how to get started with a build environment.
|
||||
|
||||
## Branches
|
||||
@@ -133,7 +133,7 @@ Yes please!
|
||||
|
||||
## Documentation
|
||||
|
||||
[Over here!](https://github.com/syncthing/syncthing/wiki)
|
||||
[Over here!](http://docs.syncthing.net/)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
2
Godeps/Godeps.json
generated
2
Godeps/Godeps.json
generated
@@ -11,7 +11,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/logger",
|
||||
"Rev": "4d4e2801954c5581e4c2a80a3d3beb3b3645fd04"
|
||||
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/luhn",
|
||||
|
||||
8
Godeps/_workspace/src/github.com/calmh/logger/logger.go
generated
vendored
8
Godeps/_workspace/src/github.com/calmh/logger/logger.go
generated
vendored
@@ -6,6 +6,7 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -37,6 +38,13 @@ type Logger struct {
|
||||
var DefaultLogger = New()
|
||||
|
||||
func New() *Logger {
|
||||
if os.Getenv("LOGGER_DISCARD") != "" {
|
||||
// Hack to completely disable logging, for example when running benchmarks.
|
||||
return &Logger{
|
||||
logger: log.New(ioutil.Discard, "", 0),
|
||||
}
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
logger: log.New(os.Stdout, "", log.Ltime),
|
||||
}
|
||||
|
||||
2
NICKS
2
NICKS
@@ -18,6 +18,7 @@ brendanlong <self@brendanlong.com>
|
||||
bsidhom <bsidhom@gmail.com>
|
||||
calmh <jakob@nym.se>
|
||||
cdata <chris@scriptolo.gy>
|
||||
cdhowie <me@chrishowie.com>
|
||||
ceh <emil@hessman.se>
|
||||
cqcallaw <enlightened.despot@gmail.com>
|
||||
dzarda <dzardacz@gmail.com>
|
||||
@@ -35,6 +36,7 @@ krozycki <rozycki.karol@gmail.com>
|
||||
marcindziadus <dziadus.marcin@gmail.com>
|
||||
marclaporte <marc@marclaporte.com>
|
||||
moshen <moshen.colin@gmail.com>
|
||||
mogwa1 <devriesb@gmail.com>
|
||||
mvdan <mvdan@mvdan.cc>
|
||||
pascalj <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
peterhoeg <peter@speartail.com>
|
||||
|
||||
17
README.md
17
README.md
@@ -1,11 +1,11 @@
|
||||
syncthing
|
||||
Syncthing
|
||||
=========
|
||||
|
||||
[](http://build.syncthing.net/job/syncthing/lastBuild/)
|
||||
[](http://godoc.org/github.com/syncthing/syncthing)
|
||||
[](https://www.mozilla.org/MPL/2.0/)
|
||||
|
||||
This is the `syncthing` project which pursues the following goals:
|
||||
This is the Syncthing project which pursues the following goals:
|
||||
|
||||
1. Define a protocol for synchronization of a folder between a number of
|
||||
collaborating devices. This protocol should be well defined, unambiguous,
|
||||
@@ -18,16 +18,16 @@ This is the `syncthing` project which pursues the following goals:
|
||||
alternative, compatible implementations of the protocol will arise.
|
||||
|
||||
The two are evolving together; the protocol is not to be considered
|
||||
stable until syncthing 1.0 is released, at which point it is locked down
|
||||
stable until Syncthing 1.0 is released, at which point it is locked down
|
||||
for incompatible changes.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Take a look at the [getting started
|
||||
guide](https://github.com/syncthing/syncthing/wiki/Getting-Started).
|
||||
guide](http://docs.syncthing.net/intro/getting-started.html).
|
||||
|
||||
There are a few examples for keeping syncthing running in the background
|
||||
There are a few examples for keeping Syncthing running in the background
|
||||
on your system in [the etc directory](https://github.com/syncthing/syncthing/blob/master/etc).
|
||||
|
||||
There is an IRC channel, `#syncthing` on Freenode, for talking directly
|
||||
@@ -37,7 +37,7 @@ Building
|
||||
--------
|
||||
|
||||
Building Syncthing from source is easy, and there's a
|
||||
[guide](https://github.com/syncthing/syncthing/wiki/Building).
|
||||
[guide](http://docs.syncthing.net/dev/building.html).
|
||||
that describes it for both Unix and Windows systems.
|
||||
|
||||
Signed Releases
|
||||
@@ -51,9 +51,8 @@ available in the md5sum.txt.asc and sha1sum.txt.asc files.
|
||||
Documentation
|
||||
=============
|
||||
|
||||
The [syncthing
|
||||
documentation](https://github.com/syncthing/syncthing/wiki/) is on the
|
||||
Github wiki.
|
||||
Please see the [Syncthing
|
||||
documentation site](http://docs.syncthing.net/dev/).
|
||||
|
||||
All code is licensed under the
|
||||
[MPLv2 License](https://github.com/syncthing/syncthing/blob/master/LICENSE).
|
||||
|
||||
50
benchfilter.go
Normal file
50
benchfilter.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Neatly format benchmarking output which otherwise looks like crap.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
var (
|
||||
benchRe = regexp.MustCompile(`^Bench`)
|
||||
spacesRe = regexp.MustCompile(`\s+`)
|
||||
numbersRe = regexp.MustCompile(`\b[\d\.]+\b`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
|
||||
br := bufio.NewScanner(os.Stdin)
|
||||
n := 0
|
||||
|
||||
for br.Scan() {
|
||||
line := br.Bytes()
|
||||
|
||||
if benchRe.Match(line) {
|
||||
n++
|
||||
line = spacesRe.ReplaceAllLiteral(line, []byte("\t"))
|
||||
line = numbersRe.ReplaceAllFunc(line, func(n []byte) []byte {
|
||||
return []byte(fmt.Sprintf("%12s", n))
|
||||
})
|
||||
tw.Write(line)
|
||||
tw.Write([]byte("\n"))
|
||||
} else if n > 0 && bytes.HasPrefix(line, []byte("ok")) {
|
||||
n = 0
|
||||
tw.Flush()
|
||||
fmt.Printf("%s\n\n", line)
|
||||
}
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
105
build.go
105
build.go
@@ -110,6 +110,9 @@ func main() {
|
||||
case "test":
|
||||
test("./...")
|
||||
|
||||
case "bench":
|
||||
bench("./...")
|
||||
|
||||
case "assets":
|
||||
assets()
|
||||
|
||||
@@ -131,6 +134,9 @@ func main() {
|
||||
case "zip":
|
||||
buildZip()
|
||||
|
||||
case "deb":
|
||||
buildDeb()
|
||||
|
||||
case "clean":
|
||||
clean()
|
||||
|
||||
@@ -179,6 +185,11 @@ func test(pkg string) {
|
||||
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
|
||||
}
|
||||
|
||||
func bench(pkg string) {
|
||||
setBuildEnv()
|
||||
runPrint("go", "test", "-run", "NONE", "-bench", ".", pkg)
|
||||
}
|
||||
|
||||
func install(pkg string, tags []string) {
|
||||
os.Setenv("GOBIN", "./bin")
|
||||
args := []string{"install", "-v", "-ldflags", ldflags()}
|
||||
@@ -272,6 +283,95 @@ func buildZip() {
|
||||
log.Println(filename)
|
||||
}
|
||||
|
||||
func buildDeb() {
|
||||
os.RemoveAll("deb")
|
||||
|
||||
// "goarch" here is set to whatever the Debian packages expect. We correct
|
||||
// "it to what we actually know how to build and keep the Debian variant
|
||||
// "name in "debarch".
|
||||
debarch := goarch
|
||||
switch goarch {
|
||||
case "i386":
|
||||
goarch = "386"
|
||||
case "armel", "armhf":
|
||||
goarch = "arm"
|
||||
}
|
||||
|
||||
build("./cmd/syncthing", []string{"noupgrade"})
|
||||
|
||||
files := []archiveFile{
|
||||
{src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
|
||||
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
|
||||
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
|
||||
{src: "syncthing", dst: "deb/usr/bin/syncthing", perm: 0755},
|
||||
{src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
|
||||
{src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
|
||||
{src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
|
||||
{src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
|
||||
{src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
|
||||
{src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
|
||||
{src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
|
||||
{src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
|
||||
{src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
|
||||
{src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
|
||||
}
|
||||
|
||||
for _, file := range listFiles("extra") {
|
||||
files = append(files, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
|
||||
}
|
||||
|
||||
for _, af := range files {
|
||||
if err := copyFile(af.src, af.dst, af.perm); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
control := `Package: syncthing
|
||||
Architecture: {{arch}}
|
||||
Depends: libc6
|
||||
Version: {{version}}
|
||||
Maintainer: Syncthing Release Management <release@syncthing.net>
|
||||
Description: Open Source Continuous File Synchronization
|
||||
Syncthing does bidirectional synchronization of files between two or
|
||||
more computers.
|
||||
`
|
||||
changelog := `syncthing ({{version}}); urgency=medium
|
||||
|
||||
* Packaging of {{version}}.
|
||||
|
||||
-- Jakob Borg <jakob@nym.se> {{date}}
|
||||
`
|
||||
|
||||
control = strings.Replace(control, "{{arch}}", debarch, -1)
|
||||
control = strings.Replace(control, "{{version}}", version[1:], -1)
|
||||
changelog = strings.Replace(changelog, "{{arch}}", debarch, -1)
|
||||
changelog = strings.Replace(changelog, "{{version}}", version[1:], -1)
|
||||
changelog = strings.Replace(changelog, "{{date}}", time.Now().Format(time.RFC1123), -1)
|
||||
|
||||
os.MkdirAll("deb/DEBIAN", 0755)
|
||||
ioutil.WriteFile("deb/DEBIAN/control", []byte(control), 0644)
|
||||
ioutil.WriteFile("deb/DEBIAN/compat", []byte("9\n"), 0644)
|
||||
ioutil.WriteFile("deb/DEBIAN/changelog", []byte(changelog), 0644)
|
||||
|
||||
}
|
||||
|
||||
func copyFile(src, dst string, perm os.FileMode) error {
|
||||
dstDir := filepath.Dir(dst)
|
||||
os.MkdirAll(dstDir, 0755) // ignore error
|
||||
srcFd, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFd.Close()
|
||||
dstFd, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFd.Close()
|
||||
_, err = io.Copy(dstFd, srcFd)
|
||||
return err
|
||||
}
|
||||
|
||||
func listFiles(dir string) []string {
|
||||
var res []string
|
||||
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||
@@ -480,8 +580,9 @@ func runPipe(file, cmd string, args ...string) {
|
||||
}
|
||||
|
||||
type archiveFile struct {
|
||||
src string
|
||||
dst string
|
||||
src string
|
||||
dst string
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
func tarGz(out string, files []archiveFile) {
|
||||
|
||||
20
build.sh
20
build.sh
@@ -17,8 +17,11 @@ case "${1:-default}" in
|
||||
ulimit -t 60 &>/dev/null || true
|
||||
ulimit -d 512000 &>/dev/null || true
|
||||
ulimit -m 512000 &>/dev/null || true
|
||||
go run build.go test
|
||||
;;
|
||||
|
||||
go run build.go "$1"
|
||||
bench)
|
||||
LOGGER_DISCARD=1 go run build.go bench | go run benchfilter.go
|
||||
;;
|
||||
|
||||
tar)
|
||||
@@ -41,8 +44,14 @@ case "${1:-default}" in
|
||||
go run build.go "$1"
|
||||
;;
|
||||
|
||||
transifex)
|
||||
go run build.go "$1"
|
||||
prerelease)
|
||||
go run build.go transifex
|
||||
git add -A gui/assets/ internal/auto/
|
||||
pushd man ; ./refresh.sh ; popd
|
||||
git add -A man
|
||||
echo
|
||||
echo Changelog:
|
||||
go run changelog.go
|
||||
;;
|
||||
|
||||
noupgrade)
|
||||
@@ -118,8 +127,9 @@ case "${1:-default}" in
|
||||
-e "STTRACE=$STTRACE" \
|
||||
syncthing/build:latest \
|
||||
sh -c './build.sh clean \
|
||||
&& ./build.sh all \
|
||||
&& STTRACE=all ./build.sh test-cov'
|
||||
&& ./build.sh test-cov \
|
||||
&& ./build.sh bench \
|
||||
&& ./build.sh all'
|
||||
;;
|
||||
|
||||
docker-test)
|
||||
|
||||
@@ -19,7 +19,8 @@ no-docs-typos() {
|
||||
grep -v 4b76ec40c07078beaa2c5e250ed7d9bd6276a718 |\
|
||||
grep -v ffc39dfbcb34eacc3ea12327a02b6e7741a2c207 |\
|
||||
grep -v 32a76901a91ff0f663db6f0830e0aedec946e4d0 |\
|
||||
grep -v af3288043a49bcc28f8ae3060852a09de552fe5f
|
||||
grep -v af3288043a49bcc28f8ae3060852a09de552fe5f |\
|
||||
grep -v 3626003f680bad3e63677982576d3a05421e88e9
|
||||
}
|
||||
|
||||
print-missing-authors() {
|
||||
@@ -29,7 +30,7 @@ print-missing-authors() {
|
||||
}
|
||||
|
||||
print-missing-copyright() {
|
||||
find . -name \*.go | xargs egrep -L 'Copyright \(C\)|automatically generated' | grep -v Godeps | grep -v internal/auto/
|
||||
find . -name \*.go | xargs egrep -L 'Copyright|automatically generated' | grep -v Godeps | grep -v internal/auto/
|
||||
}
|
||||
|
||||
authors=$(print-missing-authors)
|
||||
|
||||
@@ -65,13 +65,13 @@ func main() {
|
||||
fmt.Printf("[block] F:%q H:%x N:%q I:%d\n", folder, hash, name, binary.BigEndian.Uint32(it.Value()))
|
||||
|
||||
case db.KeyTypeDeviceStatistic:
|
||||
fmt.Printf("[dstat]\n %x\n %x", it.Key(), it.Value())
|
||||
fmt.Printf("[dstat]\n %x\n %x\n", it.Key(), it.Value())
|
||||
|
||||
case db.KeyTypeFolderStatistic:
|
||||
fmt.Printf("[fstat]\n %x\n %x", it.Key(), it.Value())
|
||||
fmt.Printf("[fstat]\n %x\n %x\n", it.Key(), it.Value())
|
||||
|
||||
default:
|
||||
fmt.Printf("[???]\n %x\n %x", it.Key(), it.Value())
|
||||
fmt.Printf("[???]\n %x\n %x\n", it.Key(), it.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,22 +485,16 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Start or stop usage reporting as appropriate
|
||||
// Fixup usage reporting settings
|
||||
|
||||
if curAcc := cfg.Options().URAccepted; newCfg.Options.URAccepted > curAcc {
|
||||
// UR was enabled
|
||||
newCfg.Options.URAccepted = usageReportVersion
|
||||
newCfg.Options.URUniqueID = randomString(8)
|
||||
err := sendUsageReport(s.model)
|
||||
if err != nil {
|
||||
l.Infoln("Usage report:", err)
|
||||
}
|
||||
go usageReportingLoop(s.model)
|
||||
} else if newCfg.Options.URAccepted < curAcc {
|
||||
// UR was disabled
|
||||
newCfg.Options.URAccepted = -1
|
||||
newCfg.Options.URUniqueID = ""
|
||||
stopUsageReporting()
|
||||
}
|
||||
|
||||
// Activate and save
|
||||
|
||||
@@ -279,7 +279,7 @@ func main() {
|
||||
l.Fatalln(dir, "is not a directory")
|
||||
}
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
err = osutil.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
l.Fatalln("generate:", err)
|
||||
}
|
||||
@@ -677,16 +677,13 @@ func syncthingMain() {
|
||||
cfg.SetOptions(opts)
|
||||
cfg.Save()
|
||||
}
|
||||
go usageReportingLoop(m)
|
||||
go func() {
|
||||
time.Sleep(10 * time.Minute)
|
||||
err := sendUsageReport(m)
|
||||
if err != nil {
|
||||
l.Infoln("Usage report:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// The usageReportingManager registers itself to listen to configuration
|
||||
// changes, and there's nothing more we need to tell it from the outside.
|
||||
// Hence we don't keep the returned pointer.
|
||||
newUsageReportingManager(m, cfg)
|
||||
|
||||
if opts.RestartOnWakeup {
|
||||
go standbyMonitor()
|
||||
}
|
||||
@@ -715,25 +712,28 @@ func syncthingMain() {
|
||||
}
|
||||
|
||||
func dbOpts() *opt.Options {
|
||||
// Calculate a sutiable database block cache capacity. We start at the
|
||||
// default of 8 MiB and use larger values for machines with more memory.
|
||||
// In reality, the database will use twice the amount we calculate here,
|
||||
// as it also has two write buffers each sized at half the block cache.
|
||||
// Calculate a suitable database block cache capacity.
|
||||
|
||||
// Default is 8 MiB.
|
||||
blockCacheCapacity := 8 << 20
|
||||
if bytes, err := memorySize(); err == nil {
|
||||
if bytes > 74<<30 {
|
||||
// At 74 GiB of RAM, we hit a 256 MiB block cache (per the
|
||||
// calculations below). There's probably no point in growing the
|
||||
// cache beyond this point.
|
||||
blockCacheCapacity = 256 << 20
|
||||
} else if bytes > 8<<30 {
|
||||
// Slowly grow from 128 MiB at 8 GiB of RAM up to 256 MiB for a
|
||||
// ~74 GiB RAM machine
|
||||
blockCacheCapacity = int(bytes/512) + 128 - 16
|
||||
} else if bytes > 512<<20 {
|
||||
// Grow from 8 MiB at start to 128 MiB of cache at 8 GiB of RAM.
|
||||
blockCacheCapacity = int(bytes / 64)
|
||||
// Increase block cache up to this maximum:
|
||||
const maxCapacity = 64 << 20
|
||||
// ... which we reach when the box has this much RAM:
|
||||
const maxAtRAM = 8 << 30
|
||||
|
||||
if v := cfg.Options().DatabaseBlockCacheMiB; v != 0 {
|
||||
// Use the value from the config, if it's set.
|
||||
blockCacheCapacity = v << 20
|
||||
} else if bytes, err := memorySize(); err == nil {
|
||||
// We start at the default of 8 MiB and use larger values for machines
|
||||
// with more memory.
|
||||
|
||||
if bytes > maxAtRAM {
|
||||
// Cap the cache at maxCapacity when we reach maxAtRam amount of memory
|
||||
blockCacheCapacity = maxCapacity
|
||||
} else if bytes > maxAtRAM/maxCapacity*int64(blockCacheCapacity) {
|
||||
// Grow from the default to maxCapacity at maxAtRam amount of memory
|
||||
blockCacheCapacity = int(bytes * maxCapacity / maxAtRAM)
|
||||
}
|
||||
l.Infoln("Database block cache capacity", blockCacheCapacity/1024, "KiB")
|
||||
}
|
||||
@@ -741,7 +741,7 @@ func dbOpts() *opt.Options {
|
||||
return &opt.Options{
|
||||
OpenFilesCacheCapacity: 100,
|
||||
BlockCacheCapacity: blockCacheCapacity,
|
||||
WriteBuffer: blockCacheCapacity / 2,
|
||||
WriteBuffer: 4 << 20,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -880,7 +880,7 @@ func discovery(extPort int) *discover.Discoverer {
|
||||
func ensureDir(dir string, mode int) {
|
||||
fi, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
err := os.MkdirAll(dir, 0700)
|
||||
err := osutil.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
@@ -14,10 +15,13 @@ import (
|
||||
var predictableRandomTest sync.Once
|
||||
|
||||
func TestPredictableRandom(t *testing.T) {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
t.Skip("Test only for 64 bit platforms; but if it works there, it should work on 32 bit")
|
||||
}
|
||||
predictableRandomTest.Do(func() {
|
||||
// predictable random sequence is predictable
|
||||
e := 3440579354231278675
|
||||
if v := predictableRandom.Int(); v != e {
|
||||
e := int64(3440579354231278675)
|
||||
if v := int64(predictableRandom.Int()); v != e {
|
||||
t.Errorf("Unexpected random value %d != %d", v, e)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -79,8 +79,10 @@ func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
|
||||
// External port changed; refresh the discovery announcement.
|
||||
// TODO: Don't reach out to some magic global here?
|
||||
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
|
||||
discoverer.StopGlobal()
|
||||
discoverer.StartGlobal(s.cfg.Options().GlobalAnnServers, uint16(extPort))
|
||||
if s.cfg.Options().GlobalAnnEnabled {
|
||||
discoverer.StopGlobal()
|
||||
discoverer.StartGlobal(s.cfg.Options().GlobalAnnServers, uint16(extPort))
|
||||
}
|
||||
}
|
||||
if debugNet {
|
||||
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
|
||||
|
||||
@@ -16,7 +16,9 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
"github.com/syncthing/syncthing/internal/model"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
// Current version number of the usage report, for acceptance purposes. If
|
||||
@@ -24,8 +26,45 @@ import (
|
||||
// are prompted for acceptance of the new report.
|
||||
const usageReportVersion = 1
|
||||
|
||||
var stopUsageReportingCh = make(chan struct{})
|
||||
type usageReportingManager struct {
|
||||
model *model.Model
|
||||
sup *suture.Supervisor
|
||||
}
|
||||
|
||||
func newUsageReportingManager(m *model.Model, cfg *config.Wrapper) *usageReportingManager {
|
||||
mgr := &usageReportingManager{
|
||||
model: m,
|
||||
}
|
||||
|
||||
// Start UR if it's enabled.
|
||||
mgr.Changed(cfg.Raw())
|
||||
|
||||
// Listen to future config changes so that we can start and stop as
|
||||
// appropriate.
|
||||
cfg.Subscribe(mgr)
|
||||
|
||||
return mgr
|
||||
}
|
||||
|
||||
func (m *usageReportingManager) Changed(cfg config.Configuration) error {
|
||||
if cfg.Options.URAccepted >= usageReportVersion && m.sup == nil {
|
||||
// Usage reporting was turned on; lets start it.
|
||||
svc := &usageReportingService{
|
||||
model: m.model,
|
||||
}
|
||||
m.sup = suture.NewSimple("usageReporting")
|
||||
m.sup.Add(svc)
|
||||
m.sup.ServeBackground()
|
||||
} else if cfg.Options.URAccepted < usageReportVersion && m.sup != nil {
|
||||
// Usage reporting was turned off
|
||||
m.sup.Stop()
|
||||
m.sup = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reportData returns the data to be sent in a usage report. It's used in
|
||||
// various places, so not part of the usageReportingSvc object.
|
||||
func reportData(m *model.Model) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
res["uniqueID"] = cfg.Options().URUniqueID
|
||||
@@ -75,8 +114,13 @@ func reportData(m *model.Model) map[string]interface{} {
|
||||
return res
|
||||
}
|
||||
|
||||
func sendUsageReport(m *model.Model) error {
|
||||
d := reportData(m)
|
||||
type usageReportingService struct {
|
||||
model *model.Model
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func (s *usageReportingService) sendUsageReport() error {
|
||||
d := reportData(s.model)
|
||||
var b bytes.Buffer
|
||||
json.NewEncoder(&b).Encode(d)
|
||||
|
||||
@@ -94,32 +138,32 @@ func sendUsageReport(m *model.Model) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func usageReportingLoop(m *model.Model) {
|
||||
func (s *usageReportingService) Serve() {
|
||||
s.stop = make(chan struct{})
|
||||
|
||||
l.Infoln("Starting usage reporting")
|
||||
t := time.NewTicker(86400 * time.Second)
|
||||
loop:
|
||||
defer l.Infoln("Stopping usage reporting")
|
||||
|
||||
t := time.NewTimer(10 * time.Minute) // time to initial report at start
|
||||
for {
|
||||
select {
|
||||
case <-stopUsageReportingCh:
|
||||
break loop
|
||||
case <-s.stop:
|
||||
return
|
||||
case <-t.C:
|
||||
err := sendUsageReport(m)
|
||||
err := s.sendUsageReport()
|
||||
if err != nil {
|
||||
l.Infoln("Usage report:", err)
|
||||
}
|
||||
t.Reset(24 * time.Hour) // next report tomorrow
|
||||
}
|
||||
}
|
||||
l.Infoln("Stopping usage reporting")
|
||||
}
|
||||
|
||||
func stopUsageReporting() {
|
||||
select {
|
||||
case stopUsageReportingCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
func (s *usageReportingService) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
// Returns CPU performance as a measure of single threaded SHA-256 MiB/s
|
||||
// cpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s
|
||||
func cpuBench() float64 {
|
||||
chunkSize := 100 * 1 << 10
|
||||
h := sha256.New()
|
||||
|
||||
@@ -102,8 +102,12 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
|
||||
return fmt.Sprintf("Started syncing %q / %q (%v %v)", data["folder"], data["item"], data["action"], data["type"])
|
||||
case events.ItemFinished:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
if err := data["err"]; err != nil {
|
||||
return fmt.Sprintf("Finished syncing %q / %q (%v %v): %v", data["folder"], data["item"], data["action"], data["type"], err)
|
||||
if err := data["error"]; err != nil {
|
||||
// If the err interface{} is not nil, it is a string pointer.
|
||||
// Dereference it to get the actual error or Sprintf will print
|
||||
// the pointer value....
|
||||
errStr := *err.(*string)
|
||||
return fmt.Sprintf("Finished syncing %q / %q (%v %v): %v", data["folder"], data["item"], data["action"], data["type"], errStr)
|
||||
}
|
||||
return fmt.Sprintf("Finished syncing %q / %q (%v %v): Success", data["folder"], data["item"], data["action"], data["type"])
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ func main() {
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
names := make(map[string]string)
|
||||
|
||||
var langs []string
|
||||
for code, stat := range stats {
|
||||
code = strings.Replace(code, "_", "-", 1)
|
||||
@@ -62,6 +64,7 @@ func main() {
|
||||
}
|
||||
|
||||
langs = append(langs, code)
|
||||
names[code] = languageName(code)
|
||||
if code == "en" {
|
||||
continue
|
||||
}
|
||||
@@ -85,6 +88,7 @@ func main() {
|
||||
}
|
||||
|
||||
saveValidLangs(langs)
|
||||
saveLanguageNames(names)
|
||||
}
|
||||
|
||||
func saveValidLangs(langs []string) {
|
||||
@@ -98,6 +102,16 @@ func saveValidLangs(langs []string) {
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
func saveLanguageNames(names map[string]string) {
|
||||
fd, err := os.Create("prettyprint.js")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprint(fd, "var langPrettyprint = ")
|
||||
json.NewEncoder(fd).Encode(names)
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
func userPass() (string, string) {
|
||||
user := os.Getenv("TRANSIFEX_USER")
|
||||
pass := os.Getenv("TRANSIFEX_PASS")
|
||||
@@ -131,7 +145,7 @@ func loadValidLangs() []string {
|
||||
}
|
||||
|
||||
var langs []string
|
||||
exp := regexp.MustCompile(`\[([a-zA-Z",-]+)\]`)
|
||||
exp := regexp.MustCompile(`\[([a-zA-Z@",-]+)\]`)
|
||||
if matches := exp.FindSubmatch(bs); len(matches) == 2 {
|
||||
langs = strings.Split(string(matches[1]), ",")
|
||||
for i := range langs {
|
||||
@@ -142,3 +156,19 @@ func loadValidLangs() []string {
|
||||
|
||||
return langs
|
||||
}
|
||||
|
||||
type languageResponse struct {
|
||||
Code string
|
||||
Name string
|
||||
}
|
||||
|
||||
func languageName(code string) string {
|
||||
var lang languageResponse
|
||||
resp := req("https://www.transifex.com/api/2/language/" + code)
|
||||
defer resp.Body.Close()
|
||||
json.NewDecoder(resp.Body).Decode(&lang)
|
||||
if lang.Name == "" {
|
||||
return code
|
||||
}
|
||||
return lang.Name
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ This directory contains configuration files for running syncthing under the
|
||||
systemd user service. For further documentation take a look at the [systemd
|
||||
section][1] on the Github Wiki.
|
||||
|
||||
[1]: https://github.com/syncthing/syncthing/wiki/Autostart-syncthing#systemd
|
||||
[1]: http://docs.syncthing.net/users/autostart.html?highlight=systemd
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Syncthing - Open Source Continuous File Synchronization for %I
|
||||
Documentation=https://github.com/syncthing/syncthing/wiki
|
||||
Documentation=http://docs.syncthing.net/
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Syncthing - Open Source Continuous File Synchronization
|
||||
Documentation=https://github.com/syncthing/syncthing/wiki
|
||||
Documentation=http://docs.syncthing.net/
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -68,7 +68,7 @@ identicon {
|
||||
}
|
||||
|
||||
.panel-heading .glyphicon {
|
||||
margin-right: 15px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
@@ -153,27 +153,33 @@ table.table-condensed td {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Menu for select language
|
||||
*/
|
||||
|
||||
@media (min-width:480px) {
|
||||
@media (min-width:480px) and (max-width:649px) {
|
||||
*[language-select] > .dropdown-menu {
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:650px) {
|
||||
*[language-select] > .dropdown-menu > li {
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
*[language-select] > .dropdown-menu {
|
||||
width: 400px;
|
||||
width: 440px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width:479px) {
|
||||
.dropdown-menu {
|
||||
padding-top: 55px;
|
||||
}
|
||||
|
||||
*[language-select] > .dropdown-toggle {
|
||||
nav .dropdown-toggle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "Ключ API",
|
||||
"About": "Аб праграме",
|
||||
"Add": "Дадаць",
|
||||
"Add Device": "Дадаць прыладу",
|
||||
"Add Folder": "Дадаць каталёг",
|
||||
"Add new folder?": "Дадаць новы каталёг ?",
|
||||
"Address": "Адрас",
|
||||
"Addresses": "Адрасы",
|
||||
"All Data": "All Data",
|
||||
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
|
||||
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
|
||||
"Automatic upgrades": "Automatic upgrades",
|
||||
"Bugs": "Памылкі",
|
||||
"CPU Utilization": "Выкарыстаньне працэсара",
|
||||
"Changelog": "Сьпіс зьменаў",
|
||||
"Close": "Зачыніць",
|
||||
"Command": "Command",
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
"Compression": "Compression",
|
||||
"Connection Error": "Connection Error",
|
||||
"Copied from elsewhere": "Copied from elsewhere",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Delete": "Выдаліць",
|
||||
"Device ID": "ID прылады",
|
||||
"Device Identification": "Ідэнтыфікацыя прылады",
|
||||
"Device Name": "Назва прылады",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Прылады",
|
||||
"Disconnected": "Адлучана",
|
||||
"Documentation": "Дакумэнтацыя",
|
||||
"Download Rate": "Хуткасьць спампоўваньня",
|
||||
"Downloaded": "Downloaded",
|
||||
"Downloading": "Downloading",
|
||||
"Edit": "Зьмяніць",
|
||||
"Edit Device": "Зьмяніць прыладу",
|
||||
"Edit Folder": "Зьмяніць каталёг",
|
||||
"Editing": "Editing",
|
||||
"Enable UPnP": "Enable UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
|
||||
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "Вэрсіі файлаў",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
|
||||
"Folder ID": "ID каталёгу",
|
||||
"Folder Master": "Folder Master",
|
||||
"Folder Path": "Шлях каталёгу",
|
||||
"Folders": "Каталёгі",
|
||||
"GUI Authentication Password": "GUI Authentication Password",
|
||||
"GUI Authentication User": "GUI Authentication User",
|
||||
"GUI Listen Addresses": "GUI Listen Addresses",
|
||||
"Generate": "Сгенераваць",
|
||||
"Global Discovery": "Глябальнае вызначэньне",
|
||||
"Global Discovery Server": "Сэрвер глябальнага вызначэньня",
|
||||
"Global State": "Глябальны стан",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore Patterns": "Ігнараваць шаблёны",
|
||||
"Ignore Permissions": "Ігнараваць правы",
|
||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
||||
"Introducer": "Introducer",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
|
||||
"Keep Versions": "Трымаць вэрсій",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Апошні атрыманы файл",
|
||||
"Last seen": "Апошні раз бачылі",
|
||||
"Later": "Later",
|
||||
"Local Discovery": "Лякальнае вызначэньне",
|
||||
"Local State": "Лякальны стан",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
|
||||
"Never": "Ніколі",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"Newest First": "Newest First",
|
||||
"No": "Не",
|
||||
"No File Versioning": "Не захоўваць вэрсіі",
|
||||
"Notice": "Notice",
|
||||
"OK": "Добра",
|
||||
"Off": "Off",
|
||||
"Oldest First": "Oldest First",
|
||||
"Out Of Sync": "Несынхранізавана",
|
||||
"Out of Sync Items": "Несынхранізаваныя складнікі",
|
||||
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
|
||||
"Override Changes": "Override Changes",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Please wait",
|
||||
"Preview": "Preview",
|
||||
"Preview Usage Report": "Preview Usage Report",
|
||||
"Quick guide to supported patterns": "Quick guide to supported patterns",
|
||||
"RAM Utilization": "Выкарыстаньне памяці",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Перачытаць",
|
||||
"Rescan All": "Rescan All",
|
||||
"Rescan Interval": "Інтэрвал перачытваньня",
|
||||
"Restart": "Перастартаваць",
|
||||
"Restart Needed": "Патрэбна перастартоўваньне",
|
||||
"Restarting": "Перастартоўваньне",
|
||||
"Reused": "Reused",
|
||||
"Save": "Захаваць",
|
||||
"Scanning": "Скануецца",
|
||||
"Select the devices to share this folder with.": "Пазначце прылады зь якімі трэба абагуліць гэты каталёг.",
|
||||
"Select the folders to share with this device.": "Пазначце каталёгі якія трэба абагуліць з гэтай прыладай.",
|
||||
"Settings": "Налады",
|
||||
"Share": "Абагуліць",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share Folders With Device": "Share Folders With Device",
|
||||
"Share With Devices": "Абагуліць з прыладамі",
|
||||
"Share this folder?": "Абагуліць гэты каталёг ?",
|
||||
"Shared With": "Абагулены з",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Short identifier for the folder. Must be the same on all cluster devices.",
|
||||
"Show ID": "Паказаць ID",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||
"Shutdown": "Выключыць",
|
||||
"Shutdown Complete": "Выключэньне завершанае",
|
||||
"Simple File Versioning": "Простае захоўваньне вэрсій",
|
||||
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Зыходнікі",
|
||||
"Staggered File Versioning": "Адаптыўнае захоўваньне вэрсій",
|
||||
"Start Browser": "Start Browser",
|
||||
"Stopped": "Спынена",
|
||||
"Support": "Падтрымка",
|
||||
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
|
||||
"Syncing": "Сынхранізуецца",
|
||||
"Syncthing has been shut down.": "Syncthing has been shut down.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
|
||||
"Syncthing is restarting.": "Syncthing перастартоўвае.",
|
||||
"Syncthing is upgrading.": "Syncthing is upgrading.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
|
||||
"The device ID cannot be blank.": "The device ID cannot be blank.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
|
||||
"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.": "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.",
|
||||
"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.": "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.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
|
||||
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.",
|
||||
"The folder ID must be unique.": "The folder ID must be unique.",
|
||||
"The folder path cannot be blank.": "The folder path cannot be blank.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "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.",
|
||||
"The maximum age must be a number and cannot be blank.": "The maximum age must be a number and cannot be blank.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
|
||||
"The number of old versions to keep, per file.": "Колькі старых вэрсій трымаць, для кожнага файлу.",
|
||||
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Unknown": "Невядома",
|
||||
"Unshared": "Unshared",
|
||||
"Unused": "Unused",
|
||||
"Up to Date": "Найноўшае",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
||||
"Upgrading": "Абнаўленьне",
|
||||
"Upload Rate": "Хуткасьць запампоўваньня",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "Use HTTPS for GUI",
|
||||
"Version": "Вэрсія",
|
||||
"Versions Path": "Versions Path",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
|
||||
"Yes": "Так",
|
||||
"You must keep at least one version.": "You must keep at least one version.",
|
||||
"full documentation": "full documentation",
|
||||
"items": "складнікаў",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Нова основна версия, която може да не е съвмеситима с предишни версии.",
|
||||
"API Key": "API Ключ",
|
||||
"About": "За Програмата",
|
||||
"Actions": "Действия",
|
||||
"Add": "Добави",
|
||||
"Add Device": "Добави устройство",
|
||||
"Add Folder": "Добави папка",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Глобавно Откриване",
|
||||
"Global Discovery Server": "Сървър за Глобално Откриване",
|
||||
"Global State": "Глобално състояние",
|
||||
"Help": "Помощ",
|
||||
"Ignore": "Игнорирай",
|
||||
"Ignore Patterns": "Шаблони за Игнориране",
|
||||
"Ignore Permissions": "Игнорирай Права за Достъп",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "Clau API",
|
||||
"About": "Sobre",
|
||||
"Actions": "Actions",
|
||||
"Add": "Afegir",
|
||||
"Add Device": "Afegir dispositiu",
|
||||
"Add Folder": "Afegir carpeta",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Descobriment Global",
|
||||
"Global Discovery Server": "Servidor de Descobriment Global",
|
||||
"Global State": "Estat global",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrons d'ignoració",
|
||||
"Ignore Permissions": "Ignora Permisos",
|
||||
|
||||
188
gui/assets/lang/lang-ca@valencia.json
Normal file
188
gui/assets/lang/lang-ca@valencia.json
Normal file
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "Una nova versión amb canvis importants pot no ser compatible amb versions prèvies.",
|
||||
"API Key": "Clau API",
|
||||
"About": "Sobre",
|
||||
"Actions": "Actions",
|
||||
"Add": "Afegir",
|
||||
"Add Device": "Afegir dispositiu",
|
||||
"Add Folder": "Afegir carpeta",
|
||||
"Add new folder?": "Afegir nova carpeta?",
|
||||
"Address": "Direcció",
|
||||
"Addresses": "Direccions",
|
||||
"All Data": "Totes les dades",
|
||||
"Allow Anonymous Usage Reporting?": "Permetre informes d'ús anònim?",
|
||||
"Alphabetic": "Alfabètic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando extern controla el versionat. És necessari eliminar el fitxer de la carpeta sincronitzada.",
|
||||
"Anonymous Usage Reporting": "Informe d'ús anònim",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Tots els dispositius configurats en un dispositiu presentador seràn afegits també a aquest dispositiu.",
|
||||
"Automatic upgrades": "Actualitzacions automàtiques",
|
||||
"Bugs": "Errors (Bugs)",
|
||||
"CPU Utilization": "Utilització de la CPU",
|
||||
"Changelog": "Registre de canvis",
|
||||
"Close": "Tancar",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentar, quant s'utilitza al principi d'una línia",
|
||||
"Compression": "Compresió",
|
||||
"Connection Error": "Error de connexió",
|
||||
"Copied from elsewhere": "Copiat de qualsevol lloc",
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 els següents Col·laboradors:",
|
||||
"Delete": "Esborrar",
|
||||
"Device ID": "ID del dispositiu",
|
||||
"Device Identification": "Identificació del dispositiu",
|
||||
"Device Name": "Nom del dispositiu",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol connectar-se. Afegir nou dispositiu?",
|
||||
"Devices": "Dispositius",
|
||||
"Disconnected": "Desconnectat",
|
||||
"Documentation": "Documentació",
|
||||
"Download Rate": "Velocitat de descàrrega",
|
||||
"Downloaded": "Descarregat",
|
||||
"Downloading": "Descarregant",
|
||||
"Edit": "Editar",
|
||||
"Edit Device": "Editar dispositiu",
|
||||
"Edit Folder": "Editar carpeta",
|
||||
"Editing": "Editant",
|
||||
"Enable UPnP": "Activar UPnp",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduïr direccions separades per coma com \"ip:port\" o \"dynamic\" per a fer un descobriment automàtic de la direcció.",
|
||||
"Enter ignore patterns, one per line.": "Introduïr patrons a ignorar, un per línia.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "Versionat extern de fitxers",
|
||||
"File Pull Order": "Ordre de fitxers del pull",
|
||||
"File Versioning": "Versionat de fitxer",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Els bits de permís del fitxer són ignorats quant es busquen els canvis. Utilitzar en sistemes de fitxers FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Els fitxers són canviats a versions amb indicació de data en una carpeta \".stversions\" quant són reemplaçats o esborrats per Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Els fitxers són protegits dels canvis fets en altres dispositius, però els canvis fets en aquest dispositiu seràn enviats a la resta del grup (cluster).",
|
||||
"Folder ID": "ID de carpeta",
|
||||
"Folder Master": "Carpeta principal",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
"Folders": "Carpetes",
|
||||
"GUI Authentication Password": "Password d'autenticació de l'Interfície Gràfica d'Usuari (GUI)",
|
||||
"GUI Authentication User": "Autenticació de l'usuari de l'Interfície Gràfica d'Usuari (GUI)",
|
||||
"GUI Listen Addresses": "Direcció d'escolta de l'Interfície Gràfica d'Usuari (GUI)",
|
||||
"Generate": "Generar",
|
||||
"Global Discovery": "Descobriment global",
|
||||
"Global Discovery Server": "Servidor de descobriment global",
|
||||
"Global State": "Estat global",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrons a ignorar",
|
||||
"Ignore Permissions": "Permisos a ignorar",
|
||||
"Incoming Rate Limit (KiB/s)": "Límit de descàrrega (KiB/s)",
|
||||
"Introducer": "Presentador",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversió de la condició donada (per exemple no excloure)",
|
||||
"Keep Versions": "Mantindre versions",
|
||||
"Largest First": "El més gran primer",
|
||||
"Last File Received": "Darrer fitxer rebut",
|
||||
"Last seen": "Vist per última vegada",
|
||||
"Later": "Més tard",
|
||||
"Local Discovery": "Descobriment local",
|
||||
"Local State": "Estat local",
|
||||
"Major Upgrade": "Actualització important",
|
||||
"Maximum Age": "Edat màxima",
|
||||
"Metadata Only": "Sols metadades",
|
||||
"Move to top of queue": "Moure al principi de la cua",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Comodí multinivell (coincideix amb múltiples nivells de directoris)",
|
||||
"Never": "Mai",
|
||||
"New Device": "Nou dispositiu",
|
||||
"New Folder": "Nova carpeta",
|
||||
"Newest First": "El més nou primer",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sense versionat de fitxer",
|
||||
"Notice": "Avís",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
"Oldest First": "El més vell primer",
|
||||
"Out Of Sync": "Sense sincronitzar",
|
||||
"Out of Sync Items": "Dispositius sense sincronitzar",
|
||||
"Outgoing Rate Limit (KiB/s)": "Límit de pujada (KiB/s)",
|
||||
"Override Changes": "Sobreescriure els canvis",
|
||||
"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": "Ruta a la carpeta local en l'ordinador. Es crearà si no existeix. El caràcter tilde (~) es pot utilitzar com a drecera",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta on les versions deurien estar emmagatzemades (deixar buit per a la carpeta .stversions en la carpeta).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Per favor, consultar les notes de la versió abans de fer una actualització important.",
|
||||
"Please wait": "Per favor, espere",
|
||||
"Preview": "Vista prèvia",
|
||||
"Preview Usage Report": "Informe d'ús de vista prèvia",
|
||||
"Quick guide to supported patterns": "Guía ràpida de patrons suportats",
|
||||
"RAM Utilization": "Utilització de la RAM",
|
||||
"Random": "Aleatori",
|
||||
"Release Notes": "Notes de la versió",
|
||||
"Rescan": "Tornar a buscar",
|
||||
"Rescan All": "Tornar a buscar tot",
|
||||
"Rescan Interval": "Interval de nova busca",
|
||||
"Restart": "Reiniciar",
|
||||
"Restart Needed": "Reinici necesari",
|
||||
"Restarting": "Reiniciant",
|
||||
"Reused": "Reutilitzat",
|
||||
"Save": "Gravar",
|
||||
"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.",
|
||||
"Settings": "Ajustos",
|
||||
"Share": "Compartir",
|
||||
"Share Folder": "Compartir carpeta",
|
||||
"Share Folders With Device": "Compartir carpetes amb el dispositiu",
|
||||
"Share With Devices": "Compartir amb els dispositius",
|
||||
"Share this folder?": "Compartir aquesta carpeta?",
|
||||
"Shared With": "Compartit amb",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curt per a la carpeta. Deu ser el mateix en tots els dispositius del grup (cluster).",
|
||||
"Show ID": "Mostrar ID",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrat en lloc de l'ID del dispositiu en l'estat del grup (cluster). S'anunciarà als altres dispositius com el nom opcional per defecte.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrat en lloc de l'ID del dispositiu en l'estat del grup (cluster). S'actualitzarà al nom que el dispositiu anuncia si es deixa buit.",
|
||||
"Shutdown": "Apagar",
|
||||
"Shutdown Complete": "Apagar completament",
|
||||
"Simple File Versioning": "Versionat de fitxers senzill",
|
||||
"Single level wildcard (matches within a directory only)": "Comodí de nivell únic (coincideix sols dins d'un directori)",
|
||||
"Smallest First": "El més xicotet primer",
|
||||
"Source Code": "Codi font",
|
||||
"Staggered File Versioning": "Versionat de fitxers escalonat",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
"Stopped": "Parat",
|
||||
"Support": "Suport",
|
||||
"Sync Protocol Listen Addresses": "Direccions d'escolta del protocol de sincronització",
|
||||
"Syncing": "Sincronitzant",
|
||||
"Syncthing has been shut down.": "Syncthing s'ha apagat",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inclou el següent software o parts d'ell:",
|
||||
"Syncthing is restarting.": "Syncthing està reiniciant.",
|
||||
"Syncthing is upgrading.": "Syncthing està actualitzant-se.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing pareix apagat o hi ha un problema amb la connexió a Internet. Tornant a intentar...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing pareix que té un problema processant la seua sol·licitud. Per favor, refresque la pàgina o reinicie Syncthing si el problema persistix.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Les estadístiques agregades estan disponibles públicament en {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuració ha sigut gravada però no activada. Syncthing deu reiniciar per tal d'activar la nova configuració.",
|
||||
"The device ID cannot be blank.": "L'ID del dispositiu no pot estar buida.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "L'ID del dispositiu que hi ha que introduïr ací es pot trobar en el menú \"Editar > Mostrar ID\" en l'altre dispositiu. Els espais i les barres son opcionals (ignorats).",
|
||||
"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.": "L'informe encriptat d'ús s'envia diariament. S'utilitza per a rastrejar plataformes comuns, tamanys de carpetes i versions de l'aplicació. Si el conjunt de dades enviat a l'informe es canvia, se li demanarà a vosté l'autorització altra vegada.\n",
|
||||
"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.": "L'ID del dispositiu introduïda no pareix vàlida. Deuria ser una cadena de 52 o 56 caracters consistents en lletres i nombre, amb espais i barres opcionals.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "El primer paràmetre de la línia de comandos és la ruta de la carpeta i el segon és la ruta relativa en la carpeta.",
|
||||
"The folder ID cannot be blank.": "L'ID de la carpeta no pot estar buit.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "L'ID de la carpeta deu ser un identificador curt (64 caracters o menys) consistint en lletres, nombres i únicament els caracters del punt (.), guió (-) i guió baix (_).",
|
||||
"The folder ID must be unique.": "L'ID de la carpeta deu ser única.",
|
||||
"The folder path cannot be blank.": "La ruta de la carpeta no pot estar buida.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "S'utilitzen els següents intervals: per a la primera hora es guarda una versió cada 30 segons, per al primer dia es guarda una versió cada hora, per als primers 30 dies es guarda una versió diaria, fins l'edat màxima es guarda una versió cada setmana.",
|
||||
"The maximum age must be a number and cannot be blank.": "L'edat màxima deu ser un nombre i no pot estar buida.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El temps màxim per a guardar una versió (en dies, ficar 0 per a guardar les versions per a sempre).",
|
||||
"The number of old versions to keep, per file.": "El nombre de versions antigues per a guardar, per cada fitxer.",
|
||||
"The number of versions must be a number and cannot be blank.": "El nombre de versions deu ser un nombre i no pot estar buit.",
|
||||
"The path cannot be blank.": "La ruta no pot estar buida.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "L'interval de reescaneig deu ser un nombre positiu de segons.",
|
||||
"This is a major version upgrade.": "Aquesta és una actualització important de la versió.",
|
||||
"Unknown": "Desconegut",
|
||||
"Unshared": "No compartit",
|
||||
"Unused": "No utilitzat",
|
||||
"Up to Date": "Actualitzat",
|
||||
"Upgrade": "Actualitzar",
|
||||
"Upgrade To {%version%}": "Actualitzar a {{version}}",
|
||||
"Upgrading": "Actualitzant",
|
||||
"Upload Rate": "Velocitat d'actualització",
|
||||
"Uptime": "Temps de funcionament",
|
||||
"Use HTTPS for GUI": "Utilitzar HTTP per a l'Interfície Gràfica d'Usuari (GUI)",
|
||||
"Version": "Versió",
|
||||
"Versions Path": "Ruta de les versions",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Les versions s'esborren automàticament si són més antigues que l'edat màxima o excedixen el nombre de fitxer permesos en un interval.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quant s'afig un nou dispositiu, hi ha que tindre en compte que aquest dispositiu deu ser afegit també en l'altre costat.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quant s'afig una nova carpeta, hi ha que tindre en compte que l'ID de la carpeta s'utilitza per a juntar les carpetes entre dispositius. Són sensibles a les majúscules i deuen coincidir exactament entre tots els dispositius.",
|
||||
"Yes": "Sí",
|
||||
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
|
||||
"full documentation": "Documentació completa",
|
||||
"items": "Elements",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\"."
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Nová důležitá verze nemusí být kompatibilní s předchozími verzemi.",
|
||||
"API Key": "API klíč",
|
||||
"About": "O aplikaci",
|
||||
"Actions": "Akce",
|
||||
"Add": "Přidat",
|
||||
"Add Device": "Přidat přístroj",
|
||||
"Add Folder": "Přidat adresář",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Globální oznamování",
|
||||
"Global Discovery Server": "Server globálního oznamování",
|
||||
"Global State": "Všeobecný status",
|
||||
"Help": "Pomoc",
|
||||
"Ignore": "Ignorovat",
|
||||
"Ignore Patterns": "Ignorované vzory",
|
||||
"Ignore Permissions": "Ignorovat oprávnění",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "Eine neue Hauptversion ist evtl. nicht mit vorherigen Versionen kompatibel.",
|
||||
"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",
|
||||
"Actions": "Aktionen",
|
||||
"Add": "Hinzufügen",
|
||||
"Add Device": "Gerät hinzufügen",
|
||||
"Add Folder": "Verzeichnis hinzufügen",
|
||||
@@ -10,7 +11,7 @@
|
||||
"Addresses": "Adressen",
|
||||
"All Data": "Alle Daten",
|
||||
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
|
||||
"Alphabetic": "Alphabetisch",
|
||||
"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 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",
|
||||
@@ -23,7 +24,7 @@
|
||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
|
||||
"Compression": "Komprimierung",
|
||||
"Connection Error": "Verbindungsfehler",
|
||||
"Copied from elsewhere": "Von woanders kopiert",
|
||||
"Copied from elsewhere": "Von anderer Quelle kopiert",
|
||||
"Copied from original": "Vom Original kopiert",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 die folgenden Unterstützer:",
|
||||
"Delete": "Löschen",
|
||||
@@ -42,17 +43,17 @@
|
||||
"Edit Folder": "Verzeichnis bearbeiten",
|
||||
"Editing": "Bearbeitung",
|
||||
"Enable UPnP": "UPnP aktivieren",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Trage durch ein Komma getrennte \"IP:Port\" Adressen oder \"dynamic\" ein um automatische Adresserkennung durchzuführen.",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Trage durch ein Komma getrennte \"IP:Port\" Adressen oder \"dynamic\" ein, um die automatische Adresserkennung zu nutzen.",
|
||||
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
|
||||
"Error": "Fehler",
|
||||
"External File Versioning": "Externe Dateiversionierung",
|
||||
"File Pull Order": "Dateianforderungsreihenfolge",
|
||||
"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 date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in einen Ordner namens .stversions verschoben.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt, auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
|
||||
"Folder ID": "Verzeichnis ID",
|
||||
"Folder Master": "Keine Veränderungen zulassen",
|
||||
"Folder Master": "Master Ordner - schreibgeschützt",
|
||||
"Folder Path": "Verzeichnispfad",
|
||||
"Folders": "Verzeichnisse",
|
||||
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
|
||||
@@ -60,20 +61,21 @@
|
||||
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
|
||||
"Generate": "Generieren",
|
||||
"Global Discovery": "Globale Auffindung",
|
||||
"Global Discovery Server": "Globaler Auffindungsserver",
|
||||
"Global Discovery Server": "Globale(r) Indexserver",
|
||||
"Global State": "Globaler Status",
|
||||
"Help": "Hilfe",
|
||||
"Ignore": "Ignorieren",
|
||||
"Ignore Patterns": "Ignoriermuster",
|
||||
"Ignore Permissions": "Berechtigungen ignorieren",
|
||||
"Incoming Rate Limit (KiB/s)": "Eingehendes Datenratelimit (KiB/s)",
|
||||
"Incoming Rate Limit (KiB/s)": "Limit Datenrate (eingehend) (KiB/s)",
|
||||
"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",
|
||||
"Largest First": "Größtes zuerst",
|
||||
"Last File Received": "Letzte Datei",
|
||||
"Last File Received": "Letzte Datei empfangen",
|
||||
"Last seen": "Zuletzt online",
|
||||
"Later": "Später",
|
||||
"Local Discovery": "Lokale Auffindung",
|
||||
"Local Discovery": "Client lokal freigeben",
|
||||
"Local State": "Lokaler Status",
|
||||
"Major Upgrade": "Hauptversionsupgrade",
|
||||
"Maximum Age": "Höchstalter",
|
||||
@@ -104,15 +106,15 @@
|
||||
"RAM Utilization": "RAM Auslastung",
|
||||
"Random": "Zufall",
|
||||
"Release Notes": "Veröffentlichungsnotizen",
|
||||
"Rescan": "Überprüfen",
|
||||
"Rescan All": "Alle überprüfen",
|
||||
"Rescan Interval": "Suchintervall",
|
||||
"Rescan": "Neu scannen",
|
||||
"Rescan All": "Alle neu scannen",
|
||||
"Rescan Interval": "Scanintervall",
|
||||
"Restart": "Neustart",
|
||||
"Restart Needed": "Neustart benötigt",
|
||||
"Restarting": "Wird neu gestartet",
|
||||
"Reused": "Erneut benutzt",
|
||||
"Save": "Speichern",
|
||||
"Scanning": "Suche",
|
||||
"Scanning": "Scanning",
|
||||
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du dieses Verzeichnis teilen willst.",
|
||||
"Select the folders to share with this device.": "Wähle die Verzeichnisse aus, die du mit diesem Gerät teilen möchtest",
|
||||
"Settings": "Einstellungen",
|
||||
@@ -124,7 +126,7 @@
|
||||
"Shared With": "Geteilt mit",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Kurze ID für das Verzeichnis. Muss auf allen Verbunds-Geräten gleich sein.",
|
||||
"Show ID": "ID anzeigen",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird als optionaler Standardname an andere Geräte bekannt gegeben.",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients weitergegeben.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird auf den Namen aktualisiert, den das Gerät angibt.",
|
||||
"Shutdown": "Herunterfahren",
|
||||
"Shutdown Complete": "Vollständig Heruntergefahren",
|
||||
@@ -145,24 +147,24 @@
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Es scheint als ob Syncthing ein Problem mit der Verarbeitung ihrer Eingabe hat. Bitte laden sie die Seite neu oder führen sie einen Neustart von Syncthing durch, falls das Problem weiterhin besteht.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Die gesammelten Statistiken sind öffentlich verfügbar unter {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu aktivieren.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu übernehmen.",
|
||||
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird benutzt 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 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 Ordner.",
|
||||
"The folder ID cannot be blank.": "Die Verzeichnis ID darf nicht leer sein.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Kennung (64 Zeichen oder weniger) sein. Sie kann nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
|
||||
"The folder ID must be unique.": "Die Verzeichnis ID muss eindeutig sein.",
|
||||
"The folder path cannot be blank.": "Der Verzeichnispfad kann nicht leer sein",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Bezeichnung haben (64 Zeichen oder weniger). Sie darf nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
|
||||
"The folder ID must be unique.": "Die Verzeichnis ID darf nur einmal existieren.",
|
||||
"The folder path cannot be blank.": "Der Verzeichnispfad darf nicht leer sein",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es wird in folgenden Abständen versioniert: in der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag, danach wird bis zum Höchstalter eine Version pro Woche beibehalten.",
|
||||
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
|
||||
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
|
||||
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
|
||||
"The path cannot be blank.": "Der Pfad darf nicht leer sein",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Das Suchintervall muss eine nicht negative Anzahl von Sekunden sein.",
|
||||
"This is a major version upgrade.": "Dies ist eine neue Hauptversion",
|
||||
"The path cannot be blank.": "Der Pfad darf nicht leer sein.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl von Sekunden sein.",
|
||||
"This is a major version upgrade.": "Dies ist eine neue Hauptversion.",
|
||||
"Unknown": "Unbekannt",
|
||||
"Unshared": "Ungeteilt",
|
||||
"Unused": "Ungenutzt",
|
||||
@@ -175,9 +177,9 @@
|
||||
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Versionierungspfad",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Versionen werden automatisch gelöscht wenn sie älter als das angegebene Höchstalter sind oder die Höchstzahl der Dateien pro Zeitabschnitt überschritten wird.",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Versionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die Höchstzahl der Dateien je Zeitabschnitt überschritten wird.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf der Gegenseite hinzugefügt werden muss.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beim Hinzufügen eines neuen Verzeichnisses, beachte dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beim Hinzufügen eines neuen Verzeichnisses beachte, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
|
||||
"full documentation": "Komplette Dokumentation",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"A new major version may not be compatible with previous versions.": "Μια νέα σημαντική έκδοση μπορεί να μην είναι συμβατή με τις προηγούμενες εκδόσεις.",
|
||||
"API Key": "Κλειδί API",
|
||||
"About": "Σχετικά με το Syncthing",
|
||||
"Actions": "Actions",
|
||||
"Add": "Προσθήκη",
|
||||
"Add Device": "Προσθήκη συσκευής",
|
||||
"Add Folder": "Προσθήκη φακέλου",
|
||||
@@ -10,8 +11,8 @@
|
||||
"Addresses": "Διευθύνσεις",
|
||||
"All Data": "Όλα τα δεδομένα",
|
||||
"Allow Anonymous Usage Reporting?": "Να επιτρέπεται η αποστολή ανώνυμων στοιχείων χρήσης;",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Μια εξωτερική εντολή χειρίζεται την διαχείριση εκδόσεων. Πρέπει να καταργήσετε το αρχείο από το φάκελο συγχρονισμένων.",
|
||||
"Alphabetic": "Αλφαβητικά",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Μια εξωτερική εντολή χειρίζεται την διαχείριση εκδόσεων. Χρειάζεται να αφαιρέσει το αρχείο από το φάκελο συγχρονισμένων.",
|
||||
"Anonymous Usage Reporting": "Ανώνυμα στοιχεία χρήσης",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Αν δηλωθεί σαν «βασικός κόμβος», τότε όλες οι συσκευές που είναι δηλωμένες εκεί θα υπάρχουν και στον τοπικό κόμβο.",
|
||||
"Automatic upgrades": "Αυτόματη αναβάθμιση",
|
||||
@@ -45,11 +46,11 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Γράψε τις διευθύνσεις IP με τη μορφή «ip:θύρα», διαχωρισμένες με κόμμα. Αλλιώς γράψε «dynamic» για να πραγματοποιηθεί η αυτόματη εύρεση διευθύνσεων.",
|
||||
"Enter ignore patterns, one per line.": "Δώσε τα πρότυπα που θα αγνοηθούν, ένα σε κάθε γραμμή.",
|
||||
"Error": "Σφάλμα",
|
||||
"External File Versioning": "Εξωτερική διαχείριση εκδόσεων Αρχείου",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"External File Versioning": "Εξωτερική τήρηση εκδόσεων",
|
||||
"File Pull Order": "Σειρά με την οποία θα κατεβαίνουν τα αρχεία",
|
||||
"File Versioning": "Τήρηση εκδόσεων",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Τα bit αδεια αρχεία αγνοούνται όταν ψάχνουν για αλλαγές. Χρήση σε συστήματα αρχείων FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Τα αρχεία που μετακινηθηκαν στην ημερομηνία που αναγράφεται τις εκδόσεις σε ένα φάκελο .stversions όταν αντικατασταθούν ή να διαγραφθούν από το Syncthing.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Τα δικαιώματα των αρχείων θα αγνοούνται όταν κοιτάζω για αλλαγές. Αφορά συστήματα αρχείων FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Τα αρχεία που σβήνονται ή αντικαθιστούνται από το Syncthing μετακινούνται σε έναν φάκελο .stversions με χρονοσφραγίδα.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Τα αρχεία προστατεύονται από αλλαγές που γίνονται σε άλλες συσκευές, αλλά όποιες αλλαγές γίνουν σε αυτή τη συσκευή θα αποσταλούν σε όλη τη συστάδα συσκευών.",
|
||||
"Folder ID": "Ταυτότητα φακέλου",
|
||||
"Folder Master": "Να μην επιτρέπονται αλλαγές",
|
||||
@@ -59,9 +60,10 @@
|
||||
"GUI Authentication User": "Χρηστώνυμο για την πρόσβαση στη διεπαφή",
|
||||
"GUI Listen Addresses": "Διευθύνσεις από τις οποίες θα είναι προσβάσιμη η διεπαφή",
|
||||
"Generate": "Δημιουργία",
|
||||
"Global Discovery": "Να μπορεί να ανευρεθεί καθολικά (από παντού)",
|
||||
"Global Discovery": "Καθολική ανεύρεση",
|
||||
"Global Discovery Server": "Διακομιστής καθολικής ανεύρεσης κόμβου",
|
||||
"Global State": "Καθολική κατάσταση",
|
||||
"Help": "Help",
|
||||
"Ignore": "Αγνόησε",
|
||||
"Ignore Patterns": "Πρότυπο για αγνόηση",
|
||||
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
|
||||
@@ -69,13 +71,13 @@
|
||||
"Introducer": "Βασικός κόμβος",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
|
||||
"Keep Versions": "Διατήρηση εκδόσεων",
|
||||
"Largest First": "Largest First",
|
||||
"Largest First": "Το μεγαλύτερο πρώτα",
|
||||
"Last File Received": "Πιο πρόσφατο αρχείο",
|
||||
"Last seen": "Τελευταία φορά συνδεδεμένος",
|
||||
"Later": "Αργότερα",
|
||||
"Local Discovery": "Τοπική ανεύρεση",
|
||||
"Local State": "Τοπική κατάσταση",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Major Upgrade": "Σημαντική αναβάθμιση",
|
||||
"Maximum Age": "Μέγιστη ηλικία",
|
||||
"Metadata Only": "Μόνο μεταδεδομένα",
|
||||
"Move to top of queue": "Μεταφορά στην αρχή της λίστας",
|
||||
@@ -83,27 +85,27 @@
|
||||
"Never": "Ποτέ",
|
||||
"New Device": "Νέα συσκευή",
|
||||
"New Folder": "Νέος φάκελος",
|
||||
"Newest First": "Newest First",
|
||||
"Newest First": "Το νεότερο πρώτα",
|
||||
"No": "Όχι",
|
||||
"No File Versioning": "Να μην τηρούνται εκδόσεις",
|
||||
"Notice": "Σημείωση",
|
||||
"OK": "OK",
|
||||
"Off": "Απενεργοποιημένο",
|
||||
"Oldest First": "Oldest First",
|
||||
"Oldest First": "Το παλιότερο πρώτα",
|
||||
"Out Of Sync": "Μη συγχρονισμένα",
|
||||
"Out of Sync Items": "Μη συγχρονισμένα αντικείμενα",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ρυθμός αποστολής (KiB/s)",
|
||||
"Override Changes": "Να αντικατασταθούν οι αλλαγές",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Μονοπάτι του φακέλου σε αυτόν τον υπολογιστή. Αν δεν υπάρχει θα δημιουργηθεί. Η περισπωμένη (~) μπορεί να μπει σαν συντόμευση για το",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ο φάκελος στον οποίο θα αποθηκεύονται οι εκδόσεις των αρχείων (αν δεν οριστεί θα αποθηκεύονται στον υποφάκελο .stversions)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please consult the release notes before performing a major upgrade.": "Παρακαλούμε, πριν από την εκτέλεση μιας σημαντικής αναβάθμισης, να συμβουλευτείς το σημείωμα που τη συνοδεύει. ",
|
||||
"Please wait": "Παρακαλώ περιμένετε",
|
||||
"Preview": "Προεπισκόπηση",
|
||||
"Preview Usage Report": "Προεπισκόπηση αναφοράς χρήσης",
|
||||
"Quick guide to supported patterns": "Σύντομη βοήθεια σχετικά με τα πρότυπα αναζήτησης που υποστηρίζονται",
|
||||
"RAM Utilization": "Επιβάρυνση RAM",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Random": "Τυχαία",
|
||||
"Release Notes": "Σημείωμα έκδοσης",
|
||||
"Rescan": "Έλεγξε για αλλαγές",
|
||||
"Rescan All": "Έλεγξέ τα όλα για αλλαγές",
|
||||
"Rescan Interval": "Κάθε πότε θα ελέγχεται για αλλαγές ",
|
||||
@@ -122,7 +124,7 @@
|
||||
"Share With Devices": "Διαμοίρασε με αυτές τις συσκευές",
|
||||
"Share this folder?": "Να διαμοιραστεί αυτός ο φάκελος;",
|
||||
"Shared With": "Διαμοιράζεται με",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Σύντομη ταυτότητα για το φάκελο. Θα πρέπει να είναι η ίδια σε όλες τις συσκευές με τις οποίες διαμοιράζεται ο φάκελος αυτός.",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Σύντομη ταυτότητα για το φάκελο. Θα πρέπει να είναι η ίδια σε όλη τη συστάδα συσκευών με τις οποίες διαμοιράζεται ο φάκελος αυτός.",
|
||||
"Show ID": "Εμφάνιση ταυτότητας",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα γνωστοποιείται σαν το προαιρετικό όνομα της συσκευής.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα ενημερώνεται αυτόματα αν αλλάξει το όνομα της συσκευής.",
|
||||
@@ -130,7 +132,7 @@
|
||||
"Shutdown Complete": "Πλήρης απενεργοποίηση",
|
||||
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
|
||||
"Single level wildcard (matches within a directory only)": "Τελεστής μπαλαντέρ (*) για ένα επίπεδο (χρησιμοποιείται για έναν φάκελο μόνο)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Smallest First": "Το μικρότερο πρώτα",
|
||||
"Source Code": "Πηγαίος κώδικας",
|
||||
"Staggered File Versioning": "Να τηρούνται κλιμακούμενες εκδόσεις",
|
||||
"Start Browser": "Εκκίνηση προγράμματος περιήγησης",
|
||||
@@ -143,14 +145,14 @@
|
||||
"Syncthing is restarting.": "Το Syncthing επανεκκινείται.",
|
||||
"Syncthing is upgrading.": "Το Syncthing αναβαθμίζεται.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Το Syncthing φαίνεται πως είναι απενεργοποιημένο ή υπάρχει πρόβλημα στη σύνδεσή σου στο διαδίκτυο. Προσπαθώ πάλι…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Το Syncthing φαίνεται να αντιμετωπίζει ένα πρόβλημα κατά την επεξεργασία του αιτήματός σας. Παρακαλούμε ανανεώστε την σελίδα ή επανεκκινήστε το Syncthing εάν το πρόβλημα παραμένει.",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Το Syncthing φαίνεται να αντιμετωπίζει ένα πρόβλημα με την επεξεργασία του αιτήματός σου. Παρακαλούμε, αν το πρόβλημα συνεχίζει, ανανέωσε την σελίδα ή επανεκκίνησε το Syncthing.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Τα στατιστικά που έχουν συλλεγεί είναι δημόσια διαθέσιμα στο {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Οι ρυθμίσεις έχουν αποθηκευτεί αλλά δεν έχουν ενεργοποιηθεί. Πρέπει να επανεκκινήσεις το Syncthing για να ισχύσουν οι νέες ρυθμίσεις.",
|
||||
"The device ID cannot be blank.": "Η ταυτότητα της συσκευής δεν μπορεί να είναι κενή",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Η ταυτότητα της συσκευής που θα μπει εδώ βρίσκεται στο μενού «Επεξεργασία > Εμφάνιση ταυτότητας» στην άλλη συσκευή. Κενοί χαρακτήρες και παύλες είναι προαιρετικοί (απλά θα αγνοηθούν).",
|
||||
"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.": "Η κρυπτογραφημένη αναφορά χρήσης στέλνεται καθημερινά. Χρησιμοποιείται για να παραχθούν στατιστικές για τα λειτουργικά συστήματα που χρησιμοποιούνται, τα μεγέθη των φακέλων και τις εκδόσεις των προγραμμάτων. Αν στο μέλλον συμπεριληφθούν και άλλα δεδομένα στην αναφορά χρήσης, τότε αυτό το παράθυρο θα εμφανιστεί ξανά.",
|
||||
"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.": "Η ταυτότητα συσκευής που έδωσες δε φαίνεται έγκυρη. Θα πρέπει να είναι μια σειρά από 52 ή 56 χαρακτήρες (γράμματα και αριθμοί). Τα κενά και οι παύλες είναι προαιρετικά (αδιάφορα).",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Η πρώτη παράμετρος γραμμής εντολών είναι η διαδρομή του φακέλου και η δεύτερη παράμετρος είναι η σχετική διαδρομή στο φάκελο.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Η πρώτη παράμετρος στη γραμμή εντολών είναι το μονοπάτι του φακέλου και η δεύτερη παράμετρος είναι το σχετικό μονοπάτι στο φάκελο.",
|
||||
"The folder ID cannot be blank.": "Η ταυτότητα του φακέλου δεν μπορεί να είναι κενή.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Η ταυτότητα του φακέλου πρέπει να είναι ένα σύντομο αναγνωριστικό (το πολύ 64 χαρακτήρες). Μπορεί να αποτελείται από γράμματα, αριθμούς, την τελεία (.), την παύλα (-) και την κάτω παύλα (_).",
|
||||
"The folder ID must be unique.": "Η ταυτότητα του φακέλου πρέπει να είναι μοναδική.",
|
||||
@@ -162,16 +164,16 @@
|
||||
"The number of versions must be a number and cannot be blank.": "Ο αριθμός εκδόσεων πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
|
||||
"The path cannot be blank.": "Το μονοπάτι δεν μπορεί να είναι κενό.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Ο χρόνος επανελέγχου για αλλαγές είναι σε δευτερόλεπτα (δηλ. θετικός αριθμός).",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"This is a major version upgrade.": "Αυτή είναι μιας σημαντική αναβάθμιση.",
|
||||
"Unknown": "Άγνωστο",
|
||||
"Unshared": "Δε μοιράζεται",
|
||||
"Unused": "Δε χρησιμοποιείται",
|
||||
"Up to Date": "Ενημερωμένος",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade": "Αναβάθμιση",
|
||||
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
|
||||
"Upgrading": "Αναβάθμιση",
|
||||
"Upload Rate": "Ταχύτητα ανεβάσματος",
|
||||
"Uptime": "Uptime",
|
||||
"Uptime": "Χρόνος απρόσκοπτης λειτουργίας",
|
||||
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
|
||||
"Version": "Έκδοση",
|
||||
"Versions Path": "Φάκελος τήρησης εκδόσεων",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "API Key",
|
||||
"About": "About",
|
||||
"Actions": "Actions",
|
||||
"Add": "Add",
|
||||
"Add Device": "Add Device",
|
||||
"Add Folder": "Add Folder",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Global Discovery",
|
||||
"Global Discovery Server": "Global Discovery Server",
|
||||
"Global State": "Global State",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore Patterns": "Ignore Patterns",
|
||||
"Ignore Permissions": "Ignore Permissions",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "API Key",
|
||||
"About": "About",
|
||||
"Actions": "Actions",
|
||||
"Add": "Add",
|
||||
"Add Device": "Add Device",
|
||||
"Add Folder": "Add Folder",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Global Discovery",
|
||||
"Global Discovery Server": "Global Discovery Server",
|
||||
"Global State": "Global State",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore Patterns": "Ignore Patterns",
|
||||
"Ignore Permissions": "Ignore Permissions",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Una nueva versión con cambios importantes puede no ser compatible con versiones anteriores.",
|
||||
"API Key": "Clave del API",
|
||||
"About": "Acerca de",
|
||||
"Actions": "Actions",
|
||||
"Add": "Añadir",
|
||||
"Add Device": "Añadir dispositivo",
|
||||
"Add Folder": "Añadir repositorio",
|
||||
@@ -42,7 +43,7 @@
|
||||
"Edit Folder": "Editar repositorio",
|
||||
"Editing": "Editando",
|
||||
"Enable UPnP": "Habilitar UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introducir las direcciones separadas por comas como \"ip:puerto\" o \"dynamic\" para el descubrimiento automático de la dirección.",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduce las direcciones separadas por comas como \"ip:puerto\" o \"dynamic\" para el descubrimiento automático de la dirección.",
|
||||
"Enter ignore patterns, one per line.": "Introducir patrones a ignorar, uno por línea.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "Versionado externo de fichero",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Descubrimiento global",
|
||||
"Global Discovery Server": "Servidor de descubrimiento global",
|
||||
"Global State": "Estado global",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrones a ignorar",
|
||||
"Ignore Permissions": "Permisos a ignorar",
|
||||
@@ -71,7 +73,7 @@
|
||||
"Keep Versions": "Mantener versiones",
|
||||
"Largest First": "Más grande primero",
|
||||
"Last File Received": "Último fichero recibido",
|
||||
"Last seen": "La última vez que se vio",
|
||||
"Last seen": "Visto por última vez",
|
||||
"Later": "Más tarde",
|
||||
"Local Discovery": "Descubrimiento local",
|
||||
"Local State": "Estado local",
|
||||
@@ -142,7 +144,7 @@
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing incluye el siguiente software o partes de él:",
|
||||
"Syncthing is restarting.": "Syncthing se está reiniciando.",
|
||||
"Syncthing is upgrading.": "Syncthing se está actualizando.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Sincthing parece no estar activo o hay un problema con tu conexión de internet. Reintentando...",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing parece no estar activo o hay un problema con tu conexión de internet. Reintentando...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing tiene problemas para procesar tu solicitud. Por favor, actualiza la página o reinicia Syncthing si el problema persiste.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Las estadísticas agregadas están disponibles públicamente en {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuración ha sido grabada pero no activada. Syncthing debe reiniciarse para activar la nueva configuración.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "Clave API",
|
||||
"About": "Acerca de",
|
||||
"Actions": "Actions",
|
||||
"Add": "Agregar",
|
||||
"Add Device": "Agregar Dispositivo",
|
||||
"Add Folder": "Agregar Repositorio",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Búsqueda en internet",
|
||||
"Global Discovery Server": "Servidor global de identificación",
|
||||
"Global State": "Estado global",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrones de exclusión",
|
||||
"Ignore Permissions": "Ignorar permisos",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "API-avain",
|
||||
"About": "Tietoja",
|
||||
"Actions": "Actions",
|
||||
"Add": "Lisää",
|
||||
"Add Device": "Lisää laite",
|
||||
"Add Folder": "Lisää kansio",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Globaali etsintä",
|
||||
"Global Discovery Server": "Globaali etsintäpalvelin",
|
||||
"Global State": "Globaali tila",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ohita",
|
||||
"Ignore Patterns": "Ohituslausekkeet",
|
||||
"Ignore Permissions": "Jätä oikeudet huomiotta",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Une nouvelle version majeure peut présenter des incompatibilités avec les versions antérieures.",
|
||||
"API Key": "Clé API",
|
||||
"About": "À propos",
|
||||
"Actions": "Actions",
|
||||
"Add": "Ajouter",
|
||||
"Add Device": "Ajouter un périphérique",
|
||||
"Add Folder": "Ajouter un répertoire",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Recherche globale",
|
||||
"Global Discovery Server": "Serveur global de recherche",
|
||||
"Global State": "État global",
|
||||
"Help": "Au secours",
|
||||
"Ignore": "Ignorer",
|
||||
"Ignore Patterns": "Modèles à éviter",
|
||||
"Ignore Permissions": "Ignorer les permissions",
|
||||
@@ -171,7 +173,7 @@
|
||||
"Upgrade To {%version%}": "Mettre à jour vers {{version}}",
|
||||
"Upgrading": "Mise à jour de Syncthing",
|
||||
"Upload Rate": "Débit d'envoi",
|
||||
"Uptime": "Uptime",
|
||||
"Uptime": "Durée de fonctionnement depuis dernier démarrage",
|
||||
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Emplacement des versions",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Az új főverzió nem kompatibilis az előző főverzióval.",
|
||||
"API Key": "API kulcs",
|
||||
"About": "Névjegy",
|
||||
"Actions": "Actions",
|
||||
"Add": "Hozzáadás",
|
||||
"Add Device": "Eszköz hozzáadása",
|
||||
"Add Folder": "Mappa hozzáadása",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Globális felfedezés",
|
||||
"Global Discovery Server": "Globális felfedező szerver",
|
||||
"Global State": "Globális állapot",
|
||||
"Help": "Help",
|
||||
"Ignore": "Visszautasítás",
|
||||
"Ignore Patterns": "Figyelmen kívül hagyás",
|
||||
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Una nuova versione principale potrebbe non essere compatibile con le versioni precedenti.",
|
||||
"API Key": "Chiave API",
|
||||
"About": "Informazioni",
|
||||
"Actions": "Azioni",
|
||||
"Add": "Aggiungi",
|
||||
"Add Device": "Aggiungi Dispositivo",
|
||||
"Add Folder": "Aggiungi Cartella",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Individuazione Globale",
|
||||
"Global Discovery Server": "Server di Ricerca Globale",
|
||||
"Global State": "Stato Globale",
|
||||
"Help": "Aiuto",
|
||||
"Ignore": "Ignora",
|
||||
"Ignore Patterns": "Schemi Esclusione File",
|
||||
"Ignore Permissions": "Ignora Permessi",
|
||||
@@ -88,7 +90,7 @@
|
||||
"No File Versioning": "Nessun Controllo Versione",
|
||||
"Notice": "Avviso",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
"Off": "Disattiva",
|
||||
"Oldest First": "Prima il meno recente",
|
||||
"Out Of Sync": "Non Sincronizzati",
|
||||
"Out of Sync Items": "Elementi Non Sincronizzati",
|
||||
@@ -96,7 +98,7 @@
|
||||
"Override Changes": "Ignora Modifiche",
|
||||
"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": "Percorso della cartella nel computer locale. Verrà creata se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Percorso di salvataggio delle versioni (lasciare vuoto per utilizzare la cartella predefinita .stversions in questa cartella).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Si prega di consultare le note di rilascio prima di eseguire un aggiornamento principale",
|
||||
"Please consult the release notes before performing a major upgrade.": "Si prega di consultare le note di rilascio prima di eseguire un aggiornamento principale.",
|
||||
"Please wait": "Attendere prego",
|
||||
"Preview": "Anteprima",
|
||||
"Preview Usage Report": "Anteprima Statistiche di Utilizzo",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "새로운 메이저 버전은 이전 버전과 호환되지 않을 수 있습니다.",
|
||||
"API Key": "API 키",
|
||||
"About": " 정보",
|
||||
"Actions": "동작",
|
||||
"Add": "추가",
|
||||
"Add Device": "기기 추가",
|
||||
"Add Folder": "폴더 추가",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "글로벌 탐색",
|
||||
"Global Discovery Server": "글로벌 탐색 서버",
|
||||
"Global State": "글로벌 서버 상태",
|
||||
"Help": "도움말",
|
||||
"Ignore": "무시",
|
||||
"Ignore Patterns": "패턴 무시",
|
||||
"Ignore Permissions": "권한 무시",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Nauja versija gali būti nesuderinama su senomis versijomis.",
|
||||
"API Key": "API raktas",
|
||||
"About": "Apie programą",
|
||||
"Actions": "Veiksmai",
|
||||
"Add": "Pridėti",
|
||||
"Add Device": "Pridėti įrenginį",
|
||||
"Add Folder": "Pridėti aplanką",
|
||||
@@ -11,7 +12,7 @@
|
||||
"All Data": "Visiems duomenims",
|
||||
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
|
||||
"Alphabetic": "Abėcėlės tvarka",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Išorinė komanda apdoroja versijų valdymą. Ji turi pašalinti failą iš sinchronizuoto aplanko.",
|
||||
"Anonymous Usage Reporting": "Anoniminė vartojimo ataskaita",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Visi supažindintojo įrenginiai bus pridėti prie jūsų įrenginių sąrašo.",
|
||||
"Automatic upgrades": "Automatiniai atnaujinimai",
|
||||
@@ -45,11 +46,11 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Įveskite dvitaškiu atskirtą \"ip:port\" adresą arba žodį \"dynamic\" norėdami gauti adresą automatiškai",
|
||||
"Enter ignore patterns, one per line.": "Suveskite nepaisomus šablonus, kiekvieną naujoje eilutėje.",
|
||||
"Error": "Klaida",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"External File Versioning": "Išorinis versijų valdymas",
|
||||
"File Pull Order": "Failų siuntimo tvarka",
|
||||
"File Versioning": "Versijų valdymas",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Ieškant pakeitimų, į failų leidimų bitus yra nekreipiama dėmesio. Naudoti FAT failų sistemose.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Programai Syncthing pakeičiant ar ištrinant failus, jie yra perkeliami į datomis pažymėtas versijas, aplanke .stversions.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Failai apsaugoti nuo pakeitimų atliktų kituose įrenginiuose, bet pakeitimai šiame įrenginyje bus nusiųsti kitiems.",
|
||||
"Folder ID": "Aplanko ID",
|
||||
"Folder Master": "Aplanko vadovas",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Visuotinis matomumas",
|
||||
"Global Discovery Server": "Visuotinio matomumo serveris",
|
||||
"Global State": "Visuotinė būsena",
|
||||
"Help": "Pagalba",
|
||||
"Ignore": "Ignoruoti",
|
||||
"Ignore Patterns": "Nepaisyti šablonų",
|
||||
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
|
||||
@@ -143,14 +145,14 @@
|
||||
"Syncthing is restarting.": "Syncthing perleidžiamas",
|
||||
"Syncthing is upgrading.": "Syncthing atsinaujina.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing išjungta arba problemos su Interneto ryšių. Bandoma iš naujo...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Atrodo, kad Syncthing, vykdydamas jūsų užklausą, susidūrė su problemomis. Prašome iš naujo įkelti puslapį, arba jei problema išlieka, iš naujo paleisti Syncthing.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Naudojimosi ataskaitą galite peržiūrėti adresu: {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Nauji nustatymai išsaugoti, bet neaktyvuoti. Perleiskite Syncthing programą iš naujo norėdami įgalinti naujus nustatymus.",
|
||||
"The device ID cannot be blank.": "Įrenginio ID negali būti tuščias.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Įrenginio ID, kurį čia reikia įvesti, gali būti rastas „Redaguoti > Rodyti ID“ dialoge kitame įrenginyje. Tarpai ir brūkšneliai nebūtini (ignoruojami).",
|
||||
"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.": "Kas dieną siunčiama šifruota naudojimo ataskaita. Ji naudojama sekti, kokios platformos naudojamos, aplankų dydžius ir programų versijas. Jei siunčiamų duomenų turinys pasikeis, šis dialogas bus parodytas iš naujo.",
|
||||
"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.": "Įvestas neteisingas įrenginio ID. Turi būti 52 ar 56 simbolių eilutė su raidėmis ir skaičiais kuriuos galima atskirti tarpu arba brūkšneliu.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Pirmasis komandų eilutės parametras yra aplanko kelias, o antrasis parametras yra santykinis kelias aplanke.",
|
||||
"The folder ID cannot be blank.": "Aplanko ID negali būti tuščias.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Aplanko vardas negali būti ilgesnis nei 64 simboliai. Galima naudoti tik raides ir skaičius bet tašką (.), brūkšnelį (-) ir pabraukimą (_).",
|
||||
"The folder ID must be unique.": "Aplanko ID turi būti unikalus.",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"A new major version may not be compatible with previous versions.": "En ny hovedversjon kan bli ikke-kompatibel med en eldre versjon.",
|
||||
"API Key": "API-nøkkel",
|
||||
"About": "Om",
|
||||
"Actions": "Handlinger",
|
||||
"Add": "Legg til",
|
||||
"Add Device": "Legg Til Enhet",
|
||||
"Add Folder": "Legg Til Mappe",
|
||||
@@ -10,8 +11,8 @@
|
||||
"Addresses": "Adresser",
|
||||
"All Data": "Alle data",
|
||||
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
|
||||
"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",
|
||||
"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",
|
||||
@@ -46,10 +47,10 @@
|
||||
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelates, ett per linje.",
|
||||
"Error": "Feilmelding",
|
||||
"External File Versioning": "Ekstern versjonskontroll",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Pull Order": "Fil henterekkefølge",
|
||||
"File Versioning": "Versjonskontroll",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fil bit-rettigheter ignoreres når forandringer oppdages. Bruk FAT filsystem. ",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til en datostemplet versjon i .stversions-katalogen når den oppdateres eller slettes av Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskyttet mot endringer som er gjort på andre enheter, men endringer som er gjort på denne enheten blir sendt til resten av gruppen.",
|
||||
"Folder ID": "Mappe ID",
|
||||
"Folder Master": "Styrende Mappe",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Global Søking",
|
||||
"Global Discovery Server": "Global Søkemotor",
|
||||
"Global State": "Global Tilstand",
|
||||
"Help": "Hjelp",
|
||||
"Ignore": "Ignorer",
|
||||
"Ignore Patterns": "Utelatelsesmønster",
|
||||
"Ignore Permissions": "Ignorer Tilgangsbit",
|
||||
@@ -69,13 +71,13 @@
|
||||
"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",
|
||||
"Largest First": "Largest First",
|
||||
"Largest First": "Største fil",
|
||||
"Last File Received": "Sist Mottatte Fil",
|
||||
"Last seen": "Sist sett",
|
||||
"Later": "Senere",
|
||||
"Local Discovery": "Lokal Søking",
|
||||
"Local State": "Lokal Tilstand",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Major Upgrade": "Hovedoppgradering",
|
||||
"Maximum Age": "Maksimal Levetid",
|
||||
"Metadata Only": "Kun metadata",
|
||||
"Move to top of queue": "Flytt til topp av kø",
|
||||
@@ -83,27 +85,27 @@
|
||||
"Never": "Aldri",
|
||||
"New Device": "Ny Enhet",
|
||||
"New Folder": "Ny Mappe",
|
||||
"Newest First": "Newest First",
|
||||
"Newest First": "Den nyeste først",
|
||||
"No": "Nei",
|
||||
"No File Versioning": "Ingen Versjonskontroll",
|
||||
"Notice": "Merknad",
|
||||
"OK": "OK",
|
||||
"Off": "Av",
|
||||
"Oldest First": "Oldest First",
|
||||
"Oldest First": "Den eldste først",
|
||||
"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",
|
||||
"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. Blir opprettet om 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).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please consult the release notes before performing a major upgrade.": "Se \"release notes\" før en hovedoppgradering utføres.",
|
||||
"Please wait": "Vennligst vent",
|
||||
"Preview": "Forhåndsvisning",
|
||||
"Preview Usage Report": "Forhåndsvisning Av Datainnsamling",
|
||||
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
|
||||
"RAM Utilization": "RAM-utnyttelse",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Random": "TIlfeldig",
|
||||
"Release Notes": "Utgivelsesnotat",
|
||||
"Rescan": "Skann på nytt",
|
||||
"Rescan All": "Skann alt på nytt",
|
||||
"Rescan Interval": "Skanneintervall",
|
||||
@@ -130,7 +132,7 @@
|
||||
"Shutdown Complete": "Avslutning fullført",
|
||||
"Simple File Versioning": "Enkel Versjonskontroll",
|
||||
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søker kun i en mappe)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Smallest First": "Den minste først",
|
||||
"Source Code": "Kildekode",
|
||||
"Staggered File Versioning": "Forskjøvet Versjonskontroll",
|
||||
"Start Browser": "Start Nettleser",
|
||||
@@ -150,7 +152,7 @@
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhet-IDen lagt til her kan hentes fram via \"Rediger > Vis ID\"-dialogboksen på den andre enheten. Mellomrom og bindestrek er valgfritt (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.": "Kryptert informasjon om bruken av programmet blir gjort daglig. Dette blir brukt til å følge med på vanlig brukte systemoppsett, størrelser på mapper, og versjoner av programmet. Om datasettet endrer seg vil denne dialogboksen dukke opp og du vil bli bedt om å godkjenne dette.",
|
||||
"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.": "IDen for denne enheten er ikke godkjent. Det bør være 52 eller 56 tegn bestående av bokstaver og tall, valgfritt med mellomrom og bindestrek.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den første kommandolinje-parameteren er katalog-filbanen, det andre parametere er den relative filbanen i katalogen.",
|
||||
"The folder ID cannot be blank.": "Mappe-ID kan ikke være tom.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Mappe-IDen må være et kort kjennemerke (64 tegn eller mindre) bestående kun av bokstaver, tall og tegnene punktum (.), mellomrom (-) og strek (_).",
|
||||
"The folder ID must be unique.": "Mappe-ID må være unik.",
|
||||
@@ -162,16 +164,16 @@
|
||||
"The number of versions must be a number and cannot be blank.": "Antall versjoner må være et tall og kan ikke være tomt.",
|
||||
"The path cannot be blank.": "Plasseringen kan ikke være tom.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Antall sekund i skanneintervallet kan ikke være negativt.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"This is a major version upgrade.": "Dette er en hovedoppgradering",
|
||||
"Unknown": "Ukjent",
|
||||
"Unshared": "Ikke delt",
|
||||
"Unused": "Ikke i bruk",
|
||||
"Up to Date": "Oppdatert",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade": "Oppgradere",
|
||||
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
|
||||
"Upgrading": "Oppgraderer",
|
||||
"Upload Rate": "Opplastingsrate",
|
||||
"Uptime": "Uptime",
|
||||
"Uptime": "Oppetid",
|
||||
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
|
||||
"Version": "Versjon",
|
||||
"Versions Path": "Plassering Av Versjoner",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Het kan voorkomen dat een grote nieuwe update niet meer werkt met eerdere versies.",
|
||||
"API Key": "API-sleutel",
|
||||
"About": "Over",
|
||||
"Actions": "Acties",
|
||||
"Add": "Toevoegen",
|
||||
"Add Device": "Toestel toevoegen",
|
||||
"Add Folder": "Folder toevoegen",
|
||||
@@ -13,7 +14,7 @@
|
||||
"Alphabetic": "Alfabetisch",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Versiebeheer gebeurt door een extern commando. Het moet het bestand verwijderen van de gesynchroniseerde map.",
|
||||
"Anonymous Usage Reporting": "Bijhouden anonieme gebruikers statistieken",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Toestellen geconfigureerd op een introductie toestel zullen ook aan dit toestel worden toegevoegd.",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Toestellen die geconfigureerd worden op een introductietoestel zullen ook aan dit toestel worden toegevoegd.",
|
||||
"Automatic upgrades": "Automatisch bijwerken",
|
||||
"Bugs": "Fouten",
|
||||
"CPU Utilization": "CPU Gebruik",
|
||||
@@ -27,7 +28,7 @@
|
||||
"Copied from original": "Gekopieerd van het origineel",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 de volgende Bijdragers:",
|
||||
"Delete": "Verwijderen",
|
||||
"Device ID": "Apparaat ID",
|
||||
"Device ID": "Apparaat-ID",
|
||||
"Device Identification": "Apparaat identificatie",
|
||||
"Device Name": "Naam apparaat",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Het apparaat {{device}} ({{address}}) wenst te verbinden. Dit apparaat toevoegen?",
|
||||
@@ -45,13 +46,13 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Geef, gescheiden door komma's, \"ip:port\" adressen of \"dynamic\" voor het automatische vinden van de addressen.",
|
||||
"Enter ignore patterns, one per line.": "Geef te negeren patronen, één per regel.",
|
||||
"Error": "Fout",
|
||||
"External File Versioning": "Extern Bestandsversiebeheer",
|
||||
"File Pull Order": "Volgorde van updaten bestanden",
|
||||
"External File Versioning": "Extern versiebeheer voor bestanden",
|
||||
"File Pull Order": "Volgorde van bijwerken van bestanden",
|
||||
"File Versioning": "Versiebeheer",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Toegangsrechten voor bestanden worden genegeerd bij het zoeken naar wijzigingen. Gebruik voor FAT bestandssystemen.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Bestanden worden niet door Syncthing vervangen of verwijderd, maar verplaatst naar de .stversions-map.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Bestanden zijn beschermt tegen aanpassingen gemaakt door andere apparaten maar aanpassingen op dit apparaat worden doorgestuurd naar de rest van de cluster.",
|
||||
"Folder ID": "Folder ID",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Bestanden worden niet door Syncthing vervangen of verwijderd, maar verplaatst naar de map .stversions.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Bestanden zijn beschermt tegen aanpassingen gemaakt door andere apparaten maar aanpassingen op dit apparaat worden doorgestuurd naar de rest van het cluster.",
|
||||
"Folder ID": "Folder-ID",
|
||||
"Folder Master": "Hoofdfolder",
|
||||
"Folder Path": "Locatie folder",
|
||||
"Folders": "Folders",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Globaal zoeken",
|
||||
"Global Discovery Server": "Globale zoekserver",
|
||||
"Global State": "Globale status",
|
||||
"Help": "Help",
|
||||
"Ignore": "Negeren",
|
||||
"Ignore Patterns": "Te negeren patronen",
|
||||
"Ignore Permissions": "Rechten negeren",
|
||||
@@ -78,7 +80,7 @@
|
||||
"Major Upgrade": "Grote update",
|
||||
"Maximum Age": "Maximum leeftijd",
|
||||
"Metadata Only": "Alleen Metadata",
|
||||
"Move to top of queue": "Verplaats naar begin van wachtrij",
|
||||
"Move to top of queue": "Verplaats naar het begin van de wachtrij",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Wildcard op meerder niveaus (toepasbaar op meerdere niveaus van folders)",
|
||||
"Never": "Nooit",
|
||||
"New Device": "Nieuw Apparaat",
|
||||
@@ -122,10 +124,10 @@
|
||||
"Share With Devices": "Delen met toestellen",
|
||||
"Share this folder?": "Deze folder delen?",
|
||||
"Shared With": "Gedeeld met",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Korte aanduiding voor deze folder. Moet dezelfde zijn op alle toestellen in de cluster.",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Korte aanduiding voor deze folder. Moet dezelfde zijn op alle toestellen in het cluster.",
|
||||
"Show ID": "Toon ID",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wordt getoond in plaats van de toestel ID in de cluster staat. Wordt doorgegeven aan andere toestellen as een bijkomende standaard toestelnaam.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wordt getoond in plaats van de toestel ID in de cluster staat. Wanneer leeg wordt deze aangepast met de naam aangekondigd door het toestel.",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wordt getoond in plaats van het toestel-ID in het cluster staat. Wordt doorgegeven aan andere toestellen as een bijkomende standaard toestelnaam.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wordt getoond in plaats van het toestel-ID in het cluster status. Indien leeggelaten, dan wordt het bijgewerkt met de naam zoals het toestel aangeeft.",
|
||||
"Shutdown": "Sluit af",
|
||||
"Shutdown Complete": "Afsluiten voltooid",
|
||||
"Simple File Versioning": "Eenvoudig versiebeheer",
|
||||
@@ -146,14 +148,14 @@
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing heeft een probleem met het verwerken van je verzoek. Gelieve de pagina te vernieuwen of Syncthing te herstarten als het probleem zich blijft voordoen.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "The verzamelde statistieken zijn publiek beschikbaar op {{url}}",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "De configuratie is opslagen maar nog niet actief. Syncthing moet opnieuw opgestart worden om de nieuwe configuratie te activeren.",
|
||||
"The device ID cannot be blank.": "Het toestel ID mag niet leeg zijn.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Het verwachte toestel ID kan teruggevonden worden in het \"Aanpassen > Toon ID\" scherm op het andere toestel. Spaties en streepjes zijn facultatief (worden genegeerd).",
|
||||
"The device ID cannot be blank.": "Het toestel-ID mag niet leeg zijn.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Het verwachte toestel-ID kan teruggevonden worden in het \"Aanpassen > Toon ID\" scherm op het andere toestel. Spaties en streepjes zijn facultatief (worden genegeerd).",
|
||||
"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.": "Het versleutelde gebruiksrapport wordt dagelijks opgestuurd en wordt gebruikt om de verschillende platformen, folder groottes en versies op te volgen. Als de reeks gegevens wijzigt zal opnieuw toestemming gevraagd worden.",
|
||||
"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.": "Dit toestel ID lijkt ongeldig. Het toestel ID bestaat uit 52 of 56 letters en nummers met facultatieve spaties en streepjes.",
|
||||
"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.": "Dit toestel-ID lijkt ongeldig. Het toestel-ID bestaat uit 52 of 56 letters en nummers met facultatieve spaties en streepjes.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "De eerste parameter is het pad naar de map en de tweede parameter is het relatieve pad binnenin de map.",
|
||||
"The folder ID cannot be blank.": "De folder ID mag niet leeg zijn.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "De folder ID mag maximaal 64 tekens lang zijn en bestaat enkel uit letters, nummers, punten (.), streepjes (-) en onderstrepingstekens (_).",
|
||||
"The folder ID must be unique.": "De folder ID moet uniek zijn.",
|
||||
"The folder ID cannot be blank.": "Het folder-ID mag niet leeg zijn.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Het folder-ID is een korte aanduiding (maximaal 64 tekens lang) en bestaat enkel uit letters, nummers, punten (.), streepjes (-) en onderstrepingstekens (_).",
|
||||
"The folder ID must be unique.": "Het folder-ID moet uniek zijn.",
|
||||
"The folder path cannot be blank.": "De folder locatie mag niet leeg zijn.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De volgende intervallen worden gebruikt: het eerste uur worden versies iedere 30 seconden bewaard, de eerste dag worden versies ieder uur bewaard, de eerste 30 dagen worden versies iedere dag bewaard, tot de maximale leeftijd worden versies iedere week bewaard.",
|
||||
"The maximum age must be a number and cannot be blank.": "De maximum leeftijd moet uit cijfers bestaan en mag niet leeggelaten worden.",
|
||||
@@ -176,8 +178,8 @@
|
||||
"Version": "Versie",
|
||||
"Versions Path": "Locatie versies",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versies worden automatisch verwijderd als deze ouder zijn dan de maximale leeftijd of als ze het maximaal aantal toegestane bestanden per interval overschrijden. ",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Onthoud dat een toegevoegd toestel ook aan de andere kant moet worden toegevoegd.",
|
||||
"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.": "Onthoud, bij het toevoegen van een folder, dat de folder ID gebruikt wordt om folders tussen toestellen te verbinden. Ze zijn hoofdletter gevoelig en moeten exact hetzelfde zijn op de andere toestellen.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Wanneer een nieuw toestel wordt toegevoegd, houd er dan rekening mee dat dit toestel ook aan de andere kant moet worden toegevoegd.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Houd er rekening mee dat -bij het toevoegen van een folder- het folder-ID gebruikt wordt om folders tussen toestellen te verbinden. Ze zijn hoofdletter gevoelig en moeten exact hetzelfde zijn op de andere toestellen.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
|
||||
"full documentation": "volledige documentatie",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "API-nøkkel",
|
||||
"About": "Om",
|
||||
"Actions": "Actions",
|
||||
"Add": "Legg til",
|
||||
"Add Device": "Legg Til Eining",
|
||||
"Add Folder": "Legg Til Mappe",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Global søking",
|
||||
"Global Discovery Server": "Global søkjetenar",
|
||||
"Global State": "Global Tilstand",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignorer",
|
||||
"Ignore Patterns": "Utelatingsmønster",
|
||||
"Ignore Permissions": "Ignorer tilgangar",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "Klucz API",
|
||||
"About": "O Syncthing",
|
||||
"Actions": "Actions",
|
||||
"Add": "Dodaj",
|
||||
"Add Device": "Dodaj urządzenie",
|
||||
"Add Folder": "Dodaj folder",
|
||||
@@ -48,7 +49,7 @@
|
||||
"External File Versioning": "Zewnętrzne wersjonowanie pliku",
|
||||
"File Pull Order": "Kolejność pobierania plików",
|
||||
"File Versioning": "Wersjonowanie plików",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Uprawnienia plików są ignorowane przy poszukiwaniu zmian. Używaj w systemie plików FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Pliki są zabezpieczone przed zmianami na innym urządzeniu, jednak zmiany w tym urządzeniu będą wysłane do reszty.",
|
||||
"Folder ID": "ID folderu",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Globalne odnajdywanie",
|
||||
"Global Discovery Server": "Globalny serwer rozgłoszeniowy",
|
||||
"Global State": "Status globalny",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignoruj",
|
||||
"Ignore Patterns": "Wzorce ignorowania",
|
||||
"Ignore Permissions": "Ignoruj uprawnienia",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Uma nova versão principal pode não ser compatível com versões anteriores.",
|
||||
"API Key": "Chave da API",
|
||||
"About": "Sobre",
|
||||
"Actions": "Ações",
|
||||
"Add": "Adicionar",
|
||||
"Add Device": "Adicionar dispositivo",
|
||||
"Add Folder": "Adicionar pasta",
|
||||
@@ -45,7 +46,7 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços \"ip:porta\" separados por vírgulas ou \"dynamic\" para descobrir automaticamente o endereço.\n",
|
||||
"Enter ignore patterns, one per line.": "Insira os padrões de exclusão, um por linha.",
|
||||
"Error": "Erro",
|
||||
"External File Versioning": "Versionamento Externo de Arquivo",
|
||||
"External File Versioning": "Versionamento externo de arquivo",
|
||||
"File Pull Order": "Ordem de retirada do arquivo",
|
||||
"File Versioning": "Versionamento de arquivos",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Os bits de permissão de um arquivo são ignorados durante as verificações. Use em sistemas de arquivo FAT.",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Descoberta global",
|
||||
"Global Discovery Server": "Servidor de descoberta global",
|
||||
"Global State": "Estado global",
|
||||
"Help": "Ajuda",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Padrões de exclusão",
|
||||
"Ignore Permissions": "Ignorar permissões",
|
||||
@@ -148,7 +150,7 @@
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi salva mas ainda não foi ativada. O Syncthing precisa ser reiniciado para a ativação da nova configuração.",
|
||||
"The device ID cannot be blank.": "O ID de dispositivo não pode ficar vazio.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a ser inserido aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífens são opcionais (ignorados).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas comuns, tamanhos de pastas e versões da aplicação. Se o tipo de dados do relatório for alterado, esta janela te notificará novamente.",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "O ID de dispositivo inserido não parece ser válido. Ele deve ter entre 52 e 56 caracteres e ser composto de letras e números, com espaços e hífens opcionais.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "O primeiro argumento da linha de comando é o caminho da pasta e o segundo é o caminho relativo à pasta.",
|
||||
"The folder ID cannot be blank.": "O ID da pasta não pode ficar vazio.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Uma nova versão principal pode não ser compatível com versões anteriores.",
|
||||
"API Key": "Chave da API",
|
||||
"About": "Acerca da aplicação",
|
||||
"Actions": "Acções",
|
||||
"Add": "Adicionar",
|
||||
"Add Device": "Adicionar dispositivo",
|
||||
"Add Folder": "Adicionar pasta",
|
||||
@@ -45,9 +46,9 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza endereços \"ip:porto\" separados por vírgulas ou \"dynamic\" para descobrir automaticamente o endereço.",
|
||||
"Enter ignore patterns, one per line.": "Escreva os padrões de exclusão, um por linha.",
|
||||
"Error": "Erro",
|
||||
"External File Versioning": "Controle de versões de ficheiros externo",
|
||||
"External File Versioning": "Externa",
|
||||
"File Pull Order": "Ordem de obtenção de ficheiros",
|
||||
"File Versioning": "Gestão de versões",
|
||||
"File Versioning": "Gestão de versões de ficheiros",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "As permissões do ficheiro são ignoradas ao procurar alterações. Utilize nos sistemas de ficheiros FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os ficheiros são movidos para versões carimbadas com o tempo numa pasta .stversions, ao serem substituídos ou apagados pelo Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os ficheiros estão protegidos contra alterações feitas noutros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Busca global",
|
||||
"Global Discovery Server": "Servidor da busca global",
|
||||
"Global State": "Estado global",
|
||||
"Help": "Ajuda",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Padrões de exclusão",
|
||||
"Ignore Permissions": "Ignorar permissões",
|
||||
@@ -69,13 +71,13 @@
|
||||
"Introducer": "Apresentador",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversão de uma dada condição (ou seja, não excluir)",
|
||||
"Keep Versions": "Manter versões",
|
||||
"Largest First": "Maior primeiro",
|
||||
"Largest First": "Primeiro os maiores",
|
||||
"Last File Received": "Último ficheiro recebido",
|
||||
"Last seen": "Última vez que foi verificado",
|
||||
"Later": "Mais tarde",
|
||||
"Local Discovery": "Busca local",
|
||||
"Local State": "Estado local",
|
||||
"Major Upgrade": "Actualização principal",
|
||||
"Major Upgrade": "Actualização importante",
|
||||
"Maximum Age": "Idade máxima",
|
||||
"Metadata Only": "Metadados apenas",
|
||||
"Move to top of queue": "Mover para o topo da fila",
|
||||
@@ -83,26 +85,26 @@
|
||||
"Never": "Nunca",
|
||||
"New Device": "Novo dispositivo",
|
||||
"New Folder": "Nova pasta",
|
||||
"Newest First": "Mais recente primeiro",
|
||||
"Newest First": "Primeiro os mais recentes",
|
||||
"No": "Não",
|
||||
"No File Versioning": "Sem gestão de versões de ficheiros",
|
||||
"No File Versioning": "Nenhuma",
|
||||
"Notice": "Avisos",
|
||||
"OK": "OK",
|
||||
"Off": "Desligado",
|
||||
"Oldest First": "Mais antigo primeiro",
|
||||
"Oldest First": "Primeiro os mais antigos",
|
||||
"Out Of Sync": "Não sincronizado",
|
||||
"Out of Sync Items": "Itens por sincronizar",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite da velocidade de envio (KiB/s)",
|
||||
"Override Changes": "Sobrepor alterações",
|
||||
"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": "Caminho para a pasta no computador local. Será criada, caso não exista. O caractere (~) pode ser utilizado como atalho para",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Caminho onde as versões são guardadas (deixe vazio para usar a pasta pré-definida .stversions na pasta).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Consulte as notas de lançamento antes de fazer uma actualização principal.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Caminho onde as versões são guardadas (deixe vazio para usar a pasta pré-definida .stversions dentro da pasta a que se refere).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Consulte as notas de lançamento antes de fazer uma actualização importante.",
|
||||
"Please wait": "Aguarde",
|
||||
"Preview": "Previsão",
|
||||
"Preview Usage Report": "Pré-visualizar relatório de utilização",
|
||||
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
|
||||
"RAM Utilization": "Utilização da RAM",
|
||||
"Random": "Aleatório",
|
||||
"Random": "Aleatória",
|
||||
"Release Notes": "Notas de lançamento",
|
||||
"Rescan": "Verificar agora",
|
||||
"Rescan All": "Verificar todas agora",
|
||||
@@ -128,11 +130,11 @@
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Apresentado ao invés do ID do dispositivo no indicador de estado do grupo. Será actualizado para o nome que o dispositivo divulga, se for deixado em branco.",
|
||||
"Shutdown": "Desligar",
|
||||
"Shutdown Complete": "Encerramento completado",
|
||||
"Simple File Versioning": "Gestão de versões de ficheiros simples",
|
||||
"Simple File Versioning": "Simples",
|
||||
"Single level wildcard (matches within a directory only)": "Caractere polivalente de um só nível (faz corresponder apenas dentro de uma pasta)",
|
||||
"Smallest First": "Menor primeiro",
|
||||
"Smallest First": "Primeiro os menores",
|
||||
"Source Code": "Código fonte",
|
||||
"Staggered File Versioning": "Gestão de versões de ficheiros escalonada",
|
||||
"Staggered File Versioning": "Escalonada",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
"Stopped": "Parado",
|
||||
"Support": "Suporte",
|
||||
@@ -162,7 +164,7 @@
|
||||
"The number of versions must be a number and cannot be blank.": "O número de versões tem que ser um número e não pode estar vazio.",
|
||||
"The path cannot be blank.": "O caminho não pode estar vazio.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações tem que ser um valor não negativo de segundos.",
|
||||
"This is a major version upgrade.": "Esta é uma actualização de uma versão principal.",
|
||||
"This is a major version upgrade.": "Esta é uma actualização para uma versão importante.",
|
||||
"Unknown": "Desconhecido",
|
||||
"Unshared": "Não partilhada",
|
||||
"Unused": "Não utilizado",
|
||||
@@ -171,7 +173,7 @@
|
||||
"Upgrade To {%version%}": "Actualizar para {{version}}",
|
||||
"Upgrading": "Actualizando",
|
||||
"Upload Rate": "Velocidade de envio",
|
||||
"Uptime": "Tempo de funcionamento contínuo",
|
||||
"Uptime": "Tempo em actividade",
|
||||
"Use HTTPS for GUI": "Utilizar HTTPS na interface gráfica",
|
||||
"Version": "Versão",
|
||||
"Versions Path": "Caminho das versões",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "Cheie API",
|
||||
"About": "Despre",
|
||||
"Actions": "Actions",
|
||||
"Add": "Adaugă",
|
||||
"Add Device": "Adaugă Dispozitiv",
|
||||
"Add Folder": "Adaugă Mapă",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Găsire Globală",
|
||||
"Global Discovery Server": "Server pentru Găsirea Globală",
|
||||
"Global State": "Status Global",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignoră",
|
||||
"Ignore Patterns": "Reguli de excludere",
|
||||
"Ignore Permissions": "Ignoră Permisiuni",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "Новое обновление основной версии может быть несовместимо с предыдущими версиями.",
|
||||
"API Key": "Ключ API",
|
||||
"About": "О программе",
|
||||
"Actions": "Actions",
|
||||
"Add": "Добавить",
|
||||
"Add Device": "Добавить устройство",
|
||||
"Add Folder": "Добавить папку",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Глобальное обнаружение",
|
||||
"Global Discovery Server": "Сервер глобального обнаружения",
|
||||
"Global State": "Глобальное состояние",
|
||||
"Help": "Помощь",
|
||||
"Ignore": "Игнорировать",
|
||||
"Ignore Patterns": "Шаблоны игнорирования",
|
||||
"Ignore Permissions": "Игнорировать файловые права доступа",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "En ny huvudversion kan eventuellt vara inkompatibel med tidigare versioner.",
|
||||
"API Key": "API-nyckel",
|
||||
"About": "Om",
|
||||
"Actions": "Actions",
|
||||
"Add": "Lägg till",
|
||||
"Add Device": "Lägg till enhet",
|
||||
"Add Folder": "Lägg till katalog",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Global uppslagning",
|
||||
"Global Discovery Server": "Global uppslagningsserver",
|
||||
"Global State": "Global status",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ignorera",
|
||||
"Ignore Patterns": "Ignorerade filmönster",
|
||||
"Ignore Permissions": "Ignorera filrättigheter",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "API Anahtarı",
|
||||
"About": "Hakkında",
|
||||
"Actions": "Actions",
|
||||
"Add": "Ekle",
|
||||
"Add Device": "Cihaz Ekle",
|
||||
"Add Folder": "Klasör Ekle",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Küresel Keşif",
|
||||
"Global Discovery Server": "Küresel Keşif Sunucusu",
|
||||
"Global State": "Küresel Durum",
|
||||
"Help": "Help",
|
||||
"Ignore": "Yoksay",
|
||||
"Ignore Patterns": "Kalıpları Yoksay",
|
||||
"Ignore Permissions": "İzinleri yoksay",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"A new major version may not be compatible with previous versions.": "Нова мажорна версія може бути несумісною із попередніми версіями.",
|
||||
"API Key": "API ключ",
|
||||
"About": "Про програму",
|
||||
"Actions": "Actions",
|
||||
"Add": "Додати",
|
||||
"Add Device": "Додати пристрій",
|
||||
"Add Folder": "Додати директорію",
|
||||
@@ -10,7 +11,7 @@
|
||||
"Addresses": "Адреси",
|
||||
"All Data": "Усі дані",
|
||||
"Allow Anonymous Usage Reporting?": "Дозволити програмі збирати анонімну статистику використання?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"Alphabetic": "За алфавітом",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Зовнішня команда керування версіями. Вона має видалити файл із директорії, що синхронізується.",
|
||||
"Anonymous Usage Reporting": "Анонімна статистика використання",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Усі пристрої, налаштовані на пристрої-рекомендувачі, будуть додані до поточного пристрою.",
|
||||
@@ -46,7 +47,7 @@
|
||||
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
|
||||
"Error": "Помилка",
|
||||
"External File Versioning": "Зовнішне керування версіями",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Pull Order": "Порядок витягнення файлів",
|
||||
"File Versioning": "Керування версіями",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Біти прав доступу до файлів будуть проігноровані під час пошуку змін. Використовуйте на файлових системах FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Файли будуть поміщатися у директорію .stversions із відповідною позначкою часу, коли вони будуть замінятися або видалятися програмою.",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "Глобальне виявлення",
|
||||
"Global Discovery Server": "Сервер для глобального виявлення",
|
||||
"Global State": "Глобальний статус",
|
||||
"Help": "Help",
|
||||
"Ignore": "Ігнорувати",
|
||||
"Ignore Patterns": "Ігнорувати шаблони",
|
||||
"Ignore Permissions": "Ігнорувати права доступу до файлів",
|
||||
@@ -69,13 +71,13 @@
|
||||
"Introducer": "Рекомендувач",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Інверсія поточної умови (тобто не виключає)",
|
||||
"Keep Versions": "Зберігати версії",
|
||||
"Largest First": "Largest First",
|
||||
"Largest First": "Спершу найбільші",
|
||||
"Last File Received": "Останній завантажений файл",
|
||||
"Last seen": "З’являвся останній раз",
|
||||
"Later": "Пізніше",
|
||||
"Local Discovery": "Локальне виявлення",
|
||||
"Local State": "Локальний статус",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Major Upgrade": "Мажорне оновлення",
|
||||
"Maximum Age": "Максимальний вік",
|
||||
"Metadata Only": "Тільки метадані",
|
||||
"Move to top of queue": "Пересунути у початок черги",
|
||||
@@ -83,27 +85,27 @@
|
||||
"Never": "Ніколи",
|
||||
"New Device": "Новий пристрій",
|
||||
"New Folder": "Нова директорія",
|
||||
"Newest First": "Newest First",
|
||||
"Newest First": "Спершу новіші",
|
||||
"No": "Ні",
|
||||
"No File Versioning": "Версіонування вимкнено",
|
||||
"Notice": "Повідомлення",
|
||||
"OK": "Гаразд",
|
||||
"Off": "Вимкнути",
|
||||
"Oldest First": "Oldest First",
|
||||
"Oldest First": "Спершу старіші",
|
||||
"Out Of Sync": "Не синхронізовано",
|
||||
"Out of Sync Items": "Не синхронізовані елементи",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ліміт швидкості віддачі (КіБ/с)",
|
||||
"Override Changes": "Перезаписати зміни",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Шлях до директорії на локальному комп’ютері. Буде створений, якщо такий не існує. Символ тильди (~) може бути використаний як ярлик для",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please consult the release notes before performing a major upgrade.": "Будь ласка перегляньте примітки до випуску перед мажорним оновленням. ",
|
||||
"Please wait": "Будь ласка, зачекайте",
|
||||
"Preview": "Попередній перегляд",
|
||||
"Preview Usage Report": "Попередній перегляд статистичного звіту",
|
||||
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
|
||||
"RAM Utilization": "Використання RAM",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Random": "Випадково",
|
||||
"Release Notes": "Примітки до випуску",
|
||||
"Rescan": "Пересканувати",
|
||||
"Rescan All": "Пересканувати усе",
|
||||
"Rescan Interval": "Інтервал для повторного сканування",
|
||||
@@ -130,7 +132,7 @@
|
||||
"Shutdown Complete": "Вимикання завершене",
|
||||
"Simple File Versioning": "Просте версіонування",
|
||||
"Single level wildcard (matches within a directory only)": "Однорівнева маска (пошук збігів лише в середині директорії) ",
|
||||
"Smallest First": "Smallest First",
|
||||
"Smallest First": "Спершу найменші",
|
||||
"Source Code": "Сирцевий код",
|
||||
"Staggered File Versioning": "Поступове версіонування",
|
||||
"Start Browser": "Запустити браузер",
|
||||
@@ -162,16 +164,16 @@
|
||||
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
|
||||
"The path cannot be blank.": "Шлях не може бути порожнім.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"This is a major version upgrade.": "Це оновлення мажорної версії",
|
||||
"Unknown": "Невідомо",
|
||||
"Unshared": "Не розповсюджується",
|
||||
"Unused": "Не використовується",
|
||||
"Up to Date": "Актуальна версія",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade": "Оновлення",
|
||||
"Upgrade To {%version%}": "Оновити до {{version}}",
|
||||
"Upgrading": "Оновлення",
|
||||
"Upload Rate": "Швидкість віддачі",
|
||||
"Uptime": "Uptime",
|
||||
"Uptime": "Тривалість роботи",
|
||||
"Use HTTPS for GUI": "Використовувати HTTPS для доступу до панелі управління",
|
||||
"Version": "Версія",
|
||||
"Versions Path": "Шлях до версій",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"A new major version may not be compatible with previous versions.": "新版本可能与之前的版本之间无法兼容",
|
||||
"API Key": "API Key",
|
||||
"About": "关于",
|
||||
"Actions": "Actions",
|
||||
"Add": "添加",
|
||||
"Add Device": "添加设备",
|
||||
"Add Folder": "添加文件夹",
|
||||
@@ -11,7 +12,7 @@
|
||||
"All Data": "所有数据",
|
||||
"Allow Anonymous Usage Reporting?": "允许匿名使用报告?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "外部命令正在接管版本控制,它可以从同步文件夹中删除文件。",
|
||||
"Anonymous Usage Reporting": "匿名使用报告",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "在介绍人设备上被添加的其它设备,也将会被添加到本机。",
|
||||
"Automatic upgrades": "自动升级",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "在互联网上寻找节点",
|
||||
"Global Discovery Server": "用以在互联网上寻找节点的Announce服务器地址",
|
||||
"Global State": "全局状态",
|
||||
"Help": "Help",
|
||||
"Ignore": "忽略",
|
||||
"Ignore Patterns": "忽略列表",
|
||||
"Ignore Permissions": "忽略文件权限",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||
"API Key": "API 金鑰",
|
||||
"About": "關於",
|
||||
"Actions": "Actions",
|
||||
"Add": "增加",
|
||||
"Add Device": "增加裝置",
|
||||
"Add Folder": "增加資料夾",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Global Discovery": "全域探索",
|
||||
"Global Discovery Server": "全域探索伺服器",
|
||||
"Global State": "全域狀態",
|
||||
"Help": "Help",
|
||||
"Ignore": "忽略",
|
||||
"Ignore Patterns": "忽略樣式",
|
||||
"Ignore Permissions": "忽略權限",
|
||||
|
||||
1
gui/assets/lang/prettyprint.js
Normal file
1
gui/assets/lang/prettyprint.js
Normal file
@@ -0,0 +1 @@
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","fi":"Finnish","fr":"French","hu":"Hungarian","it":"Italian","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian (Romania)","ru":"Russian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
|
||||
@@ -1 +1 @@
|
||||
var validLangs = ["be","bg","ca","cs","de","el","en","en-GB","es","es-ES","fi","fr","hu","it","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ro-RO","ru","sv","tr","uk","zh-CN","zh-TW"]
|
||||
var validLangs = ["bg","ca","ca@valencia","cs","de","el","en","en-GB","es","es-ES","fi","fr","hu","it","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ro-RO","ru","sv","tr","uk","zh-CN","zh-TW"]
|
||||
|
||||
175
gui/index.html
175
gui/index.html
@@ -34,27 +34,37 @@
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li ng-if="upgradeInfo && upgradeInfo.newer">
|
||||
<button type="button" class="btn navbar-btn btn-primary btn-sm" href="" ng-click="upgrade()">
|
||||
<span class="glyphicon glyphicon-chevron-up"></span> 
|
||||
<span class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li ng-if="upgradeInfo && upgradeInfo.majorNewer">
|
||||
<button type="button" class="btn navbar-btn btn-danger btn-sm" href="" ng-click="upgradeMajor()">
|
||||
<span class="glyphicon glyphicon-chevron-up"></span> 
|
||||
<span class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown" language-select></li>
|
||||
<li class="hidden-xs">
|
||||
<a href="http://docs.syncthing.net/intro/gui.html" target="_blank">
|
||||
<span class="glyphicon glyphicon-book"></span> <span translate>Help</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-cog" aria-label="Edit"></span></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-cog" aria-label="Edit"></span> <span translate>Actions</span><span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="" ng-click="editSettings()"><span class="glyphicon glyphicon-cog"></span> <span translate>Settings</span></a></li>
|
||||
<li><a href="" ng-click="idDevice()"><span class="glyphicon glyphicon-qrcode"></span> <span translate>Show ID</span></a></li>
|
||||
<li><a href="" ng-click="editSettings()"><span class="glyphicon glyphicon-cog"></span> <span translate>Settings</span></a></li>
|
||||
<li><a href="" ng-click="idDevice()"><span class="glyphicon glyphicon-qrcode"></span> <span translate>Show ID</span></a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="" ng-click="shutdown()"><span class="glyphicon glyphicon-off"></span> <span translate>Shutdown</span></a></li>
|
||||
<li><a href="" ng-click="restart()"><span class="glyphicon glyphicon-refresh"></span> <span translate>Restart</span></a></li>
|
||||
<li><a href="" ng-click="shutdown()"><span class="glyphicon glyphicon-off"></span> <span translate>Shutdown</span></a></li>
|
||||
<li><a href="" ng-click="restart()"><span class="glyphicon glyphicon-refresh"></span> <span translate>Restart</span></a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="" ng-click="about()"><span class="glyphicon glyphicon-heart-empty"></span> <span translate>About</span></a></li>
|
||||
<li class="visible-xs">
|
||||
<a href="http://docs.syncthing.net/intro/gui.html" target="_blank">
|
||||
<span class="glyphicon glyphicon-book"></span> <span translate>Help</span>
|
||||
</a>
|
||||
</li>
|
||||
<li><a href="" ng-click="about()"><span class="glyphicon glyphicon-heart-empty"></span> <span translate>About</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -73,7 +83,7 @@
|
||||
<p translate>The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.</p>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="restart()"><span class="glyphicon glyphicon-refresh"></span> <span translate>Restart</span></button>
|
||||
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="restart()"><span class="glyphicon glyphicon-refresh"></span> <span translate>Restart</span></button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,12 +97,12 @@
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<identicon data-value="device"></identicon> <span translate>New Device</span>
|
||||
<identicon data-value="device"></identicon> <span translate>New Device</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<small>{{ event.time | date:"H:mm:ss" }}:</small>
|
||||
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
|
||||
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
|
||||
Device {%device%} ({%address%}) wants to connect. Add new device?
|
||||
<span>
|
||||
@@ -100,9 +110,9 @@
|
||||
</div>
|
||||
<div class="panel-footer clearfix">
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)"><span class="glyphicon glyphicon-ok"></span> <span translate>Add</span></button>
|
||||
<button class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)"><span class="glyphicon glyphicon-remove"></span> <span translate>Ignore</span></button>
|
||||
<button class="btn btn-sm btn-default" ng-click="dismissDeviceRejection(device)"><span class="glyphicon glyphicon-time"></span> <span translate>Later</span></button>
|
||||
<button class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)"><span class="glyphicon glyphicon-ok"></span> <span translate>Add</span></button>
|
||||
<button class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)"><span class="glyphicon glyphicon-remove"></span> <span translate>Ignore</span></button>
|
||||
<button class="btn btn-sm btn-default" ng-click="dismissDeviceRejection(device)"><span class="glyphicon glyphicon-time"></span> <span translate>Later</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +132,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<small>{{ event.time | date:"H:mm:ss" }}:</small>
|
||||
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
|
||||
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
|
||||
{%device%} wants to share folder "{%folder%}".
|
||||
</span>
|
||||
@@ -133,13 +143,13 @@
|
||||
<div class="panel-footer clearfix">
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]">
|
||||
<span class="glyphicon glyphicon-ok"></span> <span translate>Add</span>
|
||||
<span class="glyphicon glyphicon-ok"></span> <span translate>Add</span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
|
||||
<span class="glyphicon glyphicon-ok"></span> <span translate>Share</span>
|
||||
<span class="glyphicon glyphicon-ok"></span> <span translate>Share</span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-default" ng-click="dismissFolderRejection(event.data.folder, event.data.device)">
|
||||
<span class="glyphicon glyphicon-time"></span> <span translate>Later</span>
|
||||
<span class="glyphicon glyphicon-time"></span> <span translate>Later</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,10 +164,10 @@
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
|
||||
<div class="panel-body">
|
||||
<p ng-repeat="err in errorList()"><small>{{err.time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
|
||||
<p ng-repeat="err in errorList()"><small>{{err.time | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span> <span translate>OK</span></button>
|
||||
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span> <span translate>OK</span></button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,51 +206,51 @@
|
||||
<table class="table table-condensed table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-folder-open"></span> <span translate>Folder Path</span></th>
|
||||
<th><span class="glyphicon glyphicon-folder-open"></span> <span translate>Folder Path</span></th>
|
||||
<td class="text-right">{{folder.path}}</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].invalid || model[folder.id].error">
|
||||
<th><span class="glyphicon glyphicon-warning-sign"></span> <span translate>Error</span></th>
|
||||
<th><span class="glyphicon glyphicon-warning-sign"></span> <span translate>Error</span></th>
|
||||
<td class="text-right">{{model[folder.id].invalid || model[folder.id].error}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-globe"></span> <span translate>Global State</span></th>
|
||||
<th><span class="glyphicon glyphicon-globe"></span> <span translate>Global State</span></th>
|
||||
<td class="text-right">{{model[folder.id].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].globalBytes | binary}}B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-home"></span> <span translate>Local State</span></th>
|
||||
<th><span class="glyphicon glyphicon-home"></span> <span translate>Local State</span></th>
|
||||
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].needFiles > 0">
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Out Of Sync</span></th>
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Out Of Sync</span></th>
|
||||
<td class="text-right">
|
||||
<a ng-click="showNeed(folder.id)" href="">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.readOnly">
|
||||
<th><span class="glyphicon glyphicon-lock"></span> <span translate>Folder Master</span></th>
|
||||
<th><span class="glyphicon glyphicon-lock"></span> <span translate>Folder Master</span></th>
|
||||
<td class="text-right">
|
||||
<span translate>Yes</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].ignorePatterns">
|
||||
<th><span class="glyphicon glyphicon-eye-close"></span> <span translate>Ignore Patterns</span></th>
|
||||
<th><span class="glyphicon glyphicon-eye-close"></span> <span translate>Ignore Patterns</span></th>
|
||||
<td class="text-right">
|
||||
<span translate>Yes</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.ignorePerms">
|
||||
<th><span class="glyphicon glyphicon-unchecked"></span> <span translate>Ignore Permissions</span></th>
|
||||
<th><span class="glyphicon glyphicon-unchecked"></span> <span translate>Ignore Permissions</span></th>
|
||||
<td class="text-right">
|
||||
<span translate>Yes</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.rescanIntervalS != 60">
|
||||
<th><span class="glyphicon glyphicon-refresh"></span> <span translate>Rescan Interval</span></th>
|
||||
<th><span class="glyphicon glyphicon-refresh"></span> <span translate>Rescan Interval</span></th>
|
||||
<td class="text-right">{{folder.rescanIntervalS}} s</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.order != 'random'">
|
||||
<th><span class="glyphicon glyphicon-sort"></span> <span translate>File Pull Order</span></th>
|
||||
<th><span class="glyphicon glyphicon-sort"></span> <span translate>File Pull Order</span></th>
|
||||
<td class="text-right" ng-switch="folder.order">
|
||||
<span ng-switch-when="random" translate>Random</span>
|
||||
<span ng-switch-when="alphabetic" translate>Alphabetic</span>
|
||||
@@ -251,7 +261,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.versioning.type">
|
||||
<th><span class="glyphicon glyphicon-tags"></span> <span translate>File Versioning</span></th>
|
||||
<th><span class="glyphicon glyphicon-tags"></span> <span translate>File Versioning</span></th>
|
||||
<td class="text-right" ng-switch="folder.versioning.type">
|
||||
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
|
||||
<span ng-switch-when="simple" translate>Simple File Versioning</span>
|
||||
@@ -259,13 +269,13 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-share-alt"></span> <span translate>Shared With</span></th>
|
||||
<th><span class="glyphicon glyphicon-share-alt"></span> <span translate>Shared With</span></th>
|
||||
<td class="text-right">{{sharesFolder(folder)}}</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.readOnly && folderStats[folder.id].lastFile">
|
||||
<th><span class="glyphicon glyphicon-transfer"></span> <span translate>Last File Received</span></th>
|
||||
<th><span class="glyphicon glyphicon-transfer"></span> <span translate>Last File Received</span></th>
|
||||
<td class="text-right">
|
||||
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm'}}">
|
||||
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
|
||||
{{folderStats[folder.id].lastFile.filename | basename}}
|
||||
</span>
|
||||
</td>
|
||||
@@ -274,10 +284,10 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button class="btn btn-sm btn-danger pull-left" ng-if="folderStatus(folder) == 'idle' && folder.readOnly && model[folder.id].needFiles > 0" ng-click="override(folder.id)" href=""><span class="glyphicon glyphicon-upload"></span> <span translate>Override Changes</span></button>
|
||||
<button class="btn btn-sm btn-danger pull-left" ng-if="folderStatus(folder) == 'idle' && folder.readOnly && model[folder.id].needFiles > 0" ng-click="override(folder.id)" href=""><span class="glyphicon glyphicon-upload"></span> <span translate>Override Changes</span></button>
|
||||
<span class="pull-right">
|
||||
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.id)"><span class="glyphicon glyphicon-refresh"></span> <span translate>Rescan</span></button>
|
||||
<button class="btn btn-sm btn-default" href="" ng-click="editFolder(folder)"><span class="glyphicon glyphicon-pencil"></span> <span translate>Edit</span></button>
|
||||
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.id)"><span class="glyphicon glyphicon-refresh"></span> <span translate>Rescan</span></button>
|
||||
<button class="btn btn-sm btn-default" href="" ng-click="editFolder(folder)"><span class="glyphicon glyphicon-pencil"></span> <span translate>Edit</span></button>
|
||||
</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
@@ -285,8 +295,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<span class="pull-right">
|
||||
<button class="btn btn-sm btn-default" ng-click="rescanAllFolders()"><span class="glyphicon glyphicon-repeat"></span> <span translate>Rescan All</span></button>
|
||||
<button class="btn btn-sm btn-default" ng-click="addFolder()"><span class="glyphicon glyphicon-plus"></span> <span translate>Add Folder</span></button>
|
||||
<button class="btn btn-sm btn-default" ng-click="rescanAllFolders()"><span class="glyphicon glyphicon-repeat"></span> <span translate>Rescan All</span></button>
|
||||
<button class="btn btn-sm btn-default" ng-click="addFolder()"><span class="glyphicon glyphicon-plus"></span> <span translate>Add Folder</span></button>
|
||||
</span>
|
||||
<div class="clearfix"></div>
|
||||
<hr class="visible-sm"/>
|
||||
@@ -309,23 +319,23 @@
|
||||
<table class="table table-condensed table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Download Rate</span></th>
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Download Rate</span></th>
|
||||
<td class="text-right">{{connectionsTotal.inbps | binary}}B/s ({{connectionsTotal.inBytesTotal | binary}}B)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-cloud-upload"></span> <span translate>Upload Rate</span></th>
|
||||
<th><span class="glyphicon glyphicon-cloud-upload"></span> <span translate>Upload Rate</span></th>
|
||||
<td class="text-right">{{connectionsTotal.outbps | binary}}B/s ({{connectionsTotal.outBytesTotal | binary}}B)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-th"></span> <span translate>RAM Utilization</span></th>
|
||||
<th><span class="glyphicon glyphicon-th"></span> <span translate>RAM Utilization</span></th>
|
||||
<td class="text-right">{{system.sys | binary}}B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-dashboard"></span> <span translate>CPU Utilization</span></th>
|
||||
<th><span class="glyphicon glyphicon-dashboard"></span> <span translate>CPU Utilization</span></th>
|
||||
<td class="text-right">{{system.cpuPercent | alwaysNumber | natural:1}}%</td>
|
||||
</tr>
|
||||
<tr ng-if="system.extAnnounceOK != undefined && announceServersTotal > 0">
|
||||
<th><span class="glyphicon glyphicon-bullhorn"></span> <span translate>Global Discovery</span></th>
|
||||
<th><span class="glyphicon glyphicon-bullhorn"></span> <span translate>Global Discovery</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="announceServersFailed.length == 0" class="data text-success">
|
||||
<span>OK</span>
|
||||
@@ -338,11 +348,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-time"></span> <span translate>Uptime</span></th>
|
||||
<th><span class="glyphicon glyphicon-time"></span> <span translate>Uptime</span></th>
|
||||
<td class="text-right">{{system.uptime | duration:"m"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-tag"></span> <span translate>Version</span></th>
|
||||
<th><span class="glyphicon glyphicon-tag"></span> <span translate>Version</span></th>
|
||||
<td class="text-right">{{versionString()}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -374,53 +384,53 @@
|
||||
<table class="table table-condensed table-striped">
|
||||
<tbody>
|
||||
<tr ng-if="connections[deviceCfg.deviceID]">
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Download Rate</span></th>
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Download Rate</span></th>
|
||||
<td class="text-right">{{connections[deviceCfg.deviceID].inbps | binary}}B/s ({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID]">
|
||||
<th><span class="glyphicon glyphicon-cloud-upload"></span> <span translate>Upload Rate</span></th>
|
||||
<th><span class="glyphicon glyphicon-cloud-upload"></span> <span translate>Upload Rate</span></th>
|
||||
<td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-link"></span> <span translate>Address</span></th>
|
||||
<th><span class="glyphicon glyphicon-link"></span> <span translate>Address</span></th>
|
||||
<td class="text-right">{{deviceAddr(deviceCfg)}}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.compression != 'metadata'">
|
||||
<th><span class="glyphicon glyphicon-compressed"></span> <span translate>Compression</span></th>
|
||||
<th><span class="glyphicon glyphicon-compressed"></span> <span translate>Compression</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="deviceCfg.compression == 'always'" translate>All Data</span>
|
||||
<span ng-if="deviceCfg.compression == 'never'" translate>Off</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.introducer">
|
||||
<th><span class="glyphicon glyphicon-thumbs-up"></span> <span translate>Introducer</span></th>
|
||||
<th><span class="glyphicon glyphicon-thumbs-up"></span> <span translate>Introducer</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID]">
|
||||
<th><span class="glyphicon glyphicon-tag"></span> <span translate>Version</span></th>
|
||||
<th><span class="glyphicon glyphicon-tag"></span> <span translate>Version</span></th>
|
||||
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
|
||||
</tr>
|
||||
<tr ng-if="!connections[deviceCfg.deviceID]">
|
||||
<th><span class="glyphicon glyphicon-eye-open"></span> <span translate>Last seen</span></th>
|
||||
<th><span class="glyphicon glyphicon-eye-open"></span> <span translate>Last seen</span></th>
|
||||
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
|
||||
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
|
||||
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="glyphicon glyphicon-hdd"></span> <span translate>Folders</span></th>
|
||||
<th><span class="glyphicon glyphicon-hdd"></span> <span translate>Folders</span></th>
|
||||
<td class="text-right">{{deviceFolders(deviceCfg).join(", ")}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="pull-right"><a class="btn btn-sm btn-default" href="" ng-click="editDevice(deviceCfg)"><span class="glyphicon glyphicon-pencil"></span> <span translate>Edit</span></a></span>
|
||||
<span class="pull-right"><a class="btn btn-sm btn-default" href="" ng-click="editDevice(deviceCfg)"><span class="glyphicon glyphicon-pencil"></span> <span translate>Edit</span></a></span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-sm btn-default pull-right" ng-click="addDevice()"><span class="glyphicon glyphicon-plus"></span> <span translate>Add Device</span></button>
|
||||
<button class="btn btn-sm btn-default pull-right" ng-click="addDevice()"><span class="glyphicon glyphicon-plus"></span> <span translate>Add Device</span></button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -433,12 +443,12 @@
|
||||
<nav class="navbar navbar-default navbar-fixed-bottom">
|
||||
<div class="container">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/wiki" target="_blank"><span class="glyphicon glyphicon-book"></span> <span translate>Documentation</span></a></li>
|
||||
<li><a class="navbar-link" href="https://forum.syncthing.net" target="_blank"><span class="glyphicon glyphicon-question-sign"></span> <span translate>Support</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases" target="_blank"><span class="glyphicon glyphicon-info-sign"></span> <span translate>Changelog</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues" target="_blank"><span class="glyphicon glyphicon-warning-sign"></span> <span translate>Bugs</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing" target="_blank"><span class="glyphicon glyphicon-wrench"></span> <span translate>Source Code</span></a></li>
|
||||
<li><a class="navbar-link" href="https://twitter.com/syncthing" target="_blank"><span class="glyphicon glyphicon-send"></span> Twitter</a></li>
|
||||
<li><a class="navbar-link" href="http://docs.syncthing.net/" target="_blank"><span class="glyphicon glyphicon-book"></span> <span translate>Documentation</span></a></li>
|
||||
<li><a class="navbar-link" href="https://forum.syncthing.net" target="_blank"><span class="glyphicon glyphicon-question-sign"></span> <span translate>Support</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases" target="_blank"><span class="glyphicon glyphicon-info-sign"></span> <span translate>Changelog</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues" target="_blank"><span class="glyphicon glyphicon-warning-sign"></span> <span translate>Bugs</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing" target="_blank"><span class="glyphicon glyphicon-wrench"></span> <span translate>Source Code</span></a></li>
|
||||
<li><a class="navbar-link" href="https://twitter.com/syncthing" target="_blank"><span class="glyphicon glyphicon-send"></span> Twitter</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -506,8 +516,8 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="upgrade()"><span class="glyphicon glyphicon-ok"></span> <span translate>Upgrade</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="upgrade()"><span class="glyphicon glyphicon-ok"></span> <span translate>Upgrade</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -585,9 +595,9 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button ng-if="editingExisting && !editingSelf" type="button" class="btn btn-danger pull-left btn-sm" ng-click="deleteDevice()"><span class="glyphicon glyphicon-minus"></span> <span translate>Delete</span></button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button ng-if="editingExisting && !editingSelf" type="button" class="btn btn-danger pull-left btn-sm" ng-click="deleteDevice()"><span class="glyphicon glyphicon-minus"></span> <span translate>Delete</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -750,10 +760,10 @@
|
||||
<div translate ng-show="!editingExisting">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.</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveFolder()" ng-disabled="folderEditor.$invalid"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button ng-if="editingExisting" type="button" class="btn btn-danger pull-left btn-sm" ng-click="deleteFolder()"><span class="glyphicon glyphicon-minus"></span> <span translate>Delete</span></button>
|
||||
<button id="editIgnoresButton" ng-if="editingExisting" type="button" class="btn btn-default pull-left btn-sm" ng-click="editIgnores()"><span class="glyphicon glyphicon-eye-close"></span> <span translate>Ignore Patterns</span></button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveFolder()" ng-disabled="folderEditor.$invalid"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button ng-if="editingExisting" type="button" class="btn btn-danger pull-left btn-sm" ng-click="deleteFolder()"><span class="glyphicon glyphicon-minus"></span> <span translate>Delete</span></button>
|
||||
<button id="editIgnoresButton" ng-if="editingExisting" type="button" class="btn btn-default pull-left btn-sm" ng-click="editIgnores()"><span class="glyphicon glyphicon-eye-close"></span> <span translate>Ignore Patterns</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -773,7 +783,7 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://github.com/syncthing/syncthing/wiki/Ignoring-Files" target="_blank" translate>full documentation</a>):</p>
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="http://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
|
||||
<dl class="dl-horizontal dl-narrow small">
|
||||
<dt><code>!</code></dt> <dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
|
||||
<dt><code>*</code></dt> <dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
|
||||
@@ -783,8 +793,8 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.path}}{{system.pathSeparator}}.stignore</code></div>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-dismiss="modal" ng-click="saveIgnores()"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-dismiss="modal" ng-click="saveIgnores()"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -905,8 +915,8 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveSettings()"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveSettings()"><span class="glyphicon glyphicon-ok"></span> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -927,8 +937,8 @@
|
||||
<pre ng-if="reportPreview"><small>{{reportData | json}}</small></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success btn-sm" ng-click="acceptUR()"><span class="glyphicon glyphicon-ok"></span> <span translate>Yes</span></button>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="declineUR()"><span class="glyphicon glyphicon-remove"></span> <span translate>No</span></button>
|
||||
<button type="button" class="btn btn-success btn-sm" ng-click="acceptUR()"><span class="glyphicon glyphicon-ok"></span> <span translate>Yes</span></button>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="declineUR()"><span class="glyphicon glyphicon-remove"></span> <span translate>No</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -948,7 +958,7 @@
|
||||
<pre><small>{{reportData | json}}</small></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-ok"></span> <span translate>OK</span></button>
|
||||
<button type="button" class="btn btn-success btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-ok"></span> <span translate>OK</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -979,7 +989,7 @@
|
||||
<a href="" ng-click="bumpFile(neededFolder, f.name)" title="{{'Move to top of queue' | translate}}">
|
||||
<span class="glyphicon glyphicon-eject"></span>
|
||||
</a>
|
||||
<span title="{{f.name}}"> {{f.name | basename}}</span>
|
||||
<span title="{{f.name}}"> {{f.name | basename}}</span>
|
||||
</td>
|
||||
|
||||
<!-- Size/Progress -->
|
||||
@@ -1026,6 +1036,7 @@
|
||||
<li class="auto-generated">Andrew Dunham</li>
|
||||
<li class="auto-generated">Arthur Axel fREW Schmidt</li>
|
||||
<li class="auto-generated">Audrius Butkevicius</li>
|
||||
<li class="auto-generated">Bart De Vries</li>
|
||||
<li class="auto-generated">Ben Curthoys</li>
|
||||
<li class="auto-generated">Ben Schulz</li>
|
||||
<li class="auto-generated">Ben Sidhom</li>
|
||||
@@ -1034,6 +1045,7 @@
|
||||
<li class="auto-generated">Caleb Callaway</li>
|
||||
<li class="auto-generated">Carsten Hagemann</li>
|
||||
<li class="auto-generated">Cathryne Linenweaver</li>
|
||||
<li class="auto-generated">Chris Howie</li>
|
||||
<li class="auto-generated">Chris Joel</li>
|
||||
<li class="auto-generated">Colin Kennedy</li>
|
||||
<li class="auto-generated">Daniel Martí</li>
|
||||
@@ -1121,6 +1133,7 @@
|
||||
<script src="scripts/syncthing/core/services/localeService.js"></script>
|
||||
|
||||
<script src="assets/lang/valid-langs.js"></script>
|
||||
<script src="assets/lang/prettyprint.js"></script>
|
||||
<script src="scripts/syncthing/app.js"></script>
|
||||
<!-- / gui application code -->
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ angular.module('syncthing.core')
|
||||
return {
|
||||
restrict: 'EA',
|
||||
template:
|
||||
'<a ng-if="visible" href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">{{localesNames[currentLocale] || "English"}} <span class="caret"></span></a>'+
|
||||
'<a ng-if="visible" href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true"><span class="glyphicon glyphicon-globe"></span> {{localesNames[currentLocale] || "English"}} <span class="caret"></span></a>'+
|
||||
'<ul ng-if="visible" class="dropdown-menu">'+
|
||||
'<li ng-repeat="(i,name) in localesNames" ng-class="{active: i==currentLocale}">'+
|
||||
'<a href="#" data-ng-click="changeLanguage(i)">{{name}}</a>'+
|
||||
@@ -21,12 +21,15 @@ angular.module('syncthing.core')
|
||||
var a = availableLocales[i];
|
||||
if (localeNames[a]) {
|
||||
availableLocaleNames[a] = localeNames[a];
|
||||
} else {
|
||||
// show code lang if it is not in the dict
|
||||
availableLocaleNames[a] = '[' + a + ']';
|
||||
}
|
||||
}
|
||||
|
||||
$scope.localesNames = availableLocaleNames;
|
||||
$scope.visible = $scope.localesNames && $scope.localesNames['en'];
|
||||
|
||||
|
||||
// using $watch cause LocaleService.currentLocale will be change after receive async query accepted-languages
|
||||
// in LocaleService.readBrowserLocales
|
||||
var remove_watch = $scope.$watch(LocaleService.getCurrentLocale, function (newValue) {
|
||||
|
||||
@@ -22,9 +22,6 @@ angular.module('syncthing.core')
|
||||
|
||||
var _SYNLANG = "SYN_LANG"; // const key for localStorage
|
||||
|
||||
// native names of locales javascript escaped
|
||||
var _LOCALES_NAMES = { "af": "Afrikaans", "am": "\u12A0\u121B\u122D\u129B", "ar": "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", "as": "\u0985\u09B8\u09AE\u09C0\u09AF\u09BC\u09BE", "ast": "Asturianu", "be": "\u0411\u0435\u043B\u0430\u0440\u0443\u0441\u043A\u0430\u044F", "bg": "\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438", "bn": "\u09AC\u09BE\u0982\u09B2\u09BE", "bn-IN": "\u09AC\u09BE\u0982\u09B2\u09BE (\u09AD\u09BE\u09B0\u09A4)", "bo": "\u0F56\u0F7C\u0F51\u0F0B\u0F61\u0F72\u0F42", "br": "Brezhoneg", "brx": "\u092C\u094B\u0921\u094B", "bs": "Bosanski", "ca": "Catal\u00E0", "ca-valencia": "Catal\u00E0 (valenci\xE0)", "cs": "\u010De\u0161tina", "cy": "Welsh/Cymraeg", "da": "Dansk", "de": "Deutsch", "dgo": "\u0921\u094B\u0917\u0930\u0940", "dz": "\u0F62\u0FAB\u0F7C\u0F44\u0F0B\u0F41", "el": "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC", "en-GB": "English (GB)", "en": "English", "en-ZA": "English (ZA)", "eo": "Esperanto", "es_ES": "Espa\u00F1ol (Espa\u00F1a)", "et": "Eesti keel", "eu": "Euskara", "fa": "\u0641\u0627\u0631\u0633\u0649", "fi": "Suomi", "fr": "Fran\xE7ais", "ga": "Gaeilge", "gd": "G\xE0idhlig", "gl": "Galego", "gu": "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0", "he": "\u05E2\u05D1\u05E8\u05D9\u05EA", "hi": "\u0939\u093F\u0928\u094D\u0926\u0940", "hr": "Hrvatski", "hu": "Magyar", "id": "Bahasa Indonesia", "is": "\xCDslenska", "it": "Italiano", "ja": "\u65E5\u672C\u8A9E", "ka": "\u10E5\u10D0\u10E0\u10D7\u10E3\u10DA\u10D8", "kk": "\u049A\u0430\u0437\u0430\u049B\u0448\u0430", "km": "\u1781\u17D2\u1798\u17C2\u179A", "kmr-Latn": "Kurdish (latin script)", "kn": "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1", "ko_KR": "\uD55C\uAD6D\uC5B4", "kok": "\u0915\u094B\u0902\u0915\u0923\u0940", "ks": "\uFEDA\uFEB8\uFEE4\uFEF3\uFEAE\uFEF3", "lb": "L\xEBtzebuergesch", "lo": "\u0E9E\u0EB2\u0EAA\u0EB2\u0EA5\u0EB2\u0EA7", "lt": "Lietuvi\u0173 kalba", "lv": "Latvie\u0161u", "mai": "\u092E\u0948\u0925\u093F\u0932\u0940", "mk": "\u043C\u0430\u043A\u0435\u0434\u043E\u043D\u0441\u043A\u0438", "ml": "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02", "mn": "\u043C\u043E\u043D\u0433\u043E\u043B", "mni": "\u09AE\u09C8\u0987\u09A4\u09C8\u0987\u09B2\u09CB\u09A8", "mr": "\u092E\u0930\u093E\u0920\u0940", "my": "\u1019\u1014\u1039\u1019\u102C\u1005\u102C", "nb": "Bokm\u00E5l", "ne": "\u0928\u0947\u092A\u093E\u0932\u0940", "nl": "Nederlands", "nn": "Nynorsk", "nr": "Nd\xE9b\xE9l\xE9", "nso": "Sesotho sa Leboa", "oc": "Occitan", "om": "Afaan Oromo", "or": "\u0B13\u0B21\u0B3C\u0B3F\u0B06", "pa-IN": "\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40", "pl": "Polski", "pt": "Portugu\xEAs", "pt-BR": "Portugu\xEAs (Brasil)", "pt-PT": "Portugu\xEAs (Portugal)", "ro_RO": "Rom\u00E2n\u0103 (Rom\u00E2nia)", "ru": "\u0420\u0443\u0441\u0441\u043A\u0438\u0439", "rw": "KinyaRwanda", "sa-IN": "\u0938\u0902\u0938\u094D\u0915\u0943\u0924\u092E\u094D", "sat": "\u0938\u0902\u0925\u093E\u0932\u0940", "sd": "\uFEB2\uFEE7\uFEA9\u06BE\u06CC", "si": "\u0DC3\u0DD2\u0D82\u0DC4\u0DBD", "sid": "Sidama", "sk": "Sloven\u010Dina", "sl": "Sloven\u0161\u010Dina", "sq": "Shqip", "sr": "\u0441\u0440\u043F\u0441\u043A\u0438", "sr-Latn": "Srpski latinicom", "ss": "SiSwati", "st": "Sesotho", "sv": "Svenska", "sw-TZ": "Kiswahili", "ta": "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", "te": "\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41", "tg": "\u0442\u043E\u04B7\u0438\u043A\u04E3", "th": "\u0E20\u0E32\u0E29\u0E32\u0E44\u0E17\u0E22", "tn": "Setswana", "tr": "T\xFCrk\xE7e", "ts": "Xitsonga", "tt": "\u0442\u0430\u0442\u0430\u0440 \u0442\u0435\u043B\u0435", "ug": "\uFE89\u06C7\uFEF2\uFECF\u06C7\uFEAD\u0686\u06D5", "uk": "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", "uz": "\u045E\u0437\u0431\u0435\u043A", "ve": "Tshiven\u1E13a", "vi": "Ti\u1EBFng vi\u1EC7t", "xh": "IsiXhosa", "zh-CN": "\u4E2D\u6587 (\u7B80\u4F53)", "zh-TW": "\u4E2D\u6587 (\u6B63\u9AD4)", "zu": "IsiZulu" };
|
||||
|
||||
this.setDefaultLocale = function (locale) {
|
||||
_defaultLocale = locale;
|
||||
};
|
||||
@@ -114,7 +111,8 @@ angular.module('syncthing.core')
|
||||
useLocale: useLocale,
|
||||
getCurrentLocale: function() { return $translate.use() },
|
||||
getAvailableLocales: function() { return _availableLocales },
|
||||
getLocalesDisplayNames: function() { return _LOCALES_NAMES }
|
||||
// langPrettyprint comes from an included global
|
||||
getLocalesDisplayNames: function() { return langPrettyprint }
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -47,7 +47,9 @@ func (b *Broadcast) writer() {
|
||||
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
l.Warnln("Broadcast: interface addresses:", err)
|
||||
if debug {
|
||||
l.Debugln("Broadcast: interface addresses:", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ type FolderConfiguration struct {
|
||||
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
|
||||
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
|
||||
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
|
||||
LenientMtimes bool `xml:"lenientMtimes" json:"lenientMTimes"`
|
||||
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
|
||||
Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
|
||||
Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
|
||||
@@ -240,6 +239,7 @@ type OptionsConfiguration struct {
|
||||
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
|
||||
SymlinksEnabled bool `xml:"symlinksEnabled" json:"symlinksEnabled" default:"true"`
|
||||
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
|
||||
DatabaseBlockCacheMiB int `xml:"databaseBlockCacheMiB" json:"databaseBlockCacheMiB" default:"0"`
|
||||
}
|
||||
|
||||
func (orig OptionsConfiguration) Copy() OptionsConfiguration {
|
||||
|
||||
@@ -52,6 +52,7 @@ func TestDefaultValues(t *testing.T) {
|
||||
ProgressUpdateIntervalS: 5,
|
||||
SymlinksEnabled: true,
|
||||
LimitBandwidthInLan: false,
|
||||
DatabaseBlockCacheMiB: 0,
|
||||
}
|
||||
|
||||
cfg := New(device1)
|
||||
@@ -158,6 +159,7 @@ func TestOverriddenValues(t *testing.T) {
|
||||
ProgressUpdateIntervalS: 10,
|
||||
SymlinksEnabled: false,
|
||||
LimitBandwidthInLan: true,
|
||||
DatabaseBlockCacheMiB: 42,
|
||||
}
|
||||
|
||||
cfg, err := Load("testdata/overridenvalues.xml", device1)
|
||||
|
||||
1
internal/config/testdata/overridenvalues.xml
vendored
1
internal/config/testdata/overridenvalues.xml
vendored
@@ -23,5 +23,6 @@
|
||||
<progressUpdateIntervalS>10</progressUpdateIntervalS>
|
||||
<symlinksEnabled>false</symlinksEnabled>
|
||||
<limitBandwidthInLan>true</limitBandwidthInLan>
|
||||
<databaseBlockCacheMiB>42</databaseBlockCacheMiB>
|
||||
</options>
|
||||
</configuration>
|
||||
|
||||
@@ -45,6 +45,7 @@ const (
|
||||
KeyTypeBlock
|
||||
KeyTypeDeviceStatistic
|
||||
KeyTypeFolderStatistic
|
||||
KeyTypeVirtualMtime
|
||||
)
|
||||
|
||||
type fileVersion struct {
|
||||
@@ -103,7 +104,14 @@ const batchFlushSize = 64
|
||||
// device (32 bytes)
|
||||
// name (variable size)
|
||||
func deviceKey(folder, device, file []byte) []byte {
|
||||
k := make([]byte, 1+64+32+len(file))
|
||||
return deviceKeyInto(nil, folder, device, file)
|
||||
}
|
||||
|
||||
func deviceKeyInto(k []byte, folder, device, file []byte) []byte {
|
||||
reqLen := 1 + 64 + 32 + len(file)
|
||||
if len(k) < reqLen {
|
||||
k = make([]byte, reqLen)
|
||||
}
|
||||
k[0] = KeyTypeDevice
|
||||
if len(folder) > 64 {
|
||||
panic("folder name too long")
|
||||
@@ -111,7 +119,7 @@ func deviceKey(folder, device, file []byte) []byte {
|
||||
copy(k[1:], []byte(folder))
|
||||
copy(k[1+64:], device[:])
|
||||
copy(k[1+64+32:], []byte(file))
|
||||
return k
|
||||
return k[:reqLen]
|
||||
}
|
||||
|
||||
func deviceKeyName(key []byte) []byte {
|
||||
@@ -314,6 +322,8 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) i
|
||||
}
|
||||
|
||||
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, myID uint64) int64 {
|
||||
mtimeRepo := NewVirtualMtimeRepo(db, string(folder))
|
||||
|
||||
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
|
||||
var tf FileInfoTruncated
|
||||
err := tf.UnmarshalXDR(dbi.Value())
|
||||
@@ -337,6 +347,7 @@ func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.F
|
||||
l.Debugf("batch.Put %p %x", batch, dbi.Key())
|
||||
}
|
||||
batch.Put(dbi.Key(), bs)
|
||||
mtimeRepo.DeleteMtime(tf.Name)
|
||||
ldbUpdateGlobal(db, batch, folder, device, deviceKeyName(dbi.Key()), f.Version)
|
||||
return ts
|
||||
}
|
||||
@@ -366,9 +377,10 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
|
||||
}()
|
||||
|
||||
var maxLocalVer int64
|
||||
var fk []byte
|
||||
for _, f := range fs {
|
||||
name := []byte(f.Name)
|
||||
fk := deviceKey(folder, device, name)
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
@@ -723,6 +735,7 @@ func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iter
|
||||
dbi := snap.NewIterator(util.BytesPrefix(globalKey(folder, prefix)), nil)
|
||||
defer dbi.Release()
|
||||
|
||||
var fk []byte
|
||||
for dbi.Next() {
|
||||
var vl versionList
|
||||
err := vl.UnmarshalXDR(dbi.Value())
|
||||
@@ -734,7 +747,7 @@ func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iter
|
||||
panic("no versions?")
|
||||
}
|
||||
name := globalKeyName(dbi.Key())
|
||||
fk := deviceKey(folder, vl.versions[0].device, name)
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, vl.versions[0].device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
@@ -811,6 +824,7 @@ func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterat
|
||||
dbi := snap.NewIterator(&util.Range{Start: start, Limit: limit}, nil)
|
||||
defer dbi.Release()
|
||||
|
||||
var fk []byte
|
||||
nextFile:
|
||||
for dbi.Next() {
|
||||
var vl versionList
|
||||
@@ -848,7 +862,7 @@ nextFile:
|
||||
// We haven't found a valid copy of the file with the needed version.
|
||||
continue nextFile
|
||||
}
|
||||
fk := deviceKey(folder, vl.versions[i].device, name)
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, vl.versions[i].device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
@@ -1009,6 +1023,8 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
if debugDB {
|
||||
l.Debugf("new batch %p", batch)
|
||||
}
|
||||
|
||||
var fk []byte
|
||||
for dbi.Next() {
|
||||
gk := dbi.Key()
|
||||
var vl versionList
|
||||
@@ -1025,7 +1041,7 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
name := globalKeyName(gk)
|
||||
var newVL versionList
|
||||
for _, version := range vl.versions {
|
||||
fk := deviceKey(folder, version.device, name)
|
||||
fk = deviceKeyInto(fk[:cap(fk)], folder, version.device, name)
|
||||
if debugDB {
|
||||
l.Debugf("snap.Get %p %x", snap, fk)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// NamespacedKV is a simple key-value store using a specific namespace within
|
||||
@@ -29,6 +30,27 @@ func NewNamespacedKV(db *leveldb.DB, prefix string) *NamespacedKV {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset removes all entries in this namespace.
|
||||
func (n *NamespacedKV) Reset() {
|
||||
it := n.db.NewIterator(util.BytesPrefix(n.prefix), nil)
|
||||
defer it.Release()
|
||||
batch := new(leveldb.Batch)
|
||||
for it.Next() {
|
||||
batch.Delete(it.Key())
|
||||
if batch.Len() > batchFlushSize {
|
||||
if err := n.db.Write(batch, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
batch.Reset()
|
||||
}
|
||||
}
|
||||
if batch.Len() > 0 {
|
||||
if err := n.db.Write(batch, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PutInt64 stores a new int64. Any existing value (even if of another type)
|
||||
// is overwritten.
|
||||
func (n *NamespacedKV) PutInt64(key string, val int64) {
|
||||
@@ -89,6 +111,24 @@ func (n NamespacedKV) String(key string) (string, bool) {
|
||||
return string(valBs), true
|
||||
}
|
||||
|
||||
// PutBytes stores a new byte slice. Any existing value (even if of another type)
|
||||
// is overwritten.
|
||||
func (n *NamespacedKV) PutBytes(key string, val []byte) {
|
||||
keyBs := append(n.prefix, []byte(key)...)
|
||||
n.db.Put(keyBs, val, nil)
|
||||
}
|
||||
|
||||
// Bytes returns the stored value as a raw byte slice and a boolean that
|
||||
// is false if no value was stored at the key.
|
||||
func (n NamespacedKV) Bytes(key string) ([]byte, bool) {
|
||||
keyBs := append(n.prefix, []byte(key)...)
|
||||
valBs, err := n.db.Get(keyBs, nil)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return valBs, true
|
||||
}
|
||||
|
||||
// Delete deletes the specified key. It is allowed to delete a nonexistent
|
||||
// key.
|
||||
func (n NamespacedKV) Delete(key string) {
|
||||
|
||||
@@ -90,3 +90,38 @@ func TestNamespacedString(t *testing.T) {
|
||||
t.Errorf("Incorrect return v %q != \"yo\" || ok %v != true", v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespacedReset(t *testing.T) {
|
||||
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n1 := NewNamespacedKV(ldb, "foo")
|
||||
|
||||
n1.PutString("test1", "yo1")
|
||||
n1.PutString("test2", "yo2")
|
||||
n1.PutString("test3", "yo3")
|
||||
|
||||
if v, ok := n1.String("test1"); v != "yo1" || !ok {
|
||||
t.Errorf("Incorrect return v %q != \"yo1\" || ok %v != true", v, ok)
|
||||
}
|
||||
if v, ok := n1.String("test2"); v != "yo2" || !ok {
|
||||
t.Errorf("Incorrect return v %q != \"yo2\" || ok %v != true", v, ok)
|
||||
}
|
||||
if v, ok := n1.String("test3"); v != "yo3" || !ok {
|
||||
t.Errorf("Incorrect return v %q != \"yo3\" || ok %v != true", v, ok)
|
||||
}
|
||||
|
||||
n1.Reset()
|
||||
|
||||
if v, ok := n1.String("test1"); v != "" || ok {
|
||||
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
|
||||
}
|
||||
if v, ok := n1.String("test2"); v != "" || ok {
|
||||
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
|
||||
}
|
||||
if v, ok := n1.String("test3"); v != "" || ok {
|
||||
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ func DropFolder(db *leveldb.DB, folder string) {
|
||||
folder: folder,
|
||||
}
|
||||
bm.Drop()
|
||||
NewVirtualMtimeRepo(db, folder).Drop()
|
||||
}
|
||||
|
||||
func normalizeFilenames(fs []protocol.FileInfo) {
|
||||
|
||||
84
internal/db/virtualmtime.go
Normal file
84
internal/db/virtualmtime.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
// This type encapsulates a repository of mtimes for platforms where file mtimes
|
||||
// can't be set to arbitrary values. For this to work, we need to store both
|
||||
// the mtime we tried to set (the "actual" mtime) as well as the mtime the file
|
||||
// has when we're done touching it (the "disk" mtime) so that we can tell if it
|
||||
// was changed. So in GetMtime(), it's not sufficient that the record exists --
|
||||
// the argument must also equal the "disk" mtime in the record, otherwise it's
|
||||
// been touched locally and the "disk" mtime is actually correct.
|
||||
|
||||
type VirtualMtimeRepo struct {
|
||||
ns *NamespacedKV
|
||||
}
|
||||
|
||||
func NewVirtualMtimeRepo(ldb *leveldb.DB, folder string) *VirtualMtimeRepo {
|
||||
prefix := string(KeyTypeVirtualMtime) + folder
|
||||
|
||||
return &VirtualMtimeRepo{
|
||||
ns: NewNamespacedKV(ldb, prefix),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *VirtualMtimeRepo) UpdateMtime(path string, diskMtime, actualMtime time.Time) {
|
||||
if debug {
|
||||
l.Debugf("virtual mtime: storing values for path:%s disk:%v actual:%v", path, diskMtime, actualMtime)
|
||||
}
|
||||
|
||||
diskBytes, _ := diskMtime.MarshalBinary()
|
||||
actualBytes, _ := actualMtime.MarshalBinary()
|
||||
|
||||
data := append(diskBytes, actualBytes...)
|
||||
|
||||
r.ns.PutBytes(path, data)
|
||||
}
|
||||
|
||||
func (r *VirtualMtimeRepo) GetMtime(path string, diskMtime time.Time) time.Time {
|
||||
data, exists := r.ns.Bytes(path)
|
||||
if !exists {
|
||||
// Absense of debug print is significant enough in itself here
|
||||
return diskMtime
|
||||
}
|
||||
|
||||
var mtime time.Time
|
||||
if err := mtime.UnmarshalBinary(data[:len(data)/2]); err != nil {
|
||||
panic(fmt.Sprintf("Can't unmarshal stored mtime at path %s: %v", path, err))
|
||||
}
|
||||
|
||||
if mtime.Equal(diskMtime) {
|
||||
if err := mtime.UnmarshalBinary(data[len(data)/2:]); err != nil {
|
||||
panic(fmt.Sprintf("Can't unmarshal stored mtime at path %s: %v", path, err))
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("virtual mtime: return %v instead of %v for path: %s", mtime, diskMtime, path)
|
||||
}
|
||||
return mtime
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("virtual mtime: record exists, but mismatch inDisk: %v dbDisk: %v for path: %s", diskMtime, mtime, path)
|
||||
}
|
||||
return diskMtime
|
||||
}
|
||||
|
||||
func (r *VirtualMtimeRepo) DeleteMtime(path string) {
|
||||
r.ns.Delete(path)
|
||||
}
|
||||
|
||||
func (r *VirtualMtimeRepo) Drop() {
|
||||
r.ns.Reset()
|
||||
}
|
||||
80
internal/db/virtualmtime_test.go
Normal file
80
internal/db/virtualmtime_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
)
|
||||
|
||||
func TestVirtualMtimeRepo(t *testing.T) {
|
||||
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// A few repos so we can ensure they don't pollute each other
|
||||
repo1 := NewVirtualMtimeRepo(ldb, "folder1")
|
||||
repo2 := NewVirtualMtimeRepo(ldb, "folder2")
|
||||
|
||||
// Since GetMtime() returns its argument if the key isn't found or is outdated, we need a dummy to test with.
|
||||
dummyTime := time.Date(2001, time.February, 3, 4, 5, 6, 0, time.UTC)
|
||||
|
||||
// Some times to test with
|
||||
time1 := time.Date(2001, time.February, 3, 4, 5, 7, 0, time.UTC)
|
||||
time2 := time.Date(2010, time.February, 3, 4, 5, 6, 0, time.UTC)
|
||||
|
||||
file1 := "file1.txt"
|
||||
|
||||
// Files are not present at the start
|
||||
|
||||
if v := repo1.GetMtime(file1, dummyTime); !v.Equal(dummyTime) {
|
||||
t.Errorf("Mtime should be missing (%v) from repo 1 but it's %v", dummyTime, v)
|
||||
}
|
||||
|
||||
if v := repo2.GetMtime(file1, dummyTime); !v.Equal(dummyTime) {
|
||||
t.Errorf("Mtime should be missing (%v) from repo 2 but it's %v", dummyTime, v)
|
||||
}
|
||||
|
||||
repo1.UpdateMtime(file1, time1, time2)
|
||||
|
||||
// Now it should return time2 only when time1 is passed as the argument
|
||||
|
||||
if v := repo1.GetMtime(file1, time1); !v.Equal(time2) {
|
||||
t.Errorf("Mtime should be %v for disk time %v but we got %v", time2, time1, v)
|
||||
}
|
||||
|
||||
if v := repo1.GetMtime(file1, dummyTime); !v.Equal(dummyTime) {
|
||||
t.Errorf("Mtime should be %v for disk time %v but we got %v", dummyTime, dummyTime, v)
|
||||
}
|
||||
|
||||
// repo2 shouldn't know about this file
|
||||
|
||||
if v := repo2.GetMtime(file1, time1); !v.Equal(time1) {
|
||||
t.Errorf("Mtime should be %v for disk time %v in repo 2 but we got %v", time1, time1, v)
|
||||
}
|
||||
|
||||
repo1.DeleteMtime(file1)
|
||||
|
||||
// Now it should be gone
|
||||
|
||||
if v := repo1.GetMtime(file1, time1); !v.Equal(time1) {
|
||||
t.Errorf("Mtime should be %v for disk time %v but we got %v", time1, time1, v)
|
||||
}
|
||||
|
||||
// Try again but with Drop()
|
||||
|
||||
repo1.UpdateMtime(file1, time1, time2)
|
||||
repo1.Drop()
|
||||
|
||||
if v := repo1.GetMtime(file1, time1); !v.Equal(time1) {
|
||||
t.Errorf("Mtime should be %v for disk time %v but we got %v", time1, time1, v)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -110,7 +111,8 @@ func (d *Discoverer) startLocalIPv6Multicasts(localMCAddr string) {
|
||||
|
||||
v6Intfs := 0
|
||||
for _, intf := range intfs {
|
||||
if intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0 {
|
||||
// Interface flags seem to always be 0 on Windows
|
||||
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -100,9 +100,10 @@ type Event struct {
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
mask EventType
|
||||
id int
|
||||
events chan Event
|
||||
mask EventType
|
||||
id int
|
||||
events chan Event
|
||||
timeout *time.Timer
|
||||
}
|
||||
|
||||
var Default = NewLogger()
|
||||
@@ -149,9 +150,10 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
dl.Debugln("subscribe", mask)
|
||||
}
|
||||
s := &Subscription{
|
||||
mask: mask,
|
||||
id: l.nextID,
|
||||
events: make(chan Event, BufferSize),
|
||||
mask: mask,
|
||||
id: l.nextID,
|
||||
events: make(chan Event, BufferSize),
|
||||
timeout: time.NewTimer(0),
|
||||
}
|
||||
l.nextID++
|
||||
l.subs[s.id] = s
|
||||
@@ -169,19 +171,22 @@ func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
l.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Poll returns an event from the subscription or an error if the poll times
|
||||
// out of the event channel is closed. Poll should not be called concurrently
|
||||
// from multiple goroutines for a single subscription.
|
||||
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
if debug {
|
||||
dl.Debugln("poll", timeout)
|
||||
}
|
||||
|
||||
to := time.After(timeout)
|
||||
s.timeout.Reset(timeout)
|
||||
select {
|
||||
case e, ok := <-s.events:
|
||||
if !ok {
|
||||
return e, ErrClosed
|
||||
}
|
||||
return e, nil
|
||||
case <-to:
|
||||
case <-s.timeout.C:
|
||||
return Event{}, ErrTimeout
|
||||
}
|
||||
}
|
||||
@@ -253,3 +258,14 @@ func (s *BufferedSubscription) Since(id int, into []Event) []Event {
|
||||
|
||||
return into
|
||||
}
|
||||
|
||||
// Error returns a string pointer suitable for JSON marshalling errors. It
|
||||
// retains the "null on sucess" semantics, but ensures the error result is a
|
||||
// string regardless of the underlying concrete error type.
|
||||
func Error(err error) *string {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
str := err.Error()
|
||||
return &str
|
||||
}
|
||||
|
||||
@@ -38,10 +38,12 @@ import (
|
||||
|
||||
// How many files to send in each Index/IndexUpdate message.
|
||||
const (
|
||||
indexTargetSize = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
|
||||
indexPerFileSize = 250 // Each FileInfo is approximately this big, in bytes, excluding BlockInfos
|
||||
IndexPerBlockSize = 40 // Each BlockInfo is approximately this big
|
||||
indexBatchSize = 1000 // Either way, don't include more files than this
|
||||
indexTargetSize = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
|
||||
indexPerFileSize = 250 // Each FileInfo is approximately this big, in bytes, excluding BlockInfos
|
||||
indexPerBlockSize = 40 // Each BlockInfo is approximately this big
|
||||
indexBatchSize = 1000 // Either way, don't include more files than this
|
||||
reqValidationTime = time.Hour // How long to cache validation entries for Request messages
|
||||
reqValidationCacheSize = 1000 // How many entries to aim for in the validation cache size
|
||||
)
|
||||
|
||||
type service interface {
|
||||
@@ -86,6 +88,9 @@ type Model struct {
|
||||
|
||||
addedFolder bool
|
||||
started bool
|
||||
|
||||
reqValidationCache map[string]time.Time // folder / file name => time when confirmed to exist
|
||||
rvmut sync.RWMutex // protects reqValidationCache
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -97,29 +102,31 @@ var (
|
||||
// for file data without altering the local folder in any way.
|
||||
func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, clientVersion string, ldb *leveldb.DB) *Model {
|
||||
m := &Model{
|
||||
cfg: cfg,
|
||||
db: ldb,
|
||||
finder: db.NewBlockFinder(ldb, cfg),
|
||||
progressEmitter: NewProgressEmitter(cfg),
|
||||
id: id,
|
||||
shortID: id.Short(),
|
||||
deviceName: deviceName,
|
||||
clientName: clientName,
|
||||
clientVersion: clientVersion,
|
||||
folderCfgs: make(map[string]config.FolderConfiguration),
|
||||
folderFiles: make(map[string]*db.FileSet),
|
||||
folderDevices: make(map[string][]protocol.DeviceID),
|
||||
deviceFolders: make(map[protocol.DeviceID][]string),
|
||||
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
|
||||
folderIgnores: make(map[string]*ignore.Matcher),
|
||||
folderRunners: make(map[string]service),
|
||||
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
|
||||
protoConn: make(map[protocol.DeviceID]protocol.Connection),
|
||||
rawConn: make(map[protocol.DeviceID]io.Closer),
|
||||
deviceVer: make(map[protocol.DeviceID]string),
|
||||
cfg: cfg,
|
||||
db: ldb,
|
||||
finder: db.NewBlockFinder(ldb, cfg),
|
||||
progressEmitter: NewProgressEmitter(cfg),
|
||||
id: id,
|
||||
shortID: id.Short(),
|
||||
deviceName: deviceName,
|
||||
clientName: clientName,
|
||||
clientVersion: clientVersion,
|
||||
folderCfgs: make(map[string]config.FolderConfiguration),
|
||||
folderFiles: make(map[string]*db.FileSet),
|
||||
folderDevices: make(map[string][]protocol.DeviceID),
|
||||
deviceFolders: make(map[protocol.DeviceID][]string),
|
||||
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
|
||||
folderIgnores: make(map[string]*ignore.Matcher),
|
||||
folderRunners: make(map[string]service),
|
||||
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
|
||||
protoConn: make(map[protocol.DeviceID]protocol.Connection),
|
||||
rawConn: make(map[protocol.DeviceID]io.Closer),
|
||||
deviceVer: make(map[protocol.DeviceID]string),
|
||||
reqValidationCache: make(map[string]time.Time),
|
||||
|
||||
fmut: sync.NewRWMutex(),
|
||||
pmut: sync.NewRWMutex(),
|
||||
fmut: sync.NewRWMutex(),
|
||||
pmut: sync.NewRWMutex(),
|
||||
rvmut: sync.NewRWMutex(),
|
||||
}
|
||||
if cfg.Options().ProgressUpdateIntervalS > -1 {
|
||||
go m.progressEmitter.Serve()
|
||||
@@ -163,10 +170,6 @@ func (m *Model) StartFolderRW(folder string) {
|
||||
p.versioner = factory(folder, cfg.Path(), cfg.Versioning.Params)
|
||||
}
|
||||
|
||||
if cfg.LenientMtimes {
|
||||
l.Infof("Folder %q is running with LenientMtimes workaround. Syncing may not work properly.", folder)
|
||||
}
|
||||
|
||||
go p.Serve()
|
||||
}
|
||||
|
||||
@@ -733,33 +736,61 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
|
||||
return nil, fmt.Errorf("protocol error: unknown flags 0x%x in Request message", flags)
|
||||
}
|
||||
|
||||
// Verify that the requested file exists in the local model.
|
||||
m.fmut.RLock()
|
||||
folderFiles, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
// Verify that the requested file exists in the local model. We only need
|
||||
// to validate this file if we haven't done so recently, so we keep a
|
||||
// cache of successfull results. "Recently" can be quite a long time, as
|
||||
// we remove validation cache entries when we detect local changes. If
|
||||
// we're out of sync here and the file actually doesn't exist any more, or
|
||||
// has shrunk or something, then we'll anyway get a read error that we
|
||||
// pass on to the other side.
|
||||
|
||||
if !ok {
|
||||
l.Warnf("Request from %s for file %s in nonexistent folder %q", deviceID, name, folder)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
m.rvmut.RLock()
|
||||
validated := m.reqValidationCache[folder+"/"+name]
|
||||
m.rvmut.RUnlock()
|
||||
|
||||
lf, ok := folderFiles.Get(protocol.LocalDeviceID, name)
|
||||
if !ok {
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
if time.Since(validated) > reqValidationTime {
|
||||
m.fmut.RLock()
|
||||
folderFiles, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
if lf.IsInvalid() || lf.IsDeleted() {
|
||||
if debug {
|
||||
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d; invalid: %v", m, deviceID, folder, name, offset, size, lf)
|
||||
if !ok {
|
||||
l.Warnf("Request from %s for file %s in nonexistent folder %q", deviceID, name, folder)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
return nil, protocol.ErrInvalid
|
||||
}
|
||||
|
||||
if offset > lf.Size() {
|
||||
if debug {
|
||||
l.Debugf("%v REQ(in; nonexistent): %s: %q o=%d s=%d", m, deviceID, name, offset, size)
|
||||
// This call is really expensive for large files, as we load the full
|
||||
// block list which may be megabytes and megabytes of data to allocate
|
||||
// space for, read, and deserialize.
|
||||
lf, ok := folderFiles.Get(protocol.LocalDeviceID, name)
|
||||
if !ok {
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
|
||||
if lf.IsInvalid() || lf.IsDeleted() {
|
||||
if debug {
|
||||
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d; invalid: %v", m, deviceID, folder, name, offset, size, lf)
|
||||
}
|
||||
return nil, protocol.ErrInvalid
|
||||
}
|
||||
|
||||
if offset > lf.Size() {
|
||||
if debug {
|
||||
l.Debugf("%v REQ(in; nonexistent): %s: %q o=%d s=%d", m, deviceID, name, offset, size)
|
||||
}
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
|
||||
m.rvmut.Lock()
|
||||
m.reqValidationCache[folder+"/"+name] = time.Now()
|
||||
if len(m.reqValidationCache) > reqValidationCacheSize {
|
||||
// Don't let the cache grow infinitely
|
||||
for name, validated := range m.reqValidationCache {
|
||||
if time.Since(validated) > time.Minute {
|
||||
delete(m.reqValidationCache, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.rvmut.Unlock()
|
||||
}
|
||||
|
||||
if debug && deviceID != protocol.LocalDeviceID {
|
||||
@@ -771,7 +802,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
|
||||
|
||||
var reader io.ReaderAt
|
||||
var err error
|
||||
if lf.IsSymlink() {
|
||||
if info, err := os.Lstat(fn); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
||||
target, _, err := symlinks.Read(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1052,7 +1083,7 @@ func sendIndexTo(initial bool, minLocalVer int64, conn protocol.Connection, fold
|
||||
}
|
||||
|
||||
batch = append(batch, f)
|
||||
currentBatchSize += indexPerFileSize + len(f.Blocks)*IndexPerBlockSize
|
||||
currentBatchSize += indexPerFileSize + len(f.Blocks)*indexPerBlockSize
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -1075,6 +1106,11 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
|
||||
m.fmut.RLock()
|
||||
m.folderFiles[folder].Update(protocol.LocalDeviceID, fs)
|
||||
m.fmut.RUnlock()
|
||||
m.rvmut.Lock()
|
||||
for _, f := range fs {
|
||||
delete(m.reqValidationCache, folder+"/"+f.Name)
|
||||
}
|
||||
m.rvmut.Unlock()
|
||||
|
||||
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
|
||||
"folder": folder,
|
||||
@@ -1222,6 +1258,7 @@ nextSub:
|
||||
TempNamer: defTempNamer,
|
||||
TempLifetime: time.Duration(m.cfg.Options().KeepTemporariesH) * time.Hour,
|
||||
CurrentFiler: cFiler{m, folder},
|
||||
MtimeRepo: db.NewVirtualMtimeRepo(m.db, folderCfg.ID),
|
||||
IgnorePerms: folderCfg.IgnorePerms,
|
||||
AutoNormalize: folderCfg.AutoNormalize,
|
||||
Hashers: m.numHashers(folder),
|
||||
@@ -1260,12 +1297,13 @@ nextSub:
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
|
||||
return err
|
||||
} else if len(batch) > 0 {
|
||||
fs.Update(protocol.LocalDeviceID, batch)
|
||||
m.updateLocals(folder, batch)
|
||||
}
|
||||
|
||||
batch = batch[:0]
|
||||
// TODO: We should limit the Have scanning to start at sub
|
||||
seenPrefix := false
|
||||
var iterError error
|
||||
fs.WithHaveTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
|
||||
f := fi.(db.FileInfoTruncated)
|
||||
hasPrefix := len(subs) == 0
|
||||
@@ -1289,6 +1327,10 @@ nextSub:
|
||||
}
|
||||
|
||||
if len(batch) == batchSizeFiles {
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
iterError = err
|
||||
return false
|
||||
}
|
||||
m.updateLocals(folder, batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
@@ -1326,7 +1368,16 @@ nextSub:
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(batch) > 0 {
|
||||
|
||||
if iterError != nil {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, iterError)
|
||||
return iterError
|
||||
}
|
||||
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
|
||||
return err
|
||||
} else if len(batch) > 0 {
|
||||
m.updateLocals(folder, batch)
|
||||
}
|
||||
|
||||
@@ -1614,7 +1665,7 @@ func (m *Model) CheckFolderHealth(id string) error {
|
||||
} else if os.IsNotExist(err) {
|
||||
// If we don't have any files in the index, and the directory
|
||||
// doesn't exist, try creating it.
|
||||
err = os.MkdirAll(folder.Path(), 0700)
|
||||
err = osutil.MkdirAll(folder.Path(), 0700)
|
||||
if err == nil {
|
||||
err = folder.CreateMarker()
|
||||
}
|
||||
|
||||
@@ -176,74 +176,58 @@ func genFiles(n int) []protocol.FileInfo {
|
||||
return files
|
||||
}
|
||||
|
||||
func BenchmarkIndex10000(b *testing.B) {
|
||||
func BenchmarkIndex_10000(b *testing.B) {
|
||||
benchmarkIndex(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkIndex_100(b *testing.B) {
|
||||
benchmarkIndex(b, 100)
|
||||
}
|
||||
|
||||
func benchmarkIndex(b *testing.B, nfiles int) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.StartFolderRO("default")
|
||||
|
||||
files := genFiles(nfiles)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
func BenchmarkIndex00100(b *testing.B) {
|
||||
func BenchmarkIndexUpdate_10000_10000(b *testing.B) {
|
||||
benchmarkIndexUpdate(b, 10000, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate_10000_100(b *testing.B) {
|
||||
benchmarkIndexUpdate(b, 10000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate_10000_1(b *testing.B) {
|
||||
benchmarkIndexUpdate(b, 10000, 1)
|
||||
}
|
||||
|
||||
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(100)
|
||||
m.StartFolderRO("default")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
}
|
||||
}
|
||||
files := genFiles(nfiles)
|
||||
ufiles := genFiles(nufiles)
|
||||
|
||||
func BenchmarkIndexUpdate10000f10000(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.IndexUpdate(device1, "default", files, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate10000f00100(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
ufiles := genFiles(100)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.IndexUpdate(device1, "default", ufiles, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexUpdate10000f00001(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genFiles(10000)
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
ufiles := genFiles(1)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.IndexUpdate(device1, "default", ufiles, 0, nil)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
type FakeConnection struct {
|
||||
@@ -474,6 +458,7 @@ func TestIgnores(t *testing.T) {
|
||||
}
|
||||
|
||||
// Assure a clean start state
|
||||
ioutil.WriteFile("testdata/.stfolder", nil, 0644)
|
||||
ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644)
|
||||
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
@@ -1165,56 +1150,23 @@ func genDeepFiles(n, d int) []protocol.FileInfo {
|
||||
}
|
||||
|
||||
func BenchmarkTree_10000_50(b *testing.B) {
|
||||
benchmarkTree(b, 10000, 50)
|
||||
}
|
||||
|
||||
func BenchmarkTree_100_50(b *testing.B) {
|
||||
benchmarkTree(b, 100, 50)
|
||||
}
|
||||
|
||||
func BenchmarkTree_100_10(b *testing.B) {
|
||||
benchmarkTree(b, 100, 10)
|
||||
}
|
||||
|
||||
func benchmarkTree(b *testing.B, n1, n2 int) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(10000, 50)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTree_10000_10(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(10000, 10)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTree_00100_50(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(100, 50)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTree_00100_10(b *testing.B) {
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
m.ScanFolder("default")
|
||||
files := genDeepFiles(100, 10)
|
||||
files := genDeepFiles(n1, n2)
|
||||
|
||||
m.Index(device1, "default", files, 0, nil)
|
||||
|
||||
@@ -1222,4 +1174,5 @@ func BenchmarkTree_00100_10(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ func (s *roFolder) Serve() {
|
||||
}()
|
||||
|
||||
reschedule := func() {
|
||||
if s.intv == 0 {
|
||||
return
|
||||
}
|
||||
// Sleep a random time between 3/4 and 5/4 of the configured interval.
|
||||
sleepNanos := (s.intv.Nanoseconds()*3 + rand.Int63n(2*s.intv.Nanoseconds())) / 4
|
||||
s.timer.Reset(time.Duration(sleepNanos) * time.Nanosecond)
|
||||
|
||||
@@ -57,19 +57,19 @@ var (
|
||||
type rwFolder struct {
|
||||
stateTracker
|
||||
|
||||
model *Model
|
||||
progressEmitter *ProgressEmitter
|
||||
model *Model
|
||||
progressEmitter *ProgressEmitter
|
||||
virtualMtimeRepo *db.VirtualMtimeRepo
|
||||
|
||||
folder string
|
||||
dir string
|
||||
scanIntv time.Duration
|
||||
versioner versioner.Versioner
|
||||
ignorePerms bool
|
||||
lenientMtimes bool
|
||||
copiers int
|
||||
pullers int
|
||||
shortID uint64
|
||||
order config.PullOrder
|
||||
folder string
|
||||
dir string
|
||||
scanIntv time.Duration
|
||||
versioner versioner.Versioner
|
||||
ignorePerms bool
|
||||
copiers int
|
||||
pullers int
|
||||
shortID uint64
|
||||
order config.PullOrder
|
||||
|
||||
stop chan struct{}
|
||||
queue *jobQueue
|
||||
@@ -87,18 +87,18 @@ func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFo
|
||||
mut: sync.NewMutex(),
|
||||
},
|
||||
|
||||
model: m,
|
||||
progressEmitter: m.progressEmitter,
|
||||
model: m,
|
||||
progressEmitter: m.progressEmitter,
|
||||
virtualMtimeRepo: db.NewVirtualMtimeRepo(m.db, cfg.ID),
|
||||
|
||||
folder: cfg.ID,
|
||||
dir: cfg.Path(),
|
||||
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
|
||||
ignorePerms: cfg.IgnorePerms,
|
||||
lenientMtimes: cfg.LenientMtimes,
|
||||
copiers: cfg.Copiers,
|
||||
pullers: cfg.Pullers,
|
||||
shortID: shortID,
|
||||
order: cfg.Order,
|
||||
folder: cfg.ID,
|
||||
dir: cfg.Path(),
|
||||
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
|
||||
ignorePerms: cfg.IgnorePerms,
|
||||
copiers: cfg.Copiers,
|
||||
pullers: cfg.Pullers,
|
||||
shortID: shortID,
|
||||
order: cfg.Order,
|
||||
|
||||
stop: make(chan struct{}),
|
||||
queue: newJobQueue(),
|
||||
@@ -109,6 +109,13 @@ func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFo
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check whether either the ignorePerm flag has been
|
||||
// set on the local host or the FlagNoPermBits has been set on the file/dir
|
||||
// which is being pulled.
|
||||
func (p *rwFolder) ignorePermissions(file protocol.FileInfo) bool {
|
||||
return p.ignorePerms || file.Flags&protocol.FlagNoPermBits != 0
|
||||
}
|
||||
|
||||
// Serve will run scans and pulls. It will return when Stop()ed or on a
|
||||
// critical error.
|
||||
func (p *rwFolder) Serve() {
|
||||
@@ -532,7 +539,7 @@ func (p *rwFolder) handleDir(file protocol.FileInfo) {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": file.Name,
|
||||
"error": err,
|
||||
"error": events.Error(err),
|
||||
"type": "dir",
|
||||
"action": "update",
|
||||
})
|
||||
@@ -540,8 +547,8 @@ func (p *rwFolder) handleDir(file protocol.FileInfo) {
|
||||
|
||||
realName := filepath.Join(p.dir, file.Name)
|
||||
mode := os.FileMode(file.Flags & 0777)
|
||||
if p.ignorePerms {
|
||||
mode = 0755
|
||||
if p.ignorePermissions(file) {
|
||||
mode = 0777
|
||||
}
|
||||
|
||||
if debug {
|
||||
@@ -569,7 +576,7 @@ func (p *rwFolder) handleDir(file protocol.FileInfo) {
|
||||
// not MkdirAll because the parent should already exist.
|
||||
mkdir := func(path string) error {
|
||||
err = os.Mkdir(path, mode)
|
||||
if err != nil || p.ignorePerms {
|
||||
if err != nil || p.ignorePermissions(file) {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(path, mode)
|
||||
@@ -592,7 +599,7 @@ func (p *rwFolder) handleDir(file protocol.FileInfo) {
|
||||
// don't handle modification times on directories, because that sucks...)
|
||||
// It's OK to change mode bits on stuff within non-writable directories.
|
||||
|
||||
if p.ignorePerms {
|
||||
if p.ignorePermissions(file) {
|
||||
p.dbUpdates <- file
|
||||
} else if err := os.Chmod(realName, mode); err == nil {
|
||||
p.dbUpdates <- file
|
||||
@@ -614,7 +621,7 @@ func (p *rwFolder) deleteDir(file protocol.FileInfo) {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": file.Name,
|
||||
"error": err,
|
||||
"error": events.Error(err),
|
||||
"type": "dir",
|
||||
"action": "delete",
|
||||
})
|
||||
@@ -631,8 +638,16 @@ func (p *rwFolder) deleteDir(file protocol.FileInfo) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = osutil.InWritableDir(osutil.Remove, realName)
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
// It was removed or it doesn't exist to start with
|
||||
p.dbUpdates <- file
|
||||
} else if _, err := os.Lstat(realName); err != nil && !os.IsPermission(err) {
|
||||
// We get an error just looking at the directory, and it's not a
|
||||
// permission problem. Lets assume the error is in fact some variant
|
||||
// of "file does not exist" (possibly expressed as some parent being a
|
||||
// file and not a directory etc) and that the delete is handled.
|
||||
p.dbUpdates <- file
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): delete: %v", p.folder, file.Name, err)
|
||||
@@ -652,7 +667,7 @@ func (p *rwFolder) deleteFile(file protocol.FileInfo) {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": file.Name,
|
||||
"error": err,
|
||||
"error": events.Error(err),
|
||||
"type": "file",
|
||||
"action": "delete",
|
||||
})
|
||||
@@ -673,10 +688,17 @@ func (p *rwFolder) deleteFile(file protocol.FileInfo) {
|
||||
err = osutil.InWritableDir(osutil.Remove, realName)
|
||||
}
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
l.Infof("Puller (folder %q, file %q): delete: %v", p.folder, file.Name, err)
|
||||
} else {
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
// It was removed or it doesn't exist to start with
|
||||
p.dbUpdates <- file
|
||||
} else if _, err := os.Lstat(realName); err != nil && !os.IsPermission(err) {
|
||||
// We get an error just looking at the file, and it's not a permission
|
||||
// problem. Lets assume the error is in fact some variant of "file
|
||||
// does not exist" (possibly expressed as some parent being a file and
|
||||
// not a directory etc) and that the delete is handled.
|
||||
p.dbUpdates <- file
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, file %q): delete: %v", p.folder, file.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,14 +722,14 @@ func (p *rwFolder) renameFile(source, target protocol.FileInfo) {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": source.Name,
|
||||
"error": err,
|
||||
"error": events.Error(err),
|
||||
"type": "file",
|
||||
"action": "delete",
|
||||
})
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": target.Name,
|
||||
"error": err,
|
||||
"error": events.Error(err),
|
||||
"type": "file",
|
||||
"action": "update",
|
||||
})
|
||||
@@ -756,6 +778,40 @@ func (p *rwFolder) renameFile(source, target protocol.FileInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
// This is the flow of data and events here, I think...
|
||||
//
|
||||
// +-----------------------+
|
||||
// | | - - - - > ItemStarted
|
||||
// | handleFile | - - - - > ItemFinished (on shortcuts)
|
||||
// | |
|
||||
// +-----------------------+
|
||||
// |
|
||||
// | copyChan (copyBlocksState; unless shortcut taken)
|
||||
// |
|
||||
// | +-----------------------+
|
||||
// | | +-----------------------+
|
||||
// +--->| | |
|
||||
// | | copierRoutine |
|
||||
// +-| |
|
||||
// +-----------------------+
|
||||
// |
|
||||
// | pullChan (sharedPullerState)
|
||||
// |
|
||||
// | +-----------------------+
|
||||
// | | +-----------------------+
|
||||
// +-->| | |
|
||||
// | | pullerRoutine |
|
||||
// +-| |
|
||||
// +-----------------------+
|
||||
// |
|
||||
// | finisherChan (sharedPullerState)
|
||||
// |
|
||||
// | +-----------------------+
|
||||
// | | |
|
||||
// +-->| finisherRoutine | - - - - > ItemFinished
|
||||
// | |
|
||||
// +-----------------------+
|
||||
|
||||
// handleFile queues the copies and pulls as necessary for a single new or
|
||||
// changed file.
|
||||
func (p *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState) {
|
||||
@@ -785,7 +841,7 @@ func (p *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocks
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": file.Name,
|
||||
"error": err,
|
||||
"error": events.Error(err),
|
||||
"type": "file",
|
||||
"action": "update",
|
||||
})
|
||||
@@ -843,7 +899,7 @@ func (p *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocks
|
||||
copyTotal: len(blocks),
|
||||
copyNeeded: len(blocks),
|
||||
reused: reused,
|
||||
ignorePerms: p.ignorePerms,
|
||||
ignorePerms: p.ignorePermissions(file),
|
||||
version: curFile.Version,
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
@@ -861,30 +917,25 @@ func (p *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocks
|
||||
|
||||
// shortcutFile sets file mode and modification time, when that's the only
|
||||
// thing that has changed.
|
||||
func (p *rwFolder) shortcutFile(file protocol.FileInfo) (err error) {
|
||||
func (p *rwFolder) shortcutFile(file protocol.FileInfo) error {
|
||||
realName := filepath.Join(p.dir, file.Name)
|
||||
if !p.ignorePerms {
|
||||
err = os.Chmod(realName, os.FileMode(file.Flags&0777))
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: %v", p.folder, file.Name, err)
|
||||
return
|
||||
if !p.ignorePermissions(file) {
|
||||
if err := os.Chmod(realName, os.FileMode(file.Flags&0777)); err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: chmod: %v", p.folder, file.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t := time.Unix(file.Modified, 0)
|
||||
err = os.Chtimes(realName, t, t)
|
||||
if err != nil {
|
||||
if p.lenientMtimes {
|
||||
err = nil
|
||||
// We accept the failure with a warning here and allow the sync to
|
||||
// continue. We'll sync the new mtime back to the other devices later.
|
||||
// If they have the same problem & setting, we might never get in
|
||||
// sync.
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: %v (continuing anyway as requested)", p.folder, file.Name, err)
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: %v", p.folder, file.Name, err)
|
||||
return
|
||||
if err := os.Chtimes(realName, t, t); err != nil {
|
||||
// Try using virtual mtimes
|
||||
info, err := os.Stat(realName)
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: unable to stat file: %v", p.folder, file.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
p.virtualMtimeRepo.UpdateMtime(file.Name, info.ModTime(), t)
|
||||
}
|
||||
|
||||
// This may have been a conflict. We should merge the version vectors so
|
||||
@@ -894,7 +945,7 @@ func (p *rwFolder) shortcutFile(file protocol.FileInfo) (err error) {
|
||||
}
|
||||
|
||||
p.dbUpdates <- file
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// shortcutSymlink changes the symlinks type if necessary.
|
||||
@@ -914,18 +965,17 @@ func (p *rwFolder) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pull
|
||||
buf := make([]byte, protocol.BlockSize)
|
||||
|
||||
for state := range in {
|
||||
if p.progressEmitter != nil {
|
||||
p.progressEmitter.Register(state.sharedPullerState)
|
||||
}
|
||||
|
||||
dstFd, err := state.tempFile()
|
||||
if err != nil {
|
||||
// Nothing more to do for this failed file (the error was logged
|
||||
// when it happened)
|
||||
// Nothing more to do for this failed file, since we couldn't create a temporary for it.
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
}
|
||||
|
||||
if p.progressEmitter != nil {
|
||||
p.progressEmitter.Register(state.sharedPullerState)
|
||||
}
|
||||
|
||||
folderRoots := make(map[string]string)
|
||||
p.model.fmut.RLock()
|
||||
for folder, cfg := range p.model.folderCfgs {
|
||||
@@ -995,6 +1045,7 @@ func (p *rwFolder) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pull
|
||||
func (p *rwFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
|
||||
for state := range in {
|
||||
if state.failed() != nil {
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1003,6 +1054,7 @@ func (p *rwFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPul
|
||||
// no point in issuing the request to the network.
|
||||
fd, err := state.tempFile()
|
||||
if err != nil {
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1053,43 +1105,26 @@ func (p *rwFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPul
|
||||
}
|
||||
}
|
||||
|
||||
func (p *rwFolder) performFinish(state *sharedPullerState) {
|
||||
var err error
|
||||
defer func() {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": state.file.Name,
|
||||
"error": err,
|
||||
"type": "file",
|
||||
"action": "update",
|
||||
})
|
||||
}()
|
||||
|
||||
func (p *rwFolder) performFinish(state *sharedPullerState) error {
|
||||
// Set the correct permission bits on the new file
|
||||
if !p.ignorePerms {
|
||||
err = os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777))
|
||||
if err != nil {
|
||||
l.Warnln("Puller: final:", err)
|
||||
return
|
||||
if !p.ignorePermissions(state.file) {
|
||||
if err := os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set the correct timestamp on the new file
|
||||
t := time.Unix(state.file.Modified, 0)
|
||||
err = os.Chtimes(state.tempName, t, t)
|
||||
if err != nil {
|
||||
if p.lenientMtimes {
|
||||
// We accept the failure with a warning here and allow the sync to
|
||||
// continue. We'll sync the new mtime back to the other devices later.
|
||||
// If they have the same problem & setting, we might never get in
|
||||
// sync.
|
||||
l.Infof("Puller (folder %q, file %q): final: %v (continuing anyway as requested)", p.folder, state.file.Name, err)
|
||||
} else {
|
||||
l.Warnln("Puller: final:", err)
|
||||
return
|
||||
if err := os.Chtimes(state.tempName, t, t); err != nil {
|
||||
// Try using virtual mtimes instead
|
||||
info, err := os.Stat(state.tempName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.virtualMtimeRepo.UpdateMtime(state.file.Name, info.ModTime(), t)
|
||||
}
|
||||
|
||||
var err error
|
||||
if p.inConflict(state.version, state.file.Version) {
|
||||
// The new file has been changed in conflict with the existing one. We
|
||||
// should file it away as a conflict instead of just removing or
|
||||
@@ -1106,8 +1141,7 @@ func (p *rwFolder) performFinish(state *sharedPullerState) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
l.Warnln("Puller: final:", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
// If the target path is a symlink or a directory, we cannot copy
|
||||
@@ -1117,18 +1151,15 @@ func (p *rwFolder) performFinish(state *sharedPullerState) {
|
||||
osutil.InWritableDir(osutil.Remove, state.realName)
|
||||
}
|
||||
// Replace the original content with the new one
|
||||
err = osutil.Rename(state.tempName, state.realName)
|
||||
if err != nil {
|
||||
l.Warnln("Puller: final:", err)
|
||||
return
|
||||
if err = osutil.Rename(state.tempName, state.realName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If it's a symlink, the target of the symlink is inside the file.
|
||||
if state.file.IsSymlink() {
|
||||
content, err := ioutil.ReadFile(state.realName)
|
||||
if err != nil {
|
||||
l.Warnln("Puller: final: reading symlink:", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the file, and replace it with a symlink.
|
||||
@@ -1137,13 +1168,13 @@ func (p *rwFolder) performFinish(state *sharedPullerState) {
|
||||
return symlinks.Create(path, string(content), state.file.Flags)
|
||||
}, state.realName)
|
||||
if err != nil {
|
||||
l.Warnln("Puller: final: creating symlink:", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Record the updated file in the index
|
||||
p.dbUpdates <- state.file
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *rwFolder) finisherRoutine(in <-chan *sharedPullerState) {
|
||||
@@ -1152,24 +1183,24 @@ func (p *rwFolder) finisherRoutine(in <-chan *sharedPullerState) {
|
||||
if debug {
|
||||
l.Debugln(p, "closing", state.file.Name)
|
||||
}
|
||||
if err != nil {
|
||||
l.Warnln("Puller: final:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
p.queue.Done(state.file.Name)
|
||||
if state.failed() == nil {
|
||||
p.performFinish(state)
|
||||
} else {
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": state.file.Name,
|
||||
"error": state.failed(),
|
||||
"type": "file",
|
||||
"action": "update",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
err = p.performFinish(state)
|
||||
}
|
||||
p.model.receivedFile(p.folder, state.file.Name)
|
||||
|
||||
if err != nil {
|
||||
l.Infoln("Puller: final:", err)
|
||||
}
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
"folder": p.folder,
|
||||
"item": state.file.Name,
|
||||
"error": events.Error(err),
|
||||
"type": "file",
|
||||
"action": "update",
|
||||
})
|
||||
|
||||
if p.progressEmitter != nil {
|
||||
p.progressEmitter.Deregister(state)
|
||||
}
|
||||
@@ -1215,12 +1246,14 @@ loop:
|
||||
|
||||
if len(batch) == maxBatchSize {
|
||||
p.model.updateLocals(p.folder, batch)
|
||||
p.model.receivedFile(p.folder, batch[len(batch)-1].Name)
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
case <-tick.C:
|
||||
if len(batch) > 0 {
|
||||
p.model.updateLocals(p.folder, batch)
|
||||
p.model.receivedFile(p.folder, batch[len(batch)-1].Name)
|
||||
batch = batch[:0]
|
||||
}
|
||||
}
|
||||
@@ -1228,6 +1261,7 @@ loop:
|
||||
|
||||
if len(batch) > 0 {
|
||||
p.model.updateLocals(p.folder, batch)
|
||||
p.model.receivedFile(p.folder, batch[len(batch)-1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ type sharedPullerState struct {
|
||||
copyOrigin int // Number of blocks copied from the original file
|
||||
copyNeeded int // Number of copy actions still pending
|
||||
pullNeeded int // Number of block pulls still pending
|
||||
closed bool // True if the file has been finalClosed.
|
||||
mut sync.Mutex // Protects the above
|
||||
}
|
||||
|
||||
@@ -115,7 +116,7 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fd, err := os.OpenFile(s.tempName, flags, 0644)
|
||||
fd, err := os.OpenFile(s.tempName, flags, 0666)
|
||||
if err != nil {
|
||||
s.failLocked("dst create", err)
|
||||
return nil, err
|
||||
@@ -218,16 +219,28 @@ func (s *sharedPullerState) finalClose() (bool, error) {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
|
||||
if s.pullNeeded+s.copyNeeded != 0 && s.err == nil {
|
||||
// Not done yet.
|
||||
if s.closed {
|
||||
// Already closed
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if fd := s.fd; fd != nil {
|
||||
s.fd = nil
|
||||
return true, fd.Close()
|
||||
if s.pullNeeded+s.copyNeeded != 0 && s.err == nil {
|
||||
// Not done yet, and not errored
|
||||
return false, nil
|
||||
}
|
||||
return false, nil
|
||||
|
||||
if s.fd != nil {
|
||||
if closeErr := s.fd.Close(); closeErr != nil && s.err == nil {
|
||||
// This is our error if we weren't errored before. Otherwise we
|
||||
// keep the earlier error.
|
||||
s.err = closeErr
|
||||
}
|
||||
s.fd = nil
|
||||
}
|
||||
|
||||
s.closed = true
|
||||
|
||||
return true, s.err
|
||||
}
|
||||
|
||||
// Returns the momentarily progress for the puller
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -18,7 +18,15 @@ type tempNamer struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
var defTempNamer = tempNamer{".syncthing"}
|
||||
var defTempNamer tempNamer
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
defTempNamer = tempNamer{"~syncthing~"}
|
||||
} else {
|
||||
defTempNamer = tempNamer{".syncthing."}
|
||||
}
|
||||
}
|
||||
|
||||
func (t tempNamer) IsTemporary(name string) bool {
|
||||
return strings.HasPrefix(filepath.Base(name), t.prefix)
|
||||
@@ -26,6 +34,12 @@ func (t tempNamer) IsTemporary(name string) bool {
|
||||
|
||||
func (t tempNamer) TempName(name string) string {
|
||||
tdir := filepath.Dir(name)
|
||||
tname := fmt.Sprintf("%s.%s", t.prefix, filepath.Base(name))
|
||||
tbase := filepath.Base(name)
|
||||
if len(tbase) > 240 {
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(name))
|
||||
tbase = fmt.Sprintf("%x", hash.Sum(nil))
|
||||
}
|
||||
tname := fmt.Sprintf("%s%s.tmp", t.prefix, tbase)
|
||||
return filepath.Join(tdir, tname)
|
||||
}
|
||||
|
||||
26
internal/model/tempname_test.go
Normal file
26
internal/model/tempname_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLongTempFilename(t *testing.T) {
|
||||
filename := ""
|
||||
for i := 0; i < 300; i++ {
|
||||
filename += "l"
|
||||
}
|
||||
tFile := defTempNamer.TempName(filename)
|
||||
if len(tFile) < 10 || len(tFile) > 200 {
|
||||
t.Fatal("Invalid long filename")
|
||||
}
|
||||
if !strings.HasSuffix(defTempNamer.TempName("short"), "short.tmp") {
|
||||
t.Fatal("Invalid short filename", defTempNamer.TempName("short"))
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build windows
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type tempNamer struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
var defTempNamer = tempNamer{"~syncthing~"}
|
||||
|
||||
func (t tempNamer) IsTemporary(name string) bool {
|
||||
return strings.HasPrefix(filepath.Base(name), t.prefix)
|
||||
}
|
||||
|
||||
func (t tempNamer) TempName(name string) string {
|
||||
tdir := filepath.Dir(name)
|
||||
tname := fmt.Sprintf("%s.%s.tmp", t.prefix, filepath.Base(name))
|
||||
return filepath.Join(tdir, tname)
|
||||
}
|
||||
17
internal/osutil/mkdirall.go
Normal file
17
internal/osutil/mkdirall.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
65
internal/osutil/mkdirall_windows.go
Normal file
65
internal/osutil/mkdirall_windows.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Modified by Zillode to fix https://github.com/syncthing/syncthing/issues/1822
|
||||
// Sync with https://github.com/golang/go/blob/master/src/os/path.go
|
||||
// See https://github.com/golang/go/issues/10900
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// MkdirAll creates a directory named path, along with any necessary parents,
|
||||
// and returns nil, or else returns an error.
|
||||
// The permission bits perm are used for all directories that MkdirAll creates.
|
||||
// If path is already a directory, MkdirAll does nothing and returns nil.
|
||||
func MkdirAll(path string, perm os.FileMode) error {
|
||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||
dir, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{"mkdir", path, syscall.ENOTDIR}
|
||||
}
|
||||
|
||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||
i := len(path)
|
||||
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
|
||||
i--
|
||||
}
|
||||
|
||||
j := i
|
||||
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
|
||||
j--
|
||||
}
|
||||
|
||||
if j > 1 {
|
||||
// Create parent
|
||||
parent := path[0 : j-1]
|
||||
if parent != filepath.VolumeName(parent) {
|
||||
err = MkdirAll(parent, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent now exists; invoke Mkdir and use its result.
|
||||
err = os.Mkdir(path, perm)
|
||||
if err != nil {
|
||||
// Handle arguments like "foo/." by
|
||||
// double-checking that directory doesn't exist.
|
||||
dir, err1 := os.Lstat(path)
|
||||
if err1 == nil && dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -144,7 +144,8 @@ func TestInWritableDirWindowsRename(t *testing.T) {
|
||||
for _, path := range []string{"testdata/windows/ro/readonly", "testdata/windows/ro", "testdata/windows"} {
|
||||
err := os.Rename(path, path+"new")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error %s", path)
|
||||
t.Skipf("seem like this test doesn't work here")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syncthing/syncthing/internal/ignore"
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"github.com/syncthing/syncthing/internal/symlinks"
|
||||
@@ -52,6 +53,8 @@ type Walker struct {
|
||||
TempLifetime time.Duration
|
||||
// If CurrentFiler is not nil, it is queried for the current file before rescanning.
|
||||
CurrentFiler CurrentFiler
|
||||
// If MtimeRepo is not nil, it is used to provide mtimes on systems that don't support setting arbirtary mtimes.
|
||||
MtimeRepo *db.VirtualMtimeRepo
|
||||
// If IgnorePerms is true, changes to permission bits will not be
|
||||
// detected. Scanned files will get zero permission bits and the
|
||||
// NoPermissionBits flag set.
|
||||
@@ -138,15 +141,20 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
return nil
|
||||
}
|
||||
|
||||
mtime := info.ModTime()
|
||||
if w.MtimeRepo != nil {
|
||||
mtime = w.MtimeRepo.GetMtime(rn, mtime)
|
||||
}
|
||||
|
||||
if w.TempNamer != nil && w.TempNamer.IsTemporary(rn) {
|
||||
// A temporary file
|
||||
if debug {
|
||||
l.Debugln("temporary:", rn)
|
||||
}
|
||||
if info.Mode().IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
|
||||
if info.Mode().IsRegular() && mtime.Add(w.TempLifetime).Before(now) {
|
||||
os.Remove(p)
|
||||
if debug {
|
||||
l.Debugln("removing temporary:", rn, info.ModTime())
|
||||
l.Debugln("removing temporary:", rn, mtime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -298,7 +306,7 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
Name: rn,
|
||||
Version: cf.Version.Update(w.ShortID),
|
||||
Flags: flags,
|
||||
Modified: info.ModTime().Unix(),
|
||||
Modified: mtime.Unix(),
|
||||
}
|
||||
if debug {
|
||||
l.Debugln("dir:", p, f)
|
||||
@@ -325,13 +333,13 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
// - has the same size as previously
|
||||
cf, ok = w.CurrentFiler.CurrentFile(rn)
|
||||
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Flags, curMode)
|
||||
if ok && permUnchanged && !cf.IsDeleted() && cf.Modified == info.ModTime().Unix() && !cf.IsDirectory() &&
|
||||
if ok && permUnchanged && !cf.IsDeleted() && cf.Modified == mtime.Unix() && !cf.IsDirectory() &&
|
||||
!cf.IsSymlink() && !cf.IsInvalid() && cf.Size() == info.Size() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&os.ModePerm)
|
||||
l.Debugln("rescan:", cf, mtime.Unix(), info.Mode()&os.ModePerm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +352,7 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
Name: rn,
|
||||
Version: cf.Version.Update(w.ShortID),
|
||||
Flags: flags,
|
||||
Modified: info.ModTime().Unix(),
|
||||
Modified: mtime.Unix(),
|
||||
}
|
||||
if debug {
|
||||
l.Debugln("to hash:", p, f)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/ignore"
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
@@ -218,7 +219,7 @@ func TestNormalization(t *testing.T) {
|
||||
|
||||
for _, s1 := range tests {
|
||||
// Create a directory for each of the interesting strings above
|
||||
if err := os.MkdirAll(filepath.Join("testdata/normalization", s1), 0755); err != nil {
|
||||
if err := osutil.MkdirAll(filepath.Join("testdata/normalization", s1), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -102,37 +103,11 @@ func Discover(timeout time.Duration) []IGD {
|
||||
|
||||
resultChan := make(chan IGD)
|
||||
|
||||
// Aggregator
|
||||
go func() {
|
||||
next:
|
||||
for result := range resultChan {
|
||||
for _, existingResult := range results {
|
||||
if existingResult.uuid == result.uuid {
|
||||
if debug {
|
||||
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
goto next
|
||||
}
|
||||
}
|
||||
results = append(results, result)
|
||||
if debug {
|
||||
l.Debugf("UPnP discovery result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg := sync.NewWaitGroup()
|
||||
|
||||
for _, intf := range interfaces {
|
||||
if intf.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
if intf.Flags&net.FlagMulticast == 0 {
|
||||
// Interface flags seem to always be 0 on Windows
|
||||
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -145,8 +120,33 @@ func Discover(timeout time.Duration) []IGD {
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
}()
|
||||
|
||||
nextResult:
|
||||
for result := range resultChan {
|
||||
for _, existingResult := range results {
|
||||
if existingResult.uuid == result.uuid {
|
||||
if debug {
|
||||
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
continue nextResult
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
if debug {
|
||||
l.Debugf("UPnP discovery result %s with services:", result.uuid)
|
||||
for _, svc := range result.services {
|
||||
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -472,7 +472,7 @@ func soapRequest(url, service, function, message string) ([]byte, error) {
|
||||
|
||||
resp, _ = ioutil.ReadAll(r.Body)
|
||||
if debug {
|
||||
l.Debugln("SOAP Response:\n\n" + string(resp) + "\n")
|
||||
l.Debugf("SOAP Response: %v\n\n%v\n\n", r.StatusCode, string(resp))
|
||||
}
|
||||
|
||||
r.Body.Close()
|
||||
@@ -528,6 +528,11 @@ type getExternalIPAddressResponse struct {
|
||||
NewExternalIPAddress string `xml:"NewExternalIPAddress"`
|
||||
}
|
||||
|
||||
type soapErrorResponse struct {
|
||||
ErrorCode int `xml:"Body>Fault>detail>UPnPError>errorCode"`
|
||||
ErrorDescription string `xml:"Body>Fault>detail>UPnPError>errorDescription"`
|
||||
}
|
||||
|
||||
// AddPortMapping adds a port mapping to the specified IGD service.
|
||||
func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
|
||||
tpl := `<u:AddPortMapping xmlns:u="%s">
|
||||
@@ -542,12 +547,20 @@ func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, ex
|
||||
</u:AddPortMapping>`
|
||||
body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol, internalPort, localIPAddress, description, timeout)
|
||||
|
||||
_, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
|
||||
if err != nil {
|
||||
return err
|
||||
response, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
|
||||
if err != nil && timeout > 0 {
|
||||
// Try to repair error code 725 - OnlyPermanentLeasesSupported
|
||||
envelope := &soapErrorResponse{}
|
||||
err = xml.Unmarshal(response, envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if envelope.ErrorCode == 725 {
|
||||
return s.AddPortMapping(localIPAddress, protocol, externalPort, internalPort, description, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePortMapping deletes a port mapping from the specified IGD service.
|
||||
|
||||
@@ -33,6 +33,33 @@ func TestExternalIPParsing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoapFaultParsing(t *testing.T) {
|
||||
soapResponse :=
|
||||
[]byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<s:Fault>
|
||||
<faultcode>s:Client</faultcode>
|
||||
<faultstring>UPnPError</faultstring>
|
||||
<detail>
|
||||
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
|
||||
<errorCode>725</errorCode>
|
||||
<errorDescription>OnlyPermanentLeasesSupported</errorDescription></UPnPError>
|
||||
</detail>
|
||||
</s:Fault>
|
||||
</s:Body>
|
||||
</s:Envelope>`)
|
||||
|
||||
envelope := &soapErrorResponse{}
|
||||
err := xml.Unmarshal(soapResponse, envelope)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if envelope.ErrorCode != 725 {
|
||||
t.Error("Parse of SOAP request failed.", envelope)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlURLParsing(t *testing.T) {
|
||||
rootURL := "http://192.168.243.1:80/igd.xml"
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ func (v Simple) Archive(filePath string) error {
|
||||
if debug {
|
||||
l.Debugln("creating versions dir", versionsDir)
|
||||
}
|
||||
os.MkdirAll(versionsDir, 0755)
|
||||
osutil.MkdirAll(versionsDir, 0755)
|
||||
osutil.HideFile(versionsDir)
|
||||
} else {
|
||||
return err
|
||||
@@ -79,7 +79,7 @@ func (v Simple) Archive(filePath string) error {
|
||||
}
|
||||
|
||||
dir := filepath.Join(versionsDir, inFolderPath)
|
||||
err = os.MkdirAll(dir, 0755)
|
||||
err = osutil.MkdirAll(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ func (v Staggered) Archive(filePath string) error {
|
||||
if debug {
|
||||
l.Debugln("creating versions dir", v.versionsPath)
|
||||
}
|
||||
os.MkdirAll(v.versionsPath, 0755)
|
||||
osutil.MkdirAll(v.versionsPath, 0755)
|
||||
osutil.HideFile(v.versionsPath)
|
||||
} else {
|
||||
return err
|
||||
@@ -275,7 +275,7 @@ func (v Staggered) Archive(filePath string) error {
|
||||
}
|
||||
|
||||
dir := filepath.Join(v.versionsPath, inFolderPath)
|
||||
err = os.MkdirAll(dir, 0755)
|
||||
err = osutil.MkdirAll(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
3
man/README.md
Normal file
3
man/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
These manual pages are generates on http://docs.syncthing.net/, based on
|
||||
the https://github.com/syncthing/docs repo. Do not edit them in this
|
||||
repo.
|
||||
8
man/refresh.sh
Executable file
8
man/refresh.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
base=http://docs.syncthing.net/man/
|
||||
pages=(syncthing-config.5 syncthing-device-ids.7 syncthing-event-api.7 syncthing-faq.7 syncthing-networking.7 syncthing-rest-api.7 syncthing-security.7 syncthing-stignore.5 syncthing-versioning.7 syncthing.1)
|
||||
|
||||
for page in "${pages[@]}" ; do
|
||||
curl -sLO "$base$page"
|
||||
done
|
||||
270
man/syncthing-config.5
Normal file
270
man/syncthing-config.5
Normal file
@@ -0,0 +1,270 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-CONFIG" "5" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-config \- Syncthing Configuration
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.sp
|
||||
\fBWARNING:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
This page may be outdated and requires review.
|
||||
Attributes have been added that are not documented.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH SYNOPSIS
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$HOME/.config/syncthing/config.xml
|
||||
$HOME/Library/Application Support/Syncthing
|
||||
%AppData%/Syncthing
|
||||
%localappdata%/Syncthing
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
Syncthing uses a single directory to store configuration, crypto keys
|
||||
and index caches. The location defaults to \fB$HOME/.config/syncthing\fP
|
||||
(Unix\-like), \fB$HOME/Library/Application Support/Syncthing\fP (Mac),
|
||||
\fB%AppData%/Syncthing\fP (Windows XP) or \fB%localappdata%/Syncthing\fP
|
||||
(Windows 7/8). It can be changed at runtime using the \fB\-home\fP flag. In this
|
||||
directory the following files are located:
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B cert.pem
|
||||
The device\(aqs RSA public key, named "cert" for legacy reasons.
|
||||
.TP
|
||||
.B key.pem
|
||||
The device\(aqs RSA private key. This needs to be protected.
|
||||
.TP
|
||||
.B config.xml
|
||||
The configuration file, in XML format.
|
||||
.TP
|
||||
.B https\-cert.pem
|
||||
The certificate for HTTPS GUI connections.
|
||||
.TP
|
||||
.B https\-key.pem
|
||||
The key for HTTPS GUI connections.
|
||||
.TP
|
||||
.B index/
|
||||
A directory holding the database with metadata and hashes of the files
|
||||
currently on disk and available from peers.
|
||||
.TP
|
||||
.B csrftokens.txt
|
||||
A list of recently issued CSRF tokens (for protection against browser cross
|
||||
site request forgery).
|
||||
.UNINDENT
|
||||
.SH CONFIG FILE FORMAT
|
||||
.sp
|
||||
The following is shows the default configuration file:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
<configuration version="2">
|
||||
<folder id="default" directory="/Users/jb/Sync" ro="false" ignorePerms="false">
|
||||
<device id="GXN5ECCWTA2B7EB5FXYL5OWGOADX5EF5VNJAQSIBAY6XHJ24BNOA"></device>
|
||||
</folder>
|
||||
<device id="GXN5ECCWTA2B7EB5FXYL5OWGOADX5EF5VNJAQSIBAY6XHJ24BNOA" name="jborg\-mbp">
|
||||
<address>dynamic</address>
|
||||
</device>
|
||||
<gui enabled="true" tls="true">
|
||||
<address>127.0.0.1:54096</address>
|
||||
<user>jb</user>
|
||||
<password>$2a$10$EKaTIcpz2...</password>
|
||||
<apikey>O80CDOJ9LVUVCMHFK2OJDO4T882735</apikey>
|
||||
</gui>
|
||||
<options>
|
||||
<listenAddress>:54097</listenAddress>
|
||||
<globalAnnounceServer>announce.syncthing.net:22025</globalAnnounceServer>
|
||||
<globalAnnounceEnabled>true</globalAnnounceEnabled>
|
||||
<localAnnounceEnabled>true</localAnnounceEnabled>
|
||||
<parallelRequests>16</parallelRequests>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<rescanIntervalS>60</rescanIntervalS>
|
||||
<reconnectionIntervalS>60</reconnectionIntervalS>
|
||||
<maxChangeKbps>10000</maxChangeKbps>
|
||||
<startBrowser>true</startBrowser>
|
||||
<upnpEnabled>true</upnpEnabled>
|
||||
<urAccepted>0</urAccepted>
|
||||
</options>
|
||||
</configuration>
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS configuration
|
||||
.sp
|
||||
This is the root element.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B version
|
||||
The config version. The current version is \fB2\fP\&.
|
||||
.UNINDENT
|
||||
.SS folder
|
||||
.sp
|
||||
One or more \fBfolder\fP elements must be present in the file. Each
|
||||
element describes one folder.
|
||||
.sp
|
||||
Within the \fBfolder\fP element one or more \fBdevice\fP element should be
|
||||
present. These must contain the \fBid\fP attribute and nothing else.
|
||||
Mentioned devices are those that will be sharing the folder in question.
|
||||
Each mentioned device must have a separate \fBdevice\fP element later in
|
||||
the file. It is customary that the local device ID is included in all
|
||||
repositories. Syncthing will currently add this automatically if it is
|
||||
not present in the configuration file.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B id
|
||||
The folder ID, must be unique. (mandatory)
|
||||
.TP
|
||||
.B directory
|
||||
The directory where the folder is stored on this
|
||||
device; not sent to other devices. (mandatory)
|
||||
.TP
|
||||
.B ro
|
||||
True if the folder is read only (will not be modified by Syncthing) on this
|
||||
device. (optional, defaults to \fBfalse\fP)
|
||||
.TP
|
||||
.B ignorePerms
|
||||
True if the folder should \fI\%ignore permissions\fP <\fBhttp://forum.syncthing.net/t/263\fP>\&.
|
||||
.UNINDENT
|
||||
.SS device
|
||||
.sp
|
||||
One or more \fBdevice\fP elements must be present in the file. Each
|
||||
element describes a device participating in the cluster. It is customary
|
||||
to include a \fBdevice\fP element for the local device; Syncthing will
|
||||
currently add one if it is not present.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B id
|
||||
The device ID. This must be written in canonical form, that is without any
|
||||
spaces or dashes. (mandatory)
|
||||
.TP
|
||||
.B name
|
||||
A friendly name for the device. (optional)
|
||||
.TP
|
||||
.B address
|
||||
The address section is only valid inside of \fBdevice\fP elements. It contains
|
||||
a single address, on one of the following forms:
|
||||
.INDENT 7.0
|
||||
.IP \(bu 2
|
||||
IPv4 addresses, IPv6 addresses within brackets, or DNS names, all
|
||||
optionally followed by a port number.
|
||||
.IP \(bu 2
|
||||
\fBdynamic\fP: The address will be resolved using discovery.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS gui
|
||||
.sp
|
||||
There must be \fIexactly one\fP \fBgui\fP element.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B enabled
|
||||
\fBtrue\fP/\fBfalse\fP
|
||||
.TP
|
||||
.B tls
|
||||
\fBtrue\fP/\fBfalse\fP: If true then the GUI will use HTTPS.
|
||||
.TP
|
||||
.B address
|
||||
One or more address elements must be present, containing an \fBip:port\fP
|
||||
listen address.
|
||||
.TP
|
||||
.B username
|
||||
Set to require authentication.
|
||||
.TP
|
||||
.B password
|
||||
Contains the bcrypt hash of the real password.
|
||||
.TP
|
||||
.B apikey
|
||||
If set, this is the API key that enables usage of the REST interface.
|
||||
.UNINDENT
|
||||
.sp
|
||||
Additionally, there must be \fIexactly one\fP \fBoptions\fP element. It contains the
|
||||
following configuration settings as children:
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B listenAddress
|
||||
\fBhost:port\fP or \fB:port\fP string denoting an address to listen for BEP
|
||||
connections. More than one \fBlistenAddress\fP may be given.
|
||||
(default: \fB0.0.0.0:22000\fP)
|
||||
.TP
|
||||
.B globalAnnounceServer
|
||||
\fBhost:port\fP string denoting where a global announce server may be
|
||||
reached. (default: \fBannounce.syncthing.net:22025\fP)
|
||||
.TP
|
||||
.B globalAnnounceEnabled
|
||||
\fBtrue\fP/\fBfalse\fP (default: \fBtrue\fP)
|
||||
.TP
|
||||
.B localAnnounceEnabled
|
||||
\fBtrue\fP/\fBfalse\fP (default: \fBtrue\fP)
|
||||
.TP
|
||||
.B parallelRequests
|
||||
The maximum number of outstanding block requests to have against any given
|
||||
peer. (default: \fB16\fP)
|
||||
.TP
|
||||
.B maxSendKbps
|
||||
Rate limit
|
||||
.TP
|
||||
.B rescanIntervalS
|
||||
The number of seconds to wait between each scan for modification of the
|
||||
local repositories. A value of \fB0\fP disables the scanner. (default: \fB60\fP)
|
||||
.TP
|
||||
.B reconnectionIntervalS
|
||||
The number of seconds to wait between each attempt to connect to currently
|
||||
unconnected devices. (default: \fB60\fP)
|
||||
.TP
|
||||
.B maxChangeKbps
|
||||
The maximum rate of change allowed for a single file. When this rate is
|
||||
exceeded, further changes to the file are not announced, until the rate is
|
||||
reduced below the limit. (default: \fB10000\fP)
|
||||
.TP
|
||||
.B startBrowser
|
||||
\fBtrue\fP/\fBfalse\fP (default: \fBtrue\fP)
|
||||
.TP
|
||||
.B upnpEnabled
|
||||
\fBtrue\fP/\fBfalse\fP (default: \fBtrue\fP)
|
||||
.TP
|
||||
.B urAccepted
|
||||
Whether the user as accepted to submit anonymous usage data. The default,
|
||||
\fB0\fP, mean the user has not made a choice, and Syncthing will ask at some
|
||||
point in the future. \fB\-1\fP means no, \fB1\fP means yes.
|
||||
.UNINDENT
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
290
man/syncthing-device-ids.7
Normal file
290
man/syncthing-device-ids.7
Normal file
@@ -0,0 +1,290 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-device-ids \- Understanding Device IDs
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
Every device is identified by a device ID. The device ID is used for address
|
||||
resolution, authentication and authorization. The term "device ID" could
|
||||
interchangably have been "key ID" since the device ID is a direct properties of
|
||||
the public key in use.
|
||||
.SH KEYS
|
||||
.sp
|
||||
To understand device IDs we need to look at the underlying mechanisms. At first
|
||||
startup, Syncthing will create an public/private key pair.
|
||||
.sp
|
||||
Currently this is a 3072 bit RSA key. The keys are saved in the form of the
|
||||
private key (\fBkey.pem\fP) and a self signed certificate (\fBcert.pem\fP). The self
|
||||
signing part doesn\(aqt actually add any security or functionality as far as
|
||||
Syncthing is concerned but it enables the use of the keys in a standard TLS
|
||||
exchange.
|
||||
.sp
|
||||
The typical certificate will look something like this, inspected with
|
||||
\fBopenssl x509\fP:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 0 (0x0)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: CN=syncthing
|
||||
Validity
|
||||
Not Before: Mar 30 21:10:52 2014 GMT
|
||||
Not After : Dec 31 23:59:59 2049 GMT
|
||||
Subject: CN=syncthing
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
RSA Public Key: (3072 bit)
|
||||
Modulus (3072 bit):
|
||||
00:da:83:8a:c0:95:af:0a:42:af:43:74:65:29:f2:
|
||||
30:e3:b9:12:d2:6b:70:93:da:0b:7b:8a:1e:e5:79:
|
||||
...
|
||||
99:09:4c:a9:7b:ba:4a:6a:8b:3b:e6:e7:c7:2c:00:
|
||||
90:aa:bc:ad:94:e7:80:95:d2:1b
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Server Authentication, TLS Web Client Authentication
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:FALSE
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
68:72:43:8b:83:61:09:68:f0:ef:f0:43:b7:30:a6:73:1e:a8:
|
||||
d9:24:6c:2d:b4:bc:c9:e8:3e:0b:1e:3c:cc:7a:b2:c8:f1:1d:
|
||||
...
|
||||
88:7e:e2:61:aa:4c:02:e3:64:b0:da:70:3a:cd:1c:3d:86:db:
|
||||
df:54:b9:4e:be:1b
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
We can see here that the certificate is little more than a container for the
|
||||
public key; the serial number is zero and the Issuer and Subject are both
|
||||
"syncthing" where a qualified name might otherwise be expected.
|
||||
.sp
|
||||
An advanced user could replace the \fBkey.pem\fP and \fBcert.pem\fP files with a
|
||||
keypair generated directly by the \fBopenssl\fP utility or other mechanism.
|
||||
.SH DEVICE IDS
|
||||
.sp
|
||||
To form a device ID the SHA\-256 hash of the certificate data in DER form is
|
||||
calculated. This means the hash covers all information under the
|
||||
\fBCertificate:\fP section above.
|
||||
.sp
|
||||
The hashing results in a 256 bit hash, which we encode using base32. Base32
|
||||
encodes five bits per character, so we need 256 / 5 = 51.2 characters to encode
|
||||
the device ID. This becomes 52 characters in practice, but 52 characters of
|
||||
base32 would decode to 260 bits which is not an whole number of bytes. The
|
||||
base32 encoding adds padding to 280 bits (the next multiple of both 5 and 8
|
||||
bits) so the resulting ID looks something like:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
MFZWI3DBONSGYYLTMRWGC43ENRQXGZDMMFZWI3DBONSGYYLTMRWA====
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
The padding (\fB====\fP) is stripped away, the device ID split in four
|
||||
groups, and \fI\%check
|
||||
digits\fP <\fBhttps://forum.syncthing.net/t/v0-9-0-new-device-id-format/478\fP>
|
||||
are added for each group. For presentation purposes the device ID is
|
||||
grouped with dashes, resulting in the final value:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
MFZWI3D\-BONSGYC\-YLTMRWG\-C43ENR5 \-QXGZDMM\-FZWI3DP\-BONSGYY\-LTMRWAD
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS Connection Establishment
|
||||
.sp
|
||||
So now we know what device IDs are, here\(aqs how they are used in Syncthing. When
|
||||
you add a device ID to the syncthing configuration, Syncthing will attempt to
|
||||
connect to that device. The first thing we need to do is figure out the IP and
|
||||
port to connect to. There\(aqs three possibilities here;
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
The IP and port can be set statically in the configuration. The IP
|
||||
can equally well be a hostname, so if you have a static IP or a
|
||||
dynamic DNS setup this might be a good option.
|
||||
.IP \(bu 2
|
||||
Using local discovery, if enabled. Every Syncthing instance on a LAN
|
||||
periodically broadcasts information about itself (device ID, address,
|
||||
port number). If we\(aqve seen one of these broadcasts for a given
|
||||
device ID that\(aqs where we try to connect.
|
||||
.IP \(bu 2
|
||||
Using global discovery, if enabled. Every Syncthing instance
|
||||
announces itself to the global discovery service (device ID and
|
||||
external port number \- the internal address is not announced to the
|
||||
global server). If we don\(aqt have a static address and haven\(aqt seen
|
||||
any local announcements the global discovery server will be queried
|
||||
for an address.
|
||||
.UNINDENT
|
||||
.sp
|
||||
Once we have and address and port a TCP connection is established and a TLS
|
||||
handshake performed. As part of the handshake both devices present their
|
||||
certificates. Once the handshake has completed and the peer certificate is
|
||||
known, the following steps are performed.
|
||||
.INDENT 0.0
|
||||
.IP 1. 3
|
||||
Calculate the remote device ID by using the process above on the
|
||||
received certificate.
|
||||
.IP 2. 3
|
||||
Weed out a few possible misconfigurations \- i.e. if the device ID is
|
||||
that of the local device or of a device we already have an active
|
||||
connection to. Drop the connection in these cases.
|
||||
.IP 3. 3
|
||||
Verify the remote device ID against the configuration. If it is not a
|
||||
device ID we are expecting to talk to, drop the connection.
|
||||
.IP 4. 3
|
||||
Verify the certificate \fBCommonName\fP against the configuration. By
|
||||
default, we expect it to be \fBsyncthing\fP, but when using custom
|
||||
certificates this can be changed.
|
||||
.IP 5. 3
|
||||
If everything checks out so far, accept the connection.
|
||||
.UNINDENT
|
||||
.SH AN ASIDE ABOUT COLLISIONS
|
||||
.sp
|
||||
The SHA\-256 hash is cryptographically collision resistant. This means
|
||||
that there is no way that we know of to create two different messages
|
||||
with the same hash.
|
||||
.sp
|
||||
You can argue that of course there are collisions \- there\(aqs an infinite
|
||||
amount of inputs and a finite amount of outputs, so per definition there
|
||||
are infinitely many messages that result in the same hash.
|
||||
.sp
|
||||
I\(aqm going to quote \fI\%stack
|
||||
overflow\fP <\fBhttp://stackoverflow.com/questions/4014090/is-it-safe-to-ignore-the-possibility-of-sha-collisions-in-practice\fP>
|
||||
here:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
The usual answer goes thus: what is the probability that a rogue
|
||||
asteroid crashes on Earth within the next second, obliterating
|
||||
civilization\-as\-we\- know\-it, and killing off a few billion people ?
|
||||
It can be argued that any unlucky event with a probability lower
|
||||
than that is not actually very important.
|
||||
.sp
|
||||
If we have a "perfect" hash function with output size n, and we have
|
||||
p messages to hash (individual message length is not important),
|
||||
then probability of collision is about p2/2n+1 (this is an
|
||||
approximation which is valid for "small" p, i.e. substantially
|
||||
smaller than 2n/2). For instance, with SHA\-256 (n=256) and one
|
||||
billion messages (p=10^9) then the probability is about 4.3*10^\-60.
|
||||
.sp
|
||||
A mass\-murderer space rock happens about once every 30 million years
|
||||
on average. This leads to a probability of such an event occurring
|
||||
in the next second to about 10^\-15. That\(aqs 45 orders of magnitude
|
||||
more probable than the SHA\-256 collision. Briefly stated, if you
|
||||
find SHA\-256 collisions scary then your priorities are wrong.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
It\(aqs also worth noting that the property of SHA\-256 that we are using is not
|
||||
simply collision resistance but resistance to a preimage attack. I.e. even if
|
||||
you can find two messages that result in a hash collision that doesn\(aqt help you
|
||||
attack Syncthing (or TLS in general). You need to create a message that hashes
|
||||
to exactly the hash that my certificate already has or you won\(aqt get in.
|
||||
.sp
|
||||
Note also that it\(aqs not good enough to find a random blob of bits that happen to
|
||||
have the same hash as my certificate. You need to create a valid DER\- encoded,
|
||||
signed certificate that has the same hash as mine. The difficulty of this is
|
||||
staggeringly far beyond the already staggering difficulty of finding a SHA\-256
|
||||
collision.
|
||||
.SH PROBLEMS AND VULNERABILITIES
|
||||
.sp
|
||||
As far as I know, these are the issues or potential issues with the
|
||||
above mechanism.
|
||||
.SS Discovery Spoofing
|
||||
.sp
|
||||
Currently, neither the local nor global discovery mechanism is protected
|
||||
by crypto. This means that any device can in theory announce itself for
|
||||
any device ID and potentially receive connections for that device.
|
||||
.sp
|
||||
This could be a denial of service attack (we can\(aqt find the real device
|
||||
for a given device ID, so can\(aqt connect to it and sync). It could also
|
||||
be an intelligence gathering attack; if I spoof a given ID, I can see
|
||||
which devices try to connect to it.
|
||||
.sp
|
||||
It could be mitigated in several ways;
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
Announcements could be signed by the device private key. This
|
||||
requires already having the public key to verify.
|
||||
.IP \(bu 2
|
||||
Announcements to the global announce server could be done using TLS,
|
||||
so the server calculates the device ID based on the certificate
|
||||
instead of trusting to the device to tell the truth.
|
||||
.IP \(bu 2
|
||||
The user could statically configure IP or hostname for the devices.
|
||||
.IP \(bu 2
|
||||
The user could run a trusted global server.
|
||||
.UNINDENT
|
||||
.sp
|
||||
It\(aqs something we might want to look at at some point, but not a huge
|
||||
problem as I see it.
|
||||
.SS Long Device IDs are Painful
|
||||
.sp
|
||||
It\(aqs a mouthful to read over the phone, annoying to type into an SMS or even
|
||||
into a computer. And it needs to be done twice, once for each side.
|
||||
.sp
|
||||
This isn\(aqt a vulnerability as such, but a user experience problem. There are
|
||||
various possible solutions:
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
Use shorter device IDs with verification based on the full ID ("You
|
||||
entered MFZWI3; I found and connected to a device with the ID
|
||||
MFZWI3\-DBONSG\-YYLTMR\-WGC43E\-NRQXGZ\-DMMFZW\-I3DBON\-SGYYLT\-MRWA, please
|
||||
confirm that this is correct.").
|
||||
.IP \(bu 2
|
||||
Use shorter device IDs with an out of band authentication, a la
|
||||
Bluetooth pairing. You enter a one time PIN into Syncthing and give
|
||||
that PIN plus a short device ID to another user. On initial connect,
|
||||
both sides verify that the other knows the correct PIN before
|
||||
accepting the connection.
|
||||
.UNINDENT
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
610
man/syncthing-event-api.7
Normal file
610
man/syncthing-event-api.7
Normal file
@@ -0,0 +1,610 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-EVENT-API" "7" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-event-api \- Event API
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
Syncthing provides a simple long polling interface for exposing events from the
|
||||
core utility towards a GUI.
|
||||
.sp
|
||||
To receive events, perform a HTTP GET of \fB/rest/events?since=<lastSeenID>\fP,
|
||||
where \fB<lastSeenID>\fP is the ID of the last event you\(aqve already seen or zero.
|
||||
Syncthing returns a JSON encoded array of event objects, starting at the event
|
||||
just after the one with the last seen ID. There is a limit to the number of
|
||||
events buffered, so if the rate of events is high or the time between polling
|
||||
calls is long some events might be missed. This can be detected by noting a
|
||||
discontinuity in the event IDs.
|
||||
.sp
|
||||
If no new events are produced since \fB<lastSeenID>\fP, the HTTP call blocks and
|
||||
waits for new events to happen before returning, or if no new events are
|
||||
produced within 60 seconds, times out.
|
||||
.sp
|
||||
To receive only a limited number of events, add the \fBlimit=n\fP parameter with a
|
||||
suitable value for \fBn\fP and only the \fIlast\fP \fBn\fP events will be returned. This
|
||||
can be used to catch up with the latest event ID after a disconnection for
|
||||
example: \fB/rest/events?since=0&limit=1\fP\&.
|
||||
.SH EVENT STRUCTURE
|
||||
.sp
|
||||
Each event is represented by an object similar to the following:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 2,
|
||||
"type": "DeviceConnected",
|
||||
"time": "2014\-07\-13T21:04:33.687836696+02:00",
|
||||
"data": {
|
||||
"addr": "172.16.32.25:22000",
|
||||
"id": "NFGKEKE\-7Z6RTH7\-I3PRZXS\-DEJF3UJ\-FRWJBFO\-VBBTDND\-4SGNGVZ\-QUQHJAG"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
The top level keys \fBid\fP, \fBtime\fP, \fBtype\fP and \fBdata\fP are always present,
|
||||
though \fBdata\fP may be \fBnull\fP\&.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B id
|
||||
A monotonically increasing integer. The first event generated has id \fB1\fP,
|
||||
the next has id \fB2\fP etc.
|
||||
.TP
|
||||
.B time
|
||||
The time the event was generated.
|
||||
.TP
|
||||
.B type
|
||||
Indicates the type of (i.e. reason for) the event and is one of the event
|
||||
types below.
|
||||
.TP
|
||||
.B data
|
||||
An object containing optional extra information; the exact structure is
|
||||
determined by the event type.
|
||||
.UNINDENT
|
||||
.SH EVENTS
|
||||
.SS ConfigSaved
|
||||
.sp
|
||||
Emitted after the config has been saved by the user or by Syncthing
|
||||
itself.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 50,
|
||||
"type": "ConfigSaved",
|
||||
"time": "2014\-12\-13T00:09:13.5166486Z",
|
||||
"data":{
|
||||
"Version": 7,
|
||||
"Options": { ... },
|
||||
"GUI": { ... },
|
||||
"Devices": [ ... ],
|
||||
"Folders": [ ... ]
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS DeviceConnected
|
||||
.sp
|
||||
Generated each time a connection to a device has been established.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 2,
|
||||
"type": "DeviceConnected",
|
||||
"time": "2014\-07\-13T21:04:33.687836696+02:00",
|
||||
"data": {
|
||||
"addr": "172.16.32.25:22000",
|
||||
"id": "NFGKEKE\-7Z6RTH7\-I3PRZXS\-DEJF3UJ\-FRWJBFO\-VBBTDND\-4SGNGVZ\-QUQHJAG"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS DeviceDisconnected
|
||||
.sp
|
||||
Generated each time a connection to a device has been terminated.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 48,
|
||||
"type": "DeviceDisconnected",
|
||||
"time": "2014\-07\-13T21:18:52.859929215+02:00",
|
||||
"data": {
|
||||
"error": "unexpected EOF",
|
||||
"id": "NFGKEKE\-7Z6RTH7\-I3PRZXS\-DEJF3UJ\-FRWJBFO\-VBBTDND\-4SGNGVZ\-QUQHJAG"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
The error key contains the cause for disconnection, which might not
|
||||
necessarily be an error as such. Specifically, "EOF" and "unexpected
|
||||
EOF" both signify TCP connection termination, either due to the other
|
||||
device restarting or going offline or due to a network change.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS DeviceDiscovered
|
||||
.sp
|
||||
Emitted when a new device is discovered using local discovery.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 13,
|
||||
"type": "DeviceDiscovered",
|
||||
"time": "2014\-07\-17T13:28:05.043465207+02:00",
|
||||
"data": {
|
||||
"addrs": [
|
||||
"172.16.32.25:22000"
|
||||
],
|
||||
"device": "NFGKEKE\-7Z6RTH7\-I3PRZXS\-DEJF3UJ\-FRWJBFO\-VBBTDND\-4SGNGVZ\-QUQHJAG"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS DeviceRejected
|
||||
.sp
|
||||
Emitted when there is a connection from a device we are not configured
|
||||
to talk to.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 24,
|
||||
"type": "DeviceRejected",
|
||||
"time": "2014\-08\-19T10:43:00.562821045+02:00",
|
||||
"data": {
|
||||
"address": "127.0.0.1:51807",
|
||||
"device": "EJHMPAQ\-OGCVORE\-ISB4IS3\-SYYVJXF\-TKJGLTU\-66DIQPF\-GJ5D2GX\-GQ3OWQK"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS DownloadProgress
|
||||
.sp
|
||||
Emitted during file downloads for each folder for each file. By default
|
||||
only a single file in a folder is handled at the same time, but custom
|
||||
configuration can cause multiple files to be shown.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 221,
|
||||
"type": "DownloadProgress",
|
||||
"time": "2014\-12\-13T00:26:12.9876937Z",
|
||||
"data": {
|
||||
"folder1": {
|
||||
"file1": {
|
||||
"Total": 800,
|
||||
"Pulling": 2,
|
||||
"CopiedFromOrigin": 0,
|
||||
"Reused": 633,
|
||||
"CopiedFromElsewhere": 0,
|
||||
"Pulled": 38,
|
||||
"BytesTotal": 104792064,
|
||||
"BytesDone": 87883776
|
||||
},
|
||||
"dir\e\efile2": {
|
||||
"Total": 80,
|
||||
"Pulling": 2,
|
||||
"CopiedFromOrigin": 0,
|
||||
"Reused": 0,
|
||||
"CopiedFromElsewhere": 0,
|
||||
"Pulled": 32,
|
||||
"BytesTotal": 10420224,
|
||||
"BytesDone": 4128768
|
||||
}
|
||||
},
|
||||
"folder2": {
|
||||
"file3": {
|
||||
"Total": 800,
|
||||
"Pulling": 2,
|
||||
"CopiedFromOrigin": 0,
|
||||
"Reused": 633,
|
||||
"CopiedFromElsewhere": 0,
|
||||
"Pulled": 38,
|
||||
"BytesTotal": 104792064,
|
||||
"BytesDone": 87883776
|
||||
},
|
||||
"dir\e\efile4": {
|
||||
"Total": 80,
|
||||
"Pulling": 2,
|
||||
"CopiedFromOrigin": 0,
|
||||
"Reused": 0,
|
||||
"CopiedFromElsewhere": 0,
|
||||
"Pulled": 32,
|
||||
"BytesTotal": 10420224,
|
||||
"BytesDone": 4128768
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
\fBTotal\fP \- total number of blocks in the file
|
||||
.IP \(bu 2
|
||||
\fBPulling\fP \- number of blocks currently being downloaded
|
||||
.IP \(bu 2
|
||||
\fBCopiedFromOrigin\fP \- number of blocks copied from the file we are
|
||||
about to replace
|
||||
.IP \(bu 2
|
||||
\fBReused\fP \- number of blocks reused from a previous temporary file
|
||||
.IP \(bu 2
|
||||
\fBCopiedFromElsewhere\fP \- number of blocks copied from other files or
|
||||
potentially other folders
|
||||
.IP \(bu 2
|
||||
\fBPulled\fP \- number of blocks actually downloaded so far
|
||||
.IP \(bu 2
|
||||
\fBBytesTotal\fP \- approximate total file size
|
||||
.IP \(bu 2
|
||||
\fBBytesDone\fP \- approximate number of bytes already handled (already
|
||||
reused, copied or pulled)
|
||||
.UNINDENT
|
||||
.sp
|
||||
Where block size is 128KB.
|
||||
.sp
|
||||
Files/folders appearing in the event data imply that the download has
|
||||
been started for that file/folder, where disappearing implies that the
|
||||
downloads has been finished or failed for that file/folder. There is
|
||||
always a last event emitted with no data, which implies all downloads
|
||||
being finished/failed.
|
||||
.SS FolderCompletion
|
||||
.sp
|
||||
The \fBFolderCompletion\fP event is emitted when the local or remote
|
||||
contents for a folder changes. It contains the completion percentage for
|
||||
a given remote device and is emitted once per currently connected remote
|
||||
device.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 84,
|
||||
"type": "FolderCompletion",
|
||||
"time": "2015\-04\-17T14:14:27.043576583+09:00",
|
||||
"data": {
|
||||
"completion": 100,
|
||||
"device": "I6KAH76\-66SLLLB\-5PFXSOA\-UFJCDZC\-YAOMLEK\-CP2GB32\-BV5RQST\-3PSROAU",
|
||||
"folder": "default"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS FolderRejected
|
||||
.sp
|
||||
Emitted when a device sends index information for a folder we do not
|
||||
have, or have but do not share with the device in question.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 27,
|
||||
"type": "FolderRejected",
|
||||
"time": "2014\-08\-19T10:41:06.761751399+02:00",
|
||||
"data": {
|
||||
"device": "EJHMPAQ\-OGCVORE\-ISB4IS3\-SYYVJXF\-TKJGLTU\-66DIQPF\-GJ5D2GX\-GQ3OWQK",
|
||||
"folder": "unique"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS FolderSummary
|
||||
.sp
|
||||
The FolderSummary event is emitted when folder contents have changed
|
||||
locally. This can be used to calculate the current local completion
|
||||
state.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 16,
|
||||
"type": "FolderSummary",
|
||||
"time": "2015\-04\-17T14:12:20.460121585+09:00",
|
||||
"data": {
|
||||
"folder": "default",
|
||||
"summary": {
|
||||
"globalBytes": 0,
|
||||
"globalDeleted": 0,
|
||||
"globalFiles": 0,
|
||||
"ignorePatterns": false,
|
||||
"inSyncBytes": 0,
|
||||
"inSyncFiles": 0,
|
||||
"invalid": "",
|
||||
"localBytes": 0,
|
||||
"localDeleted": 0,
|
||||
"localFiles": 0,
|
||||
"needBytes": 0,
|
||||
"needFiles": 0,
|
||||
"state": "idle",
|
||||
"stateChanged": "2015\-04\-17T14:12:12.455224687+09:00",
|
||||
"version": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS ItemFinished
|
||||
.sp
|
||||
Generated when Syncthing ends synchronizing a file to a newer version. A
|
||||
successful operation:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 93,
|
||||
"type": "ItemFinished",
|
||||
"time": "2014\-07\-13T21:22:03.414609034+02:00",
|
||||
"data": {
|
||||
"item": "test.txt",
|
||||
"folder": "default",
|
||||
"error": null,
|
||||
"type": "file",
|
||||
"action": "update"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
An unsuccessful operation:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 44,
|
||||
"type": "ItemFinished",
|
||||
"time": "2015\-05\-27T11:21:05.711133004+02:00",
|
||||
"data": {
|
||||
"action": "update",
|
||||
"error": "open /Users/jb/src/github.com/syncthing/syncthing/test/s2/foo/.syncthing.hej.tmp: permission denied",
|
||||
"folder": "default",
|
||||
"item": "foo/hej",
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
The \fBaction\fP field is either \fBupdate\fP or \fBdelete\fP\&.
|
||||
.SS ItemStarted
|
||||
.sp
|
||||
Generated when Syncthing begins synchronizing a file to a newer version.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 93,
|
||||
"type": "ItemStarted",
|
||||
"time": "2014\-07\-13T21:22:03.414609034+02:00",
|
||||
"data": {
|
||||
"item": "test.txt",
|
||||
"folder": "default",
|
||||
"type": "file",
|
||||
"action": "update"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
The \fBaction\fP field is either \fBupdate\fP or \fBdelete\fP\&.
|
||||
.SS LocalIndexUpdated
|
||||
.sp
|
||||
Generated when the local index information has changed, due to
|
||||
synchronizing one or more items from the cluster or discovering local
|
||||
changes during a scan.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 59,
|
||||
"type": "LocalIndexUpdated",
|
||||
"time": "2014\-07\-17T13:27:28.051369434+02:00",
|
||||
"data": {
|
||||
"folder": "default",
|
||||
"items": 1000,
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS Ping
|
||||
.sp
|
||||
The Ping event is generated automatically every 60 seconds. This means
|
||||
that even in the absence of any other activity, the event polling HTTP
|
||||
request will return within a minute.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 46,
|
||||
"type": "Ping",
|
||||
"time": "2014\-07\-13T21:13:18.502171586+02:00",
|
||||
"data": null
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS RemoteIndexUpdated
|
||||
.sp
|
||||
Generated each time new index information is received from a device.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 44,
|
||||
"type": "RemoteIndexUpdated",
|
||||
"time": "2014\-07\-13T21:04:35.394184435+02:00",
|
||||
"data": {
|
||||
"device": "NFGKEKE\-7Z6RTH7\-I3PRZXS\-DEJF3UJ\-FRWJBFO\-VBBTDND\-4SGNGVZ\-QUQHJAG",
|
||||
"folder": "lightroom",
|
||||
"items": 1000
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS Starting
|
||||
.sp
|
||||
Emitted exactly once, when Syncthing starts, before parsing
|
||||
configuration etc.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 1,
|
||||
"type": "Starting",
|
||||
"time": "2014\-07\-17T13:13:32.044470055+02:00",
|
||||
"data": {
|
||||
"home": "/home/jb/.config/syncthing"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS StartupCompleted
|
||||
.sp
|
||||
Emitted exactly once, when initialization is complete and Syncthing is
|
||||
ready to start exchanging data with other devices.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 1,
|
||||
"type": "StartupComplete",
|
||||
"time": "2014\-07\-13T21:03:18.383239179+02:00",
|
||||
"data": null
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS StateChanged
|
||||
.sp
|
||||
Emitted when a folder changes state. Possible states are \fBidle\fP,
|
||||
\fBscanning\fP, \fBcleaning\fP and \fBsyncing\fP\&. The field \fBduration\fP is
|
||||
the number of seconds the folder spent in state \fBfrom\fP\&. In the example
|
||||
below, the folder \fBdefault\fP was in state \fBscanning\fP for 0.198
|
||||
seconds and is now in state \fBidle\fP\&.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"id": 8,
|
||||
"type": "StateChanged",
|
||||
"time": "2014\-07\-17T13:14:28.697493016+02:00",
|
||||
"data": {
|
||||
"folder": "default",
|
||||
"from": "scanning",
|
||||
"duration": 0.19782869900000002,
|
||||
"to": "idle"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
290
man/syncthing-faq.7
Normal file
290
man/syncthing-faq.7
Normal file
@@ -0,0 +1,290 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-FAQ" "7" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-faq \- Frequently Asked Questions
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.SH GENERAL
|
||||
.SS What is Syncthing?
|
||||
.sp
|
||||
Syncthing is an application that lets you synchronize your files across multiple
|
||||
devices. This means the creation, modification or deletion of files on one
|
||||
machine will automatically be replicated to your other devices. We believe your
|
||||
data is your data alone and you deserve to choose where it is stored. Therefore
|
||||
Syncthing does not upload your data to the cloud but exchanges your data across
|
||||
your machines as soon as they are online at the same time.
|
||||
.SS Is it "syncthing", "Syncthing" or "SyncThing"?
|
||||
.sp
|
||||
It\(aqs \fBSyncthing\fP, although the command and source repository is spelled
|
||||
\fBsyncthing\fP so it may be referred to in that way as well. It\(aqs definitely not
|
||||
SyncThing, even though the abbreviation \fBst\fP is used in some
|
||||
circumstances and file names.
|
||||
.SS How does Syncthing differ from BitTorrent Sync?
|
||||
.sp
|
||||
The two are different and not related. Syncthing and BitTorrent Sync accomplish
|
||||
some of the same things, namely syncing files between two or more computers.
|
||||
.sp
|
||||
BitTorrent Sync by BitTorrent, Inc is a proprietary peer\-to\-peer file
|
||||
synchronization tool available for Windows, Mac, Linux, Android, iOS, Windows
|
||||
Phone, Amazon Kindle Fire and BSD. \fI\%1\fP <\fBhttp://en.wikipedia.org/wiki/BitTorrent_Sync\fP> Syncthing is an open source
|
||||
file synchronization tool.
|
||||
.sp
|
||||
Syncthing uses an open and documented protocol, and likewise the security
|
||||
mechanisms in use are well defined and visible in the source code. BitTorrent
|
||||
Sync uses an undocumented, closed protocol with unknown security properties.
|
||||
.SH USAGE
|
||||
.SS What things are synced?
|
||||
.sp
|
||||
The following things are \fIalways\fP synchronized:
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
File Contents
|
||||
.IP \(bu 2
|
||||
File Modification Times
|
||||
.UNINDENT
|
||||
.sp
|
||||
The following may be synchronized or not, depending:
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
File Permissions (When supported by file system. On Windows, only the
|
||||
read only bit is synchronized.)
|
||||
.IP \(bu 2
|
||||
Symbolic Links (When supported by the OS. On Windows Vista and up,
|
||||
requires administrator privileges. Links are synced as is and are not
|
||||
followed.)
|
||||
.UNINDENT
|
||||
.sp
|
||||
The following is \fInot\fP synchronized;
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
File or Directory Owners and Groups (not preserved)
|
||||
.IP \(bu 2
|
||||
Directory Modification Times (not preserved)
|
||||
.IP \(bu 2
|
||||
Hard Links (followed, not preserved)
|
||||
.IP \(bu 2
|
||||
Extended Attributes, Resource Forks (not preserved)
|
||||
.IP \(bu 2
|
||||
Windows, POSIX or NFS ACLs (not preserved)
|
||||
.IP \(bu 2
|
||||
Devices, FIFOs, and Other Specials (ignored)
|
||||
.IP \(bu 2
|
||||
Sparse file sparseness (will become unsparse)
|
||||
.UNINDENT
|
||||
.SS Is synchronization fast?
|
||||
.sp
|
||||
Syncthing segments files into pieces, called blocks, to transfer data from one
|
||||
device to another. Therefore, multiple devices can share the synchronization
|
||||
load, in a similar way as the torrent protocol. The more devices you have online
|
||||
(and synchronized), the faster an additional device will receive the data
|
||||
because small blocks will be fetched from all devices in parallel.
|
||||
.sp
|
||||
Syncthing handles renaming files and updating their metadata in an efficient
|
||||
manner. This means that renaming a large file will not cause a retransmission of
|
||||
that file. Additionally, appending data to existing large files should be
|
||||
handled efficiently as well.
|
||||
.sp
|
||||
Temporary files are used to store partial data downloaded from other devices.
|
||||
They are automatically removed whenever a file transfer has been completed or
|
||||
after the configured amount of time which is set in the configuration file (24
|
||||
hours by default).
|
||||
.SS Should I keep my device IDs secret?
|
||||
.sp
|
||||
No. The IDs are not sensitive. Given a device ID it\(aqs possible to find the IP
|
||||
address for that node, if global discovery is enabled on it. Knowing the device
|
||||
ID doesn\(aqt help you actually establish a connection to that node or get a list
|
||||
of files, etc.
|
||||
.sp
|
||||
For a connection to be established, both nodes need to know about the other\(aqs
|
||||
device ID. It\(aqs not possible (in practice) to forge a device ID. (To forge a
|
||||
device ID you need to create a TLS certificate with that specific SHA\-256 hash.
|
||||
If you can do that, you can spoof any TLS certificate. The world is your
|
||||
oyster!)
|
||||
.sp
|
||||
\fBSEE ALSO:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
device\-ids
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS What if there is a conflict?
|
||||
.sp
|
||||
Syncthing does recognize conflicts. When a file has been modified on two devices
|
||||
simultaneously, one of the files will be renamed to \fB<filename>.sync\-
|
||||
conflict\-<date>\-<time>.<ext>\fP\&. The device which has the larger value of the
|
||||
first 63 bits for his device ID will have his file marked as the conflicting
|
||||
file. Note that we only create \fBsync\-conflict\fP files when the actual content
|
||||
differs.
|
||||
.sp
|
||||
Beware that the \fB<filename>.sync\-conflict\-<date>\-<time>.<ext>\fP files are
|
||||
treated as normal files after they are created, so they are propagated between
|
||||
devices. We do this because the conflict is detected and resolved on one device,
|
||||
creating the \fBsync\-conflict\fP file, but it\(aqs just as much of a conflict
|
||||
everywhere else and we don\(aqt know which of the conflicting files is the "best"
|
||||
from the user point of view. Moreover, if there\(aqs something that automatically
|
||||
causes a conflict on change you\(aqll end up with \fBsync\-conflict\-...sync\-conflict
|
||||
\-...\-sync\-conflict\fP files.
|
||||
.SS How to configure multiple users on a single machine?
|
||||
.sp
|
||||
Each user should run their own Syncthing instance. Be aware that you might need
|
||||
to configure ports such that they do not overlap (see the config.xml).
|
||||
.SS Is Syncthing my ideal backup application?
|
||||
.sp
|
||||
No, Syncthing is not a backup application because all changes to your files
|
||||
(modification, deletion, etc) will be propagated to all your devices. You can
|
||||
enable versioning, but we encourage the use of other tools to keep your data
|
||||
safe from your (or our) mistakes.
|
||||
.SS Why is there no iOS client?
|
||||
.sp
|
||||
Alternative implementation Syncthing (using the Syncthing protocol) are being
|
||||
developed at this point in time to enable iOS support. Additionally, it seems
|
||||
that the next version of Go will support the darwin\-arm architecture such that
|
||||
we can compile the mainstream code for the iOS platform.
|
||||
.SS Why does it use so much CPU?
|
||||
.INDENT 0.0
|
||||
.IP 1. 3
|
||||
When new or changed files are detected, or Syncthing starts for the
|
||||
first time, your files are hashed using SHA\-256.
|
||||
.IP 2. 3
|
||||
Data that is sent over the network is first compressed and then
|
||||
encrypted using AES\-128. When receiving data, it must be decrypted
|
||||
and decompressed.
|
||||
.UNINDENT
|
||||
.sp
|
||||
Hashing, compression and encryption cost CPU time. Also, using the GUI causes a
|
||||
certain amount of CPU usage. Note however that once things are \fIin sync\fP CPU
|
||||
usage should be negligible.
|
||||
.SS How can I exclude files with brackets (\fB[]\fP) in the name?
|
||||
.sp
|
||||
The patterns in .stignore are glob patterns, where brackets are used to denote
|
||||
character ranges. That is, the pattern \fBq[abc]x\fP will match the files \fBqax\fP,
|
||||
\fBqbx\fP and \fBqcx\fP\&.
|
||||
.sp
|
||||
To match an actual file \fIcalled\fP \fBq[abc]x\fP the pattern needs to "escape" the
|
||||
brackets, like so: \fBq\e[abc\e]x\fP\&.
|
||||
.SS Why is the setup more complicated than BTSync?
|
||||
.sp
|
||||
Security over convenience. In Syncthing you have to setup both sides to connect
|
||||
two nodes. An attacker can\(aqt do much with a stolen node ID, because you have to
|
||||
add the node on the other side too. You have better control where your files are
|
||||
transferred.
|
||||
.SS How do I access the web GUI from another computer?
|
||||
.sp
|
||||
The default listening address is 127.0.0.1:8384, so you can only access the GUI
|
||||
from the same machine. Change the \fBGUI listen address\fP through the web UI from
|
||||
\fB127.0.0.1:8384\fP to \fB0.0.0.0:8384\fP or change the config.xml:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
<gui enabled="true" tls="false">
|
||||
<address>127.0.0.1:8384</address>
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
to
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
<gui enabled="true" tls="false">
|
||||
<address>0.0.0.0:8384</address>
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
Then the GUI is accessible from everywhere. You should most likely set a
|
||||
password and enable HTTPS now. You can do this from inside the GUI.
|
||||
.sp
|
||||
If both your computers are Unixy (Linux, Mac, etc) You can also leave the GUI
|
||||
settings at default and use an ssh port forward to access it. For example,
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$ ssh \-L 9090:127.0.0.1:8384 user@othercomputer.example.com
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
will log you into othercomputer.example.com, and present the \fIremote\fP Syncthing
|
||||
GUI on \fI\%http://localhost:9090\fP on your \fIlocal\fP computer. You should not open more
|
||||
than one Syncthing GUI in a single browser due to conflicting X\-CSRFTokens. Any
|
||||
modification will be rejected. See \fI\%issue #720\fP <\fBhttps://github.com/syncthing/syncthing/issues/720\fP> to work around this limitation.
|
||||
.sp
|
||||
The CSRF tokens are stored using cookies. Therefore, if you get the message
|
||||
\fBSyncthing seems to be experiencing a problem processing your request\fP, you
|
||||
should verify the cookie settings of your browser.
|
||||
.SS Why do I see Syncthing twice in task manager?
|
||||
.sp
|
||||
One process manages the other, to capture logs and manage restarts. This makes
|
||||
it easier to handle upgrades from within Syncthing itself, and also ensures that
|
||||
we get a nice log file to help us narrow down the cause for crashes and other
|
||||
bugs.
|
||||
.SS Where do Syncthing logs go to?
|
||||
.sp
|
||||
Syncthing logs to stdout by default. On Windows Syncthing by default also
|
||||
creates \fBsyncthing.log\fP in Syncthing\(aqs home directory (check \fB\-help\fP to see
|
||||
where that is).
|
||||
.SS How do I upgrade Syncthing?
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
If automatic upgrades is enabled (which is the default), Syncthing will
|
||||
upgrade itself automatically within 24 hours of a new release.
|
||||
.IP \(bu 2
|
||||
The upgrade button appears in the web GUI when a new version has been released.
|
||||
Pressing it will perform an upgrade.
|
||||
.IP \(bu 2
|
||||
To force an upgrade from the command line, run \fBsyncthing \-upgrade\fP\&.
|
||||
.UNINDENT
|
||||
.sp
|
||||
Note that your system should have CA certificates installed which allow a secure
|
||||
connection to GitHub (e.g. FreeBSD requires \fBsudo pkg install ca_root_nss\fP).
|
||||
If \fBcurl\fP or \fBwget\fP works with normal HTTPS sites, then so should Syncthing.
|
||||
.SS Where do I find the latest release?
|
||||
.sp
|
||||
We release new versions through GitHub. The latest release is always found \fI\%on
|
||||
the release page\fP <\fBhttps://github.com/syncthing/syncthing/releases/latest\fP>\&.
|
||||
Unfortunately GitHub does not provide a single URL to automatically download the
|
||||
latest version. We suggest to use the GitHub API at
|
||||
\fI\%https://api.github.com/repos/syncthing/syncthing/releases/latest\fP and parsing the
|
||||
JSON response.
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
104
man/syncthing-networking.7
Normal file
104
man/syncthing-networking.7
Normal file
@@ -0,0 +1,104 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-NETWORKING" "7" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-networking \- Firewall Setup
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.SH PORT FORWARDS
|
||||
.sp
|
||||
If you have a NAT router which supports UPnP, the easiest way to get a working
|
||||
port forward is to make sure UPnP setting is enabled on both Syncthing and the
|
||||
router – Syncthing will try to handle the rest. If it succeeds you will see a
|
||||
message in the console saying:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
Created UPnP port mapping for external port XXXXX on UPnP device YYYYY.
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
If this is not possible or desirable you should set up a port forward for port
|
||||
\fB22000/TCP\fP, or the port set in the \fISync Protocol Listen Address\fP setting.
|
||||
The external forwarded port and the internal destination port has to be the same
|
||||
(i.e. 22000/TCP).
|
||||
.sp
|
||||
Communication in Syncthing works both ways. Therefore if you set up port
|
||||
forwards for one device, other devices will be able to connect to it even when
|
||||
they are behind a NAT network or firewall.
|
||||
.SH LOCAL FIREWALL
|
||||
.sp
|
||||
If your PC has a local firewall, you will need to open the following ports for
|
||||
incoming traffic:
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
Port \fB22000/TCP\fP (or the actual listening port if you have changed
|
||||
the \fISync Protocol Listen Address\fP setting.)
|
||||
.IP \(bu 2
|
||||
Port \fB21025/UDP\fP (for discovery broadcasts)
|
||||
.UNINDENT
|
||||
.SH REMOTE WEB GUI
|
||||
.sp
|
||||
To be able to access the web GUI from other computers, you need to change the
|
||||
\fIGUI Listen Address\fP setting from the default \fB127.0.0.1:8384\fP to
|
||||
\fB0.0.0.0:8384\fP\&. You also need to open the port in your local firewall if you
|
||||
have one.
|
||||
.SS Tunneling via SSH
|
||||
.sp
|
||||
If you have SSH access to the machine running Syncthing but would rather not
|
||||
open the web GUI port to the outside world, you can access it through a SSH
|
||||
tunnel instead. You can start a tunnel with a command like the following:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
ssh \-L 9999:localhost:8384 machine
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
This will bind to your local port 9999 and forward all connections from there to
|
||||
port 8384 on the target machine. This still works even if Syncthing is bound to
|
||||
listen on localhost only.
|
||||
.sp
|
||||
You can forward multiple ports corresponding to many machines this way, but
|
||||
because Syncthing uses session cookies for the entire domain (i.e. your local
|
||||
machine), you will need to connect to each control panel in a separate browser
|
||||
instance or explicitly issue a browser reload when switching between them.
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
668
man/syncthing-rest-api.7
Normal file
668
man/syncthing-rest-api.7
Normal file
@@ -0,0 +1,668 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-REST-API" "7" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-rest-api \- REST API
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
Syncthing exposes a REST interface over HTTP on the GUI port. This is used by
|
||||
the GUI code (JavaScript) and can be used by other processes wishing to control
|
||||
Syncthing. In most cases both the input and output data is in JSON format. The
|
||||
interface is subject to change.
|
||||
.SH API KEY
|
||||
.sp
|
||||
To use the POST methods, or \fIany\fP method when authentication is enabled, an API
|
||||
key must be set and used. The API key can be generated in the GUI, or set in the
|
||||
\fBconfiguration/gui/apikey\fP element in the configuration file. To use an API
|
||||
key, set the request header \fBX\-API\-Key\fP to the API key value.
|
||||
.SH SYSTEM ENDPOINTS
|
||||
.SS GET /rest/system/config
|
||||
.sp
|
||||
Returns the current configuration.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
# etc
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/system/config/insync
|
||||
.sp
|
||||
Returns whether the config is in sync, i.e. whether the running
|
||||
configuration is the same as that on disk.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"configInSync": true
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/config
|
||||
.sp
|
||||
Post the full contents of the configuration, in the same format as returned by
|
||||
the corresponding GET request. The configuration will be saved to disk and the
|
||||
\fBconfigInSync\fP flag set to false. Restart Syncthing to activate.
|
||||
.SS GET /rest/system/connections
|
||||
.sp
|
||||
Returns the list of current connections and some metadata associated
|
||||
with the connection/peer.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"connections": {
|
||||
"SMAHWLH\-AP74FAB\-QWLDYGV\-Q65ASPL\-GAAR2TB\-KEF5FLB\-DRLZCPN\-DJBFZAG": {
|
||||
"address": "172.21.20.78:22000",
|
||||
"at": "2015\-03\-16T21:51:38.672758819+01:00",
|
||||
"clientVersion": "v0.10.27",
|
||||
"inBytesTotal": 415980,
|
||||
"outBytesTotal": 396300
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"address": "",
|
||||
"at": "2015\-03\-16T21:51:38.672868814+01:00",
|
||||
"clientVersion": "",
|
||||
"inBytesTotal": 415980,
|
||||
"outBytesTotal": 396300
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/system/discovery
|
||||
.sp
|
||||
Returns the contents of the local discovery cache.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q": [
|
||||
"192.162.129.11:22000"
|
||||
]
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/discovery/hint
|
||||
.sp
|
||||
Post with the query parameters \fBdevice\fP and \fBaddr\fP to add entries to
|
||||
the discovery cache.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
curl \-X POST http://127.0.0.1:8384/rest/system/discovery/hint?device=LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q\e&addr=192.162.129.11:22000
|
||||
# Or with the X\-API\-Key header:
|
||||
curl \-X POST \-\-header "X\-API\-Key: TcE28kVPdtJ8COws1JdM0b2nodj77WeQ" http://127.0.0.1:8384/rest/system/discovery/hint?device=LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q\e&addr=192.162.129.11:22000
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/error/clear
|
||||
.sp
|
||||
Post with empty to body to remove all recent errors.
|
||||
.SS GET /rest/system/error
|
||||
.sp
|
||||
Returns the list of recent errors.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"time": "2014\-09\-18T12:59:26.549953186+02:00",
|
||||
"error": "This is an error string"
|
||||
}
|
||||
]
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/error
|
||||
.sp
|
||||
Post with an error message in the body (plain text) to register a new
|
||||
error. The new error will be displayed on any active GUI clients.
|
||||
.SS GET /rest/system/ping
|
||||
.sp
|
||||
Returns a \fB{"ping": "pong"}\fP object.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"ping": "pong"
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/ping
|
||||
.sp
|
||||
Returns a \fB{"ping": "pong"}\fP object.
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
Due to being a POST request, this method requires using an API key or CSRF token, as opposed to the GET request to the same URL.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/reset
|
||||
.sp
|
||||
Post with empty body to immediately \fIreset\fP Syncthing. This means
|
||||
renaming all folder directories to temporary, unique names, wiping all
|
||||
indexes and restarting. This should probably not be used during normal
|
||||
operations...
|
||||
.SS POST /rest/system/restart
|
||||
.sp
|
||||
Post with empty body to immediately restart Syncthing.
|
||||
.SS POST /rest/system/shutdown
|
||||
.sp
|
||||
Post with empty body to cause Syncthing to exit and not restart.
|
||||
.SS GET /rest/system/status
|
||||
.sp
|
||||
Returns information about current system status and resource usage.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"alloc": 30618136,
|
||||
"cpuPercent": 0.006944836512046966,
|
||||
"extAnnounceOK": {
|
||||
"udp4://announce.syncthing.net:22026": true,
|
||||
"udp6://announce\-v6.syncthing.net:22026": true
|
||||
},
|
||||
"goroutines": 49,
|
||||
"myID": "P56IOI7\-MZJNU2Y\-IQGDREY\-DM2MGTI\-MGL3BXN\-PQ6W5BM\-TBBZ4TJ\-XZWICQ2",
|
||||
"pathSeparator": "/",
|
||||
"sys": 42092792,
|
||||
"tilde": "/Users/jb"
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/system/upgrade
|
||||
.sp
|
||||
Checks for a possible upgrade and returns an object describing the
|
||||
newest version and upgrade possibility.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"latest": "v0.10.27",
|
||||
"newer": false,
|
||||
"running": "v0.10.27+5\-g36c93b7"
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/system/upgrade
|
||||
.sp
|
||||
Perform an upgrade to the newest released version and restart. Does
|
||||
nothing if there is no newer version than currently running.
|
||||
.SS GET /rest/system/version
|
||||
.sp
|
||||
Returns the current Syncthing version information.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"arch": "amd64",
|
||||
"longVersion": "syncthing v0.10.27+3\-gea8c3de (go1.4 darwin\-amd64 default) jb@syno 2015\-03\-16 11:01:29 UTC",
|
||||
"os": "darwin",
|
||||
"version": "v0.10.27+3\-gea8c3de"
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH DATABASE ENDPOINTS
|
||||
.SS GET /rest/db/browse
|
||||
.sp
|
||||
Returns the directory tree of the global model. Directories are always
|
||||
JSON objects (map/dictionary), and files are always arrays of
|
||||
modification time and size. The first integer is the files modification
|
||||
time, and the second integer is the file size.
|
||||
.sp
|
||||
The call takes one mandatory \fBfolder\fP parameter and two optional
|
||||
parameters. Optional parameter \fBlevels\fP defines how deep within the
|
||||
tree we want to dwell down (0 based, defaults to unlimited depth)
|
||||
Optional parameter \fBprefix\fP defines a prefix within the tree where to
|
||||
start building the structure.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$ curl \-s http://localhost:8384/rest/db/browse?folder=default | json_pp
|
||||
{
|
||||
"directory": {
|
||||
"file": ["2015\-04\-20T22:20:45+09:00", 130940928],
|
||||
"subdirectory": {
|
||||
"another file": ["2015\-04\-20T22:20:45+09:00", 130940928]
|
||||
}
|
||||
},
|
||||
"rootfile": ["2015\-04\-20T22:20:45+09:00", 130940928]
|
||||
}
|
||||
|
||||
$ curl \-s http://localhost:8384/rest/db/browse?folder=default&levels=0 | json_pp
|
||||
{
|
||||
"directory": {},
|
||||
"rootfile": ["2015\-04\-20T22:20:45+09:00", 130940928]
|
||||
}
|
||||
|
||||
$ curl \-s http://localhost:8384/rest/db/browse?folder=default&levels=1 | json_pp
|
||||
{
|
||||
"directory": {
|
||||
"file": ["2015\-04\-20T22:20:45+09:00", 130940928],
|
||||
"subdirectory": {}
|
||||
},
|
||||
"rootfile": ["2015\-04\-20T22:20:45+09:00", 130940928]
|
||||
}
|
||||
|
||||
$ curl \-s http://localhost:8384/rest/db/browse?folder=default&prefix=directory/subdirectory | json_pp
|
||||
{
|
||||
"another file": ["2015\-04\-20T22:20:45+09:00", 130940928]
|
||||
}
|
||||
|
||||
$ curl \-s http://localhost:8384/rest/db/browse?folder=default&prefix=directory&levels=0 | json_pp
|
||||
{
|
||||
"file": ["2015\-04\-20T22:20:45+09:00", 130940928],
|
||||
"subdirectory": {}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/db/completion
|
||||
.sp
|
||||
Returns the completion percentage (0 to 100) for a given device and
|
||||
folder.Takes \fBdevice\fP and \fBfolder\fP parameters.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"completion": 0
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/db/file
|
||||
.sp
|
||||
Returns most data available about a given file, including version and
|
||||
availability.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"availability": [
|
||||
"I6KAH76\-66SLLLB\-5PFXSOA\-UFJCDZC\-YAOMLEK\-CP2GB32\-BV5RQST\-3PSROAU"
|
||||
],
|
||||
"global": {
|
||||
"flags": "0644",
|
||||
"localVersion": 3,
|
||||
"modified": "2015\-04\-20T22:20:45+09:00",
|
||||
"name": "util.go",
|
||||
"numBlocks": 1,
|
||||
"size": 9642,
|
||||
"version": [
|
||||
"5407294127585413568:1"
|
||||
]
|
||||
},
|
||||
"local": {
|
||||
"flags": "0644",
|
||||
"localVersion": 4,
|
||||
"modified": "2015\-04\-20T22:20:45+09:00",
|
||||
"name": "util.go",
|
||||
"numBlocks": 1,
|
||||
"size": 9642,
|
||||
"version": [
|
||||
"5407294127585413568:1"
|
||||
]
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/db/ignores
|
||||
.sp
|
||||
Takes one parameter, \fBfolder\fP, and returns the content of the
|
||||
\fB\&.stignore\fP as the \fBignore\fP field. A second field, \fBpatterns\fP,
|
||||
provides a compiled version of all included ignore patterns in the form
|
||||
of regular expressions. Excluded items in the \fBpatterns\fP field have a
|
||||
nonstandard \fB(?exclude)\fP marker in front of the regular expression.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"ignore": [
|
||||
"/Backups"
|
||||
],
|
||||
"patterns": [
|
||||
"(?i)^Backups$",
|
||||
"(?i)^Backups/.*$"
|
||||
]
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/db/ignores
|
||||
.sp
|
||||
Expects a format similar to the output of \fBGET\fP call, but only
|
||||
containing the \fBignore\fP field (\fBpatterns\fP field should be omitted).
|
||||
It takes one parameter, \fBfolder\fP, and either updates the content of
|
||||
the \fB\&.stignore\fP echoing it back as a response, or returns an error.
|
||||
.SS GET /rest/db/need
|
||||
.sp
|
||||
Takes one parameter, \fBfolder\fP, and returns lists of files which are
|
||||
needed by this device in order for it to become in sync.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
# Files currently being downloaded
|
||||
"progress": [
|
||||
{
|
||||
"flags": "0755",
|
||||
"localVersion": 6,
|
||||
"modified": "2015\-04\-20T23:06:12+09:00",
|
||||
"name": "ls",
|
||||
"size": 34640,
|
||||
"version": [
|
||||
"5157751870738175669:1"
|
||||
]
|
||||
}
|
||||
],
|
||||
# Files queued to be downloaded next (as per array order)
|
||||
"queued": [
|
||||
...
|
||||
],
|
||||
# Files to be downloaded after all queued files will be downloaded.
|
||||
# This happens when we start downloading files, and new files get added while we are downloading.
|
||||
"rest": [
|
||||
...
|
||||
]
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS POST /rest/db/prio
|
||||
.sp
|
||||
Moves the file to the top of the download queue.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
curl \-X POST http://127.0.0.1:8384/rest/db/prio?folder=default&file=foo/bar
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
Response contains the same output as \fBGET /rest/db/need\fP
|
||||
.SS POST /rest/db/scan
|
||||
.sp
|
||||
Request immediate rescan of a folder, or a specific path within a folder.
|
||||
Takes the mandatory parameter \fIfolder\fP (folder ID), an optional parameter
|
||||
\fBsub\fP (path relative to the folder root) and an optional parameter \fBnext\fP\&. If
|
||||
\fBsub\fP is omitted or empty, the entire folder is scanned for changes, otherwise
|
||||
only the given path (and children, in case it\(aqs a directory) is scanned. The
|
||||
\fBnext\fP argument delays Syncthing\(aqs automated rescan interval for a given
|
||||
amount of seconds.
|
||||
.sp
|
||||
Requesting scan of a path that no longer exists, but previously did, is
|
||||
valid and will result in Syncthing noticing the deletion of the path in
|
||||
question.
|
||||
.sp
|
||||
Returns status 200 and no content upon success, or status 500 and a
|
||||
plain text error if an error occurred during scanning.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
curl \-X POST http://127.0.0.1:8384/rest/db/scan?folder=default&sub=foo/bar
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/db/status
|
||||
.sp
|
||||
Returns information about the current status of a folder.
|
||||
.sp
|
||||
Parameters: \fBfolder\fP, the ID of a folder.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
# latest version according to cluster:
|
||||
"globalBytes": 13173473780,
|
||||
"globalDeleted": 1847,
|
||||
"globalFiles": 42106,
|
||||
# what we have locally:
|
||||
"localBytes": 13173473780,
|
||||
"localDeleted": 1847,
|
||||
"localFiles": 42106,
|
||||
# which part of what we have locally is the latest cluster version:
|
||||
"inSyncBytes": 13173473780,
|
||||
"inSyncFiles": 42106,
|
||||
# which part of what we have locally should be fetched from the cluster:
|
||||
"needBytes": 0,
|
||||
"needFiles": 0,
|
||||
# various other metadata
|
||||
"ignorePatterns": true,
|
||||
"invalid": "",
|
||||
"state": "idle",
|
||||
"stateChanged": "2015\-03\-16T21:47:28.750853241+01:00",
|
||||
"version": 71989
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH STATISTICS ENDPOINTS
|
||||
.SS GET /rest/stats/device
|
||||
.sp
|
||||
Returns general statistics about devices. Currently, only contains the
|
||||
time the device was last seen.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$ curl \-s http://localhost:8384/rest/stats/device | json
|
||||
{
|
||||
"P56IOI7\-MZJNU2Y\-IQGDREY\-DM2MGTI\-MGL3BXN\-PQ6W5BM\-TBBZ4TJ\-XZWICQ2": {
|
||||
"lastSeen" : "2015\-04\-18T11:21:31.3256277+01:00"
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/stats/folder
|
||||
.sp
|
||||
Returns general statistics about folders. Currently, only contains the
|
||||
last synced file.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$ curl \-s http://localhost:8384/rest/stats/folder | json
|
||||
{
|
||||
"folderid" : {
|
||||
"lastFile" : {
|
||||
"filename" : "file/name",
|
||||
"at" : "2015\-04\-16T22:04:18.3066971+01:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH MISC SERVICES ENDPOINTS
|
||||
.SS GET /rest/svc/deviceid
|
||||
.sp
|
||||
Verifies and formats a device ID. Accepts all currently valid formats
|
||||
(52 or 56 characters with or without separators, upper or lower case,
|
||||
with trivial substitutions). Takes one parameter, \fBid\fP, and returns
|
||||
either a valid device ID in modern format, or an error.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$ curl \-s http://localhost:8384/rest/svc/deviceid?id=1234 | json
|
||||
{
|
||||
"error": "device ID invalid: incorrect length"
|
||||
}
|
||||
|
||||
$ curl \-s http://localhost:8384/rest/svc/deviceid?id=p56ioi7m\-\-zjnu2iq\-gdr\-eydm\-2mgtmgl3bxnpq6w5btbbz4tjxzwicq | json
|
||||
{
|
||||
"id": "P56IOI7\-MZJNU2Y\-IQGDREY\-DM2MGTI\-MGL3BXN\-PQ6W5BM\-TBBZ4TJ\-XZWICQ2"
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/svc/lang
|
||||
.sp
|
||||
Returns a list of canonicalized localization codes, as picked up from
|
||||
the \fBAccept\-Language\fP header sent by the browser.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
["sv_sv","sv","en_us","en"]
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/svc/report
|
||||
.sp
|
||||
Returns the data sent in the anonymous usage report.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"folderMaxFiles": 42106,
|
||||
"folderMaxMiB": 12563,
|
||||
"longVersion": "syncthing v0.10.27+5\-g36c93b7 (go1.4 darwin\-amd64 default) jb@syno 2015\-03\-16 20:43:34 UTC",
|
||||
"memorySize": 16384,
|
||||
"memoryUsageMiB": 41,
|
||||
"numDevices": 10,
|
||||
"numFolders": 4,
|
||||
"platform": "darwin\-amd64",
|
||||
"sha256Perf": 122.38,
|
||||
"totFiles": 45180,
|
||||
"totMiB": 18151,
|
||||
"uniqueID": "6vulmdGw",
|
||||
"version": "v0.10.27+5\-g36c93b7"
|
||||
}
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
135
man/syncthing-security.7
Normal file
135
man/syncthing-security.7
Normal file
@@ -0,0 +1,135 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-SECURITY" "7" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-security \- Security Principles
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.sp
|
||||
Security is one of the primary project goals. This means that it should not be
|
||||
possible for an attacker to join a cluster uninvited, and it should not be
|
||||
possible to extract private information from intercepted traffic. Currently this
|
||||
is implemented as follows.
|
||||
.sp
|
||||
All traffic is protected by TLS. To prevent uninvited nodes from joining a
|
||||
cluster, the certificate fingerprint of each node is compared to a preset list
|
||||
of acceptable nodes at connection establishment. The fingerprint is computed as
|
||||
the SHA\-256 hash of the certificate and displayed in BASE32 encoding to form a
|
||||
reasonably compact and convenient string.
|
||||
.sp
|
||||
Incoming requests for file data are verified to the extent that the requested
|
||||
file name must exist in the local index and the global model.
|
||||
.sp
|
||||
For information about ensuring you are running the code you think you are and
|
||||
for reporting security vulnerabilities, please see the official \fI\%security page\fP <\fBhttp://syncthing.net/security.html\fP>\&.
|
||||
.SH INFORMATION LEAKAGE
|
||||
.SS Global Discovery
|
||||
.sp
|
||||
When global discovery is enabled, Syncthing sends an announcement packet every
|
||||
30 minutes to the global discovery server, so that it can keep a mapping between
|
||||
your device ID and external IP. Also, when connecting to other devices that have
|
||||
not been seen on the local network, a query is sent to the global discovery
|
||||
server containing the device ID of the requested device. The discovery server is
|
||||
currently hosted by \fI\%@calmh\fP <\fBhttps://github.com/calmh\fP>\&. Global discovery defaults to \fBon\fP\&.
|
||||
.sp
|
||||
When turned off, devices with dynamic addresses not on the local network cannot
|
||||
be found and connected to.
|
||||
.sp
|
||||
If a different global discovery server is configured, no data is sent to the
|
||||
default global discovery server.
|
||||
.SS Local Discovery
|
||||
.sp
|
||||
When local discovery is enabled, Syncthing sends broadcast (IPv4) and multicast
|
||||
(IPv6) packets to the local network every 30 seconds. The packets contain the
|
||||
device ID and listening port. Local discovery defaults to \fBon\fP\&.
|
||||
.sp
|
||||
An eavesdropper on the local network can deduce which machines are running
|
||||
Syncthing with local discovery enabled, and what their device IDs are.
|
||||
.sp
|
||||
When turned off, devices with dynamic addresses on the local network cannot be
|
||||
found and connected to.
|
||||
.SS Upgrade Checks
|
||||
.sp
|
||||
When automatic upgrades are enabled, Syncthing checks for a new version at
|
||||
startup and then once every twelve hours. This is by an HTTPS request to the
|
||||
download site for releases, currently \fBhosted at GitHub\fP\&. Automatic upgrades
|
||||
default to \fBon\fP (unless Syncthing was compiled with upgrades disabled).
|
||||
.sp
|
||||
Even when automatic upgrades are disabled in the configuration, an upgrade check
|
||||
as above is done when the GUI is loaded, in order to show the "Upgrade to ..."
|
||||
button when necessary. This can be disabled only by compiling syncthing with
|
||||
upgrades disabled.
|
||||
.sp
|
||||
In effect this exposes the majority of the Syncthing population to tracking by
|
||||
the operator of the download site (currently GitHub). That data is not available
|
||||
to outside parties (including \fI\%@calmh\fP <\fBhttps://github.com/calmh\fP> etc), except that download counts
|
||||
per release binary are available in the GitHub API. The upgrade check (or
|
||||
download) requests \fIdo not\fP contain any identifiable information about the user,
|
||||
device, Syncthing version, etc.
|
||||
.SS Usage Reporting
|
||||
.sp
|
||||
When usage reporting is enabled, Syncthing reports usage data at startup and
|
||||
then every 24 hours. The report is sent as an HTTPS POST to the usage reporting
|
||||
server, currently hosted by \fI\%@calmh\fP <\fBhttps://github.com/calmh\fP>\&. The contents of the usage report can
|
||||
be seen behind the "Preview" link in settings. Usage reporting defaults to
|
||||
\fBoff\fP but the GUI will ask once about enabling it, shortly after the first
|
||||
install.
|
||||
.sp
|
||||
The reported data is protected from eavesdroppers, but the connection to the
|
||||
usage reporting server itself may expose the client as running Syncthing.
|
||||
.SS Sync Connections (BEP)
|
||||
.sp
|
||||
Sync connections are attempted to all configured devices, when the address is
|
||||
possible to resolve. The sync connection is based on TLS 1.2. The TLS
|
||||
certificates are sent in clear text (as in HTTPS etc), meaning that the
|
||||
certificate Common Name (by default \fBsyncthing\fP) is visible.
|
||||
.sp
|
||||
An eavesdropper can deduce that this is a Syncthing connection and calculate the
|
||||
device ID:s involved based on the hashes of the sent certificates.
|
||||
.sp
|
||||
Likewise, if the sync port (default 22000) is accessible from the internet, a
|
||||
port scanner may discover it, attempt a TLS negotiation and thus obtain the
|
||||
device certificate. This provides the same information as in the eavesdropper
|
||||
case.
|
||||
.SS Web GUI
|
||||
.sp
|
||||
If the web GUI is accessible, it exposes the device as running Syncthing. The
|
||||
web GUI defaults to being reachable from the \fBlocal host only\fP\&.
|
||||
.SH IN SHORT
|
||||
.sp
|
||||
Parties doing surveillance on your network (whether that be corporate IT, the
|
||||
NSA or someone else) will be able to see that you use Syncthing, and your device
|
||||
ID\(aqs \fI\%are OK to share anyway\fP <\fBhttp://docs.syncthing.net/users/faq.html#should-i-keep-my-device-ids-secret\fP>,
|
||||
but the actual transmitted data is protected as well as we can. Knowing your
|
||||
device ID can expose your IP address, using global discovery.
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
199
man/syncthing-stignore.5
Normal file
199
man/syncthing-stignore.5
Normal file
@@ -0,0 +1,199 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-STIGNORE" "5" "June 07, 2015" "v0.11" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
||||
.
|
||||
.nr rst2man-indent-level 0
|
||||
.
|
||||
.de1 rstReportMargin
|
||||
\\$1 \\n[an-margin]
|
||||
level \\n[rst2man-indent-level]
|
||||
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
-
|
||||
\\n[rst2man-indent0]
|
||||
\\n[rst2man-indent1]
|
||||
\\n[rst2man-indent2]
|
||||
..
|
||||
.de1 INDENT
|
||||
.\" .rstReportMargin pre:
|
||||
. RS \\$1
|
||||
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
||||
. nr rst2man-indent-level +1
|
||||
.\" .rstReportMargin post:
|
||||
..
|
||||
.de UNINDENT
|
||||
. RE
|
||||
.\" indent \\n[an-margin]
|
||||
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.nr rst2man-indent-level -1
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.SH SYNOPSIS
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
\&.stignore
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
If some files should not be synchronized to other nodes, a file called
|
||||
\fB\&.stignore\fP can be created containing file patterns to ignore. The
|
||||
\fB\&.stignore\fP file must be placed in the root of the repository. The
|
||||
\fB\&.stignore\fP file itself will never be synced to other nodes, although it can
|
||||
\fB#include\fP files that \fIare\fP synchronized between nodes. All patterns are
|
||||
relative to the repository root.
|
||||
.SH PATTERNS
|
||||
.sp
|
||||
The \fB\&.stignore\fP file contains a list of file or path patterns. The
|
||||
\fIfirst\fP pattern that matches will decide the fate of a given file.
|
||||
.INDENT 0.0
|
||||
.IP \(bu 2
|
||||
Regular file names match themselves, i.e. the pattern \fBfoo\fP matches
|
||||
the files \fBfoo\fP, \fBsubdir/foo\fP as well as any directory named
|
||||
\fBfoo\fP\&. Spaces are treated as regular characters.
|
||||
.IP \(bu 2
|
||||
Asterisk matches zero or more characters in a filename, but does not
|
||||
match the directory separator. \fBte*st\fP matches \fBtest\fP,
|
||||
\fBsubdir/telerest\fP but not \fBtele/rest\fP\&.
|
||||
.IP \(bu 2
|
||||
Double asterisk matches as above, but also directory separators.
|
||||
\fBte**st\fP matches \fBtest\fP, \fBsubdir/telerest\fP and
|
||||
\fBtele/sub/dir/rest\fP\&.
|
||||
.IP \(bu 2
|
||||
Question mark matches a single character that is not the directory
|
||||
separator. \fBte??st\fP matches \fBtebest\fP but not \fBteb/st\fP or
|
||||
\fBtest\fP\&.
|
||||
.IP \(bu 2
|
||||
A pattern beginning with \fB/\fP matches in the current directory only.
|
||||
\fB/foo\fP matches \fBfoo\fP but not \fBsubdir/foo\fP\&.
|
||||
.IP \(bu 2
|
||||
A pattern beginning with \fB#include\fP results in loading patterns
|
||||
from the named file. It is an error for a file to not exist or be
|
||||
included more than once. Note that while this can be used to include
|
||||
patterns from a file in a subdirectory, the patterns themselves are
|
||||
still relative to the repository \fIroot\fP\&. Example:
|
||||
\fB#include more\-patterns.txt\fP\&.
|
||||
.IP \(bu 2
|
||||
A pattern beginning with \fB!\fP negates the pattern: matching files
|
||||
are \fIincluded\fP (that is, \fInot\fP ignored). This can be used to override
|
||||
more general patterns that follow. Note that files in ignored
|
||||
directories can not be re\-included this way. This is due to the fact
|
||||
that syncthing stops scanning when it reaches an ignored directory,
|
||||
so doesn\(aqt know what files it might contain.
|
||||
.IP \(bu 2
|
||||
A pattern beginning with \fB(?i)\fP enables case\-insensitive pattern
|
||||
matching. \fB(?i)test\fP matches \fBtest\fP, \fBTEST\fP and \fBtEsT\fP\&. The
|
||||
\fB(?i)\fP prefix can be combined with other patterns, for example the
|
||||
pattern \fB(?i)!picture*.png\fP indicates that \fBPicture1.PNG\fP should
|
||||
be synchronized. Note that case\-insensitive patterns must start with
|
||||
\fB(?i)\fP when combined with other flags.
|
||||
.IP \(bu 2
|
||||
A line beginning with \fB//\fP is a comment and has no effect.
|
||||
.UNINDENT
|
||||
.SH EXAMPLE
|
||||
.sp
|
||||
Given a directory layout:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
foo
|
||||
foofoo
|
||||
bar/
|
||||
baz
|
||||
quux
|
||||
quuz
|
||||
bar2/
|
||||
baz
|
||||
frobble
|
||||
My Pictures/
|
||||
Img15.PNG
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
and an \fB\&.stignore\fP file with the contents:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
!frobble
|
||||
!quuz
|
||||
foo
|
||||
*2
|
||||
qu*
|
||||
(?i)my pictures
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
all files and directories called "foo", ending in a "2" or starting with
|
||||
"qu" will be ignored. The end result becomes:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
foo # ignored, matches "foo"
|
||||
foofoo # synced, does not match "foo" but would match "foo*" or "*foo"
|
||||
bar/ # synced
|
||||
baz # synced
|
||||
quux # ignored, matches "qu*"
|
||||
quuz # synced, matches "qu*" but is excluded by the preceding "!quuz"
|
||||
bar2/ # ignored, matched "*2"
|
||||
baz # ignored, due to parent being ignored
|
||||
frobble # ignored, due to parent being ignored; "!frobble" doesn\(aqt help
|
||||
My Pictures/ # ignored, matched case insensitive "(?i)my pictures" pattern
|
||||
Img15.PNG # ignored, due to parent being ignored
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
Please note that directory patterns ending with a slash
|
||||
\fBsome/directory/\fP matches the content of the directory, but not the
|
||||
directory itself. If you want the pattern to match the director and it\(aqs
|
||||
content, make sure it does not have a \fB/\fP at the end of the pattern.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH EFFECTS ON "IN SYNC" STATUS
|
||||
.sp
|
||||
Currently the effects on who is in sync with what can be a bit confusing
|
||||
when using ignore patterns. This should be cleared up in a future
|
||||
version...
|
||||
.sp
|
||||
Assume two nodes, Alice and Bob, where Alice has 100 files to share, but
|
||||
Bob ignores 25 of these. From Alice\(aqs point of view Bob will become
|
||||
about 75% in sync (the actual number depends on the sizes of the
|
||||
individual files) and remain in "Syncing" state even though it is in
|
||||
fact not syncing anything (\fI\%issue #623\fP <\fBhttps://github.com/syncthing/syncthing/issues/623\fP>). From
|
||||
Bob\(aqs point of view it\(aqs 100% up to date but will show fewer files in
|
||||
both the local and global view.
|
||||
.sp
|
||||
If Bob adds files that have already been synced to the ignore list, they
|
||||
will remain in the "global" view but disappear from the "local" view.
|
||||
The end result is more files in the global repository than in the local,
|
||||
but still 100% in sync (\fI\%issue #624\fP <\fBhttps://github.com/syncthing/syncthing/issues/624\fP>). From
|
||||
Alice\(aqs point of view, Bob will remain 100% in sync until the next
|
||||
reconnect, because Bob has already announce that he has the files that
|
||||
are now suddenly ignored.
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
.SH COPYRIGHT
|
||||
2015, The Syncthing Authors
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user