mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-08 13:59:18 -05:00
Compare commits
186 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1437952aee | ||
|
|
a162157301 | ||
|
|
6316bf3582 | ||
|
|
1d856b4723 | ||
|
|
7f56d5c23a | ||
|
|
a778763851 | ||
|
|
983d7ec265 | ||
|
|
297769ef57 | ||
|
|
885d050e5f | ||
|
|
8fb4ce6cad | ||
|
|
42738ab54d | ||
|
|
7d1250620e | ||
|
|
5c49b93c67 | ||
|
|
9a11f81fd3 | ||
|
|
cba2e972fd | ||
|
|
76ad925842 | ||
|
|
ef6f52f688 | ||
|
|
197bfa9f11 | ||
|
|
145f8c7435 | ||
|
|
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 | ||
|
|
bcf51aed83 | ||
|
|
1a11ce6211 | ||
|
|
10021c97a6 | ||
|
|
e869e3c534 | ||
|
|
f6416285db | ||
|
|
7f0593cd2d | ||
|
|
e4c41718d8 | ||
|
|
7234553990 | ||
|
|
5761efdb73 | ||
|
|
1133192a86 | ||
|
|
af3288043a | ||
|
|
24a348f6a8 | ||
|
|
5528b6c231 | ||
|
|
245bd1eb17 | ||
|
|
03506db76c | ||
|
|
cb5ef26020 | ||
|
|
2493ae4c2c | ||
|
|
2bd88344ad | ||
|
|
4950980d69 | ||
|
|
2361a0dd6e | ||
|
|
152cdd1750 | ||
|
|
31797a5831 | ||
|
|
9308c42cff | ||
|
|
c096cd34b9 | ||
|
|
5fc0808f28 | ||
|
|
e6866ee980 | ||
|
|
45631d30b0 | ||
|
|
0f432a0844 | ||
|
|
df59bc7194 | ||
|
|
ff4706e450 | ||
|
|
fc013bd04c | ||
|
|
7e29a8b927 | ||
|
|
67ae7a0b6c | ||
|
|
bd5a64bac0 | ||
|
|
1bd85d8baf | ||
|
|
fe34b08ece | ||
|
|
33048f88b8 | ||
|
|
0ec01f4e78 | ||
|
|
d0ebf06ff8 | ||
|
|
687b249034 | ||
|
|
d1528dcff0 | ||
|
|
1c31cf6319 | ||
|
|
67f0c9bef0 | ||
|
|
d54c366150 | ||
|
|
4ff535f883 | ||
|
|
58b15f9452 | ||
|
|
dedca59ac9 | ||
|
|
3cbddfe545 | ||
|
|
e3cae69495 | ||
|
|
aee40316f8 | ||
|
|
d754f9ae89 | ||
|
|
3c50b3a9e0 | ||
|
|
fb312a71f7 | ||
|
|
136d79eaa3 | ||
|
|
834336499a | ||
|
|
c9da8237df | ||
|
|
9638dcda0a | ||
|
|
756c5a2604 | ||
|
|
a9c31652b6 | ||
|
|
32a76901a9 | ||
|
|
a5e11c7489 | ||
|
|
f2b12014e1 | ||
|
|
60fcaebfdb | ||
|
|
32fe2cb659 | ||
|
|
1207d54fdd | ||
|
|
3932884688 | ||
|
|
19a2042746 | ||
|
|
4c6eb137da | ||
|
|
57ec2ff915 | ||
|
|
77a161a087 | ||
|
|
0642402449 | ||
|
|
50d377d9fe | ||
|
|
f5211b0697 | ||
|
|
fd4ea46fd7 | ||
|
|
8d8546868d | ||
|
|
8ce547edeb | ||
|
|
a17c48aed6 | ||
|
|
d12db3e7b8 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,8 +3,6 @@ syncthing.exe
|
||||
*.tar.gz
|
||||
*.zip
|
||||
*.asc
|
||||
*.sublime*
|
||||
.idea/
|
||||
.jshintrc
|
||||
coverage.out
|
||||
files/pidx
|
||||
@@ -12,7 +10,7 @@ bin
|
||||
perfstats*.csv
|
||||
coverage.xml
|
||||
!gui/scripts/syncthing
|
||||
.DS_Store
|
||||
syncthing.md5
|
||||
syncthing.exe.md5
|
||||
RELEASE
|
||||
deb
|
||||
|
||||
3
AUTHORS
3
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>
|
||||
@@ -34,6 +36,7 @@ Johan Vromans <jvromans@squirrel.nl>
|
||||
Karol Różycki <rozycki.karol@gmail.com>
|
||||
Ken'ichi Kamada <kamada@nanohz.org>
|
||||
Lode Hoste <zillode@zillode.be>
|
||||
Lord Landon Agahnim <lordlandon@gmail.com>
|
||||
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
|
||||
Marc Pujol <kilburn@la3.org>
|
||||
|
||||
102
CONTRIBUTING.md
102
CONTRIBUTING.md
@@ -32,64 +32,15 @@ latest info on Transifex.
|
||||
## Contributing Code
|
||||
|
||||
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);
|
||||
it's all in the name of quality. :) Following the points below will make this
|
||||
a smoother process.
|
||||
where to start, any open issues are fair game! See the [Contribution
|
||||
Guidelines](http://docs.syncthing.net/dev/contributing.html) for the full
|
||||
story on committing code.
|
||||
|
||||
Individuals making significant and valuable contributions are given
|
||||
commit-access to the project. If you make a significant contribution and
|
||||
are not considered for commit-access, please contact any of the
|
||||
Syncthing core team members.
|
||||
## Contributing Documentation
|
||||
|
||||
All nontrivial contributions should go through the pull request
|
||||
mechanism for internal review. Determining what is "nontrivial" is left
|
||||
at the discretion of the contributor.
|
||||
|
||||
### Authorship
|
||||
|
||||
All code authors are listed in the AUTHORS file. Commits must be made
|
||||
with the same name and email as listed in the AUTHORS file. To
|
||||
accomplish this, ensure that your git configuration is set correctly
|
||||
prior to making your first commit;
|
||||
|
||||
$ git config --global user.name "Jane Doe"
|
||||
$ git config --global user.email janedoe@example.com
|
||||
|
||||
You must be reachable on the given email address. If you do not wish to
|
||||
use your real name for whatever reason, using a nickname or pseudonym is
|
||||
perfectly acceptable.
|
||||
|
||||
### Core Team
|
||||
|
||||
The Syncthing core team currently consists of the following members;
|
||||
|
||||
- Jakob Borg (@calmh)
|
||||
- Audrius Butkevicius (@AudriusButkevicius)
|
||||
|
||||
## Coding Style
|
||||
|
||||
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
|
||||
as much as makes sense.
|
||||
|
||||
- All text files use Unix line endings.
|
||||
|
||||
- Each commit should be `go fmt` clean.
|
||||
|
||||
- The commit message subject should be a single short sentence
|
||||
describing the change, starting with a capital letter.
|
||||
|
||||
- Commits that resolve an existing issue must include the issue number
|
||||
as `(fixes #123)` at the end of the commit message subject.
|
||||
|
||||
- Imports are grouped per `goimports` standard; that is, standard
|
||||
library first, then third party libraries after a blank line.
|
||||
|
||||
- A contribution solving a single issue or introducing a single new
|
||||
feature should probably be a single commit based on the current
|
||||
`master` branch. You may be asked to "rebase" or "squash" your pull
|
||||
request to make sure this is the case, especially if there have been
|
||||
amendments during review.
|
||||
Updates to the [documentation site](http://docs.syncthing.net/) can be
|
||||
made as pull requests on the [documentation
|
||||
repository](https://github.com/syncthing/docs).
|
||||
|
||||
## Licensing
|
||||
|
||||
@@ -99,42 +50,3 @@ strings which are licensed under the Creative Commons Attribution 4.0
|
||||
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.
|
||||
|
||||
## Building
|
||||
|
||||
[See the documentation](https://github.com/syncthing/syncthing/wiki/Building)
|
||||
on how to get started with a build environment.
|
||||
|
||||
## Branches
|
||||
|
||||
- `master` is the main branch containing good code that will end up in
|
||||
the next release. You should base your work on it. It won't ever be
|
||||
rebased or force-pushed to.
|
||||
|
||||
- `vx.y` branches exist to make patch releases on otherwise obsolete
|
||||
minor releases. Should only contain fixes cherry picked from master.
|
||||
Don't base any work on them.
|
||||
|
||||
- Other branches are probably topic branches and may be subject to
|
||||
rebasing. Don't base any work on them unless you specifically know
|
||||
otherwise.
|
||||
|
||||
## Tags
|
||||
|
||||
All releases are tagged semver style as `vx.y.z`. Release tags are
|
||||
signed by GPG key BCE524C7.
|
||||
|
||||
## Tests
|
||||
|
||||
Yes please!
|
||||
|
||||
## Documentation
|
||||
|
||||
[Over here!](https://github.com/syncthing/syncthing/wiki)
|
||||
|
||||
## License
|
||||
|
||||
MPLv2
|
||||
|
||||
2
Godeps/Godeps.json
generated
2
Godeps/Godeps.json
generated
@@ -11,7 +11,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/logger",
|
||||
"Rev": "f50d32b313bec2933a3e1049f7416a29f3413d29"
|
||||
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/luhn",
|
||||
|
||||
27
Godeps/_workspace/src/github.com/calmh/logger/logger.go
generated
vendored
27
Godeps/_workspace/src/github.com/calmh/logger/logger.go
generated
vendored
@@ -6,6 +6,7 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -16,6 +17,7 @@ type LogLevel int
|
||||
|
||||
const (
|
||||
LevelDebug LogLevel = iota
|
||||
LevelVerbose
|
||||
LevelInfo
|
||||
LevelOK
|
||||
LevelWarn
|
||||
@@ -36,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),
|
||||
}
|
||||
@@ -83,6 +92,24 @@ func (l *Logger) Debugf(format string, vals ...interface{}) {
|
||||
l.callHandlers(LevelDebug, s)
|
||||
}
|
||||
|
||||
// Infoln logs a line with a VERBOSE prefix.
|
||||
func (l *Logger) Verboseln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintln(vals...)
|
||||
l.logger.Output(2, "VERBOSE: "+s)
|
||||
l.callHandlers(LevelVerbose, s)
|
||||
}
|
||||
|
||||
// Infof logs a formatted line with a VERBOSE prefix.
|
||||
func (l *Logger) Verbosef(format string, vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
defer l.mut.Unlock()
|
||||
s := fmt.Sprintf(format, vals...)
|
||||
l.logger.Output(2, "VERBOSE: "+s)
|
||||
l.callHandlers(LevelVerbose, s)
|
||||
}
|
||||
|
||||
// Infoln logs a line with an INFO prefix.
|
||||
func (l *Logger) Infoln(vals ...interface{}) {
|
||||
l.mut.Lock()
|
||||
|
||||
3
NICKS
3
NICKS
@@ -3,6 +3,7 @@
|
||||
AudriusButkevicius <audrius.butkevicius@gmail.com>
|
||||
Cathryne <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
|
||||
KayoticSully <kayoticsully@gmail.com>
|
||||
LordLandon <lordlandon@gmail.com>
|
||||
Moter8 <moter8@gmail.com>
|
||||
Nutomic <me@nutomic.com>
|
||||
Rewt0r <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||
@@ -17,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>
|
||||
@@ -34,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>
|
||||
|
||||
19
README.md
19
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,
|
||||
@@ -15,19 +15,19 @@ This is the `syncthing` project which pursues the following goals:
|
||||
|
||||
2. Provide the reference implementation to demonstrate the usability of
|
||||
said protocol. This is the `syncthing` utility. We hope that
|
||||
alternative, compatible implementations of the protocol will arrise.
|
||||
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/).
|
||||
|
||||
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()
|
||||
}
|
||||
160
build.go
160
build.go
@@ -78,6 +78,11 @@ func main() {
|
||||
tags = []string{"noupgrade"}
|
||||
}
|
||||
install("./cmd/...", tags)
|
||||
|
||||
vet("./cmd/syncthing")
|
||||
vet("./internal/...")
|
||||
lint("./cmd/syncthing")
|
||||
lint("./internal/...")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -103,8 +108,10 @@ func main() {
|
||||
build(pkg, tags)
|
||||
|
||||
case "test":
|
||||
pkg := "./..."
|
||||
test(pkg)
|
||||
test("./...")
|
||||
|
||||
case "bench":
|
||||
bench("./...")
|
||||
|
||||
case "assets":
|
||||
assets()
|
||||
@@ -127,9 +134,20 @@ func main() {
|
||||
case "zip":
|
||||
buildZip()
|
||||
|
||||
case "deb":
|
||||
buildDeb()
|
||||
|
||||
case "clean":
|
||||
clean()
|
||||
|
||||
case "vet":
|
||||
vet("./cmd/syncthing")
|
||||
vet("./internal/...")
|
||||
|
||||
case "lint":
|
||||
lint("./cmd/syncthing")
|
||||
lint("./internal/...")
|
||||
|
||||
default:
|
||||
log.Fatalf("Unknown command %q", cmd)
|
||||
}
|
||||
@@ -167,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()}
|
||||
@@ -260,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 {
|
||||
@@ -437,10 +549,7 @@ func run(cmd string, args ...string) []byte {
|
||||
func runError(cmd string, args ...string) ([]byte, error) {
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
bs, err := ecmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.TrimSpace(bs), nil
|
||||
return bytes.TrimSpace(bs), err
|
||||
}
|
||||
|
||||
func runPrint(cmd string, args ...string) {
|
||||
@@ -471,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) {
|
||||
@@ -615,3 +725,37 @@ func md5File(file string) error {
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func vet(pkg string) {
|
||||
bs, err := runError("go", "vet", pkg)
|
||||
if err != nil && err.Error() == "exit status 3" || bytes.Contains(bs, []byte("no such tool \"vet\"")) {
|
||||
// Go said there is no go vet
|
||||
log.Println(`- No go vet, no vetting. Try "go get -u golang.org/x/tools/cmd/vet".`)
|
||||
return
|
||||
}
|
||||
|
||||
falseAlarmComposites := regexp.MustCompile("composite literal uses unkeyed fields")
|
||||
exitStatus := regexp.MustCompile("exit status 1")
|
||||
for _, line := range bytes.Split(bs, []byte("\n")) {
|
||||
if falseAlarmComposites.Match(line) || exitStatus.Match(line) {
|
||||
continue
|
||||
}
|
||||
log.Printf("%s", line)
|
||||
}
|
||||
}
|
||||
|
||||
func lint(pkg string) {
|
||||
bs, err := runError("golint", pkg)
|
||||
if err != nil {
|
||||
log.Println(`- No golint, not linting. Try "go get -u github.com/golang/lint/golint".`)
|
||||
return
|
||||
}
|
||||
|
||||
analCommentPolicy := regexp.MustCompile(`exported (function|method|const|type|var) [^\s]+ should have comment`)
|
||||
for _, line := range bytes.Split(bs, []byte("\n")) {
|
||||
if analCommentPolicy.Match(line) {
|
||||
continue
|
||||
}
|
||||
log.Printf("%s", line)
|
||||
}
|
||||
}
|
||||
|
||||
41
build.sh
41
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,10 +127,9 @@ case "${1:-default}" in
|
||||
-e "STTRACE=$STTRACE" \
|
||||
syncthing/build:latest \
|
||||
sh -c './build.sh clean \
|
||||
&& go tool vet -composites=false cmd/*/*.go internal/*/*.go \
|
||||
&& ( golint ./cmd/... ; golint ./internal/... ) | egrep -v "comment on exported|should have comment" \
|
||||
&& ./build.sh all \
|
||||
&& STTRACE=all ./build.sh test-cov'
|
||||
&& ./build.sh test-cov \
|
||||
&& ./build.sh bench \
|
||||
&& ./build.sh all'
|
||||
;;
|
||||
|
||||
docker-test)
|
||||
@@ -138,6 +146,25 @@ case "${1:-default}" in
|
||||
&& git clean -fxd .'
|
||||
;;
|
||||
|
||||
docker-lint)
|
||||
docker run --rm -h syncthing-builder -u $(id -u) -t \
|
||||
-v $(pwd):/go/src/github.com/syncthing/syncthing \
|
||||
-w /go/src/github.com/syncthing/syncthing \
|
||||
-e "STTRACE=$STTRACE" \
|
||||
syncthing/build:latest \
|
||||
sh -euxc 'go run build.go lint'
|
||||
;;
|
||||
|
||||
|
||||
docker-vet)
|
||||
docker run --rm -h syncthing-builder -u $(id -u) -t \
|
||||
-v $(pwd):/go/src/github.com/syncthing/syncthing \
|
||||
-w /go/src/github.com/syncthing/syncthing \
|
||||
-e "STTRACE=$STTRACE" \
|
||||
syncthing/build:latest \
|
||||
sh -euxc 'go run build.go vet'
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown build command $1"
|
||||
;;
|
||||
|
||||
@@ -17,7 +17,10 @@ no-docs-typos() {
|
||||
grep -v a9339d0627fff439879d157c75077f02c9fac61b |\
|
||||
grep -v 254c63763a3ad42fd82259f1767db526cff94a14 |\
|
||||
grep -v 4b76ec40c07078beaa2c5e250ed7d9bd6276a718 |\
|
||||
grep -v ffc39dfbcb34eacc3ea12327a02b6e7741a2c207
|
||||
grep -v ffc39dfbcb34eacc3ea12327a02b6e7741a2c207 |\
|
||||
grep -v 32a76901a91ff0f663db6f0830e0aedec946e4d0 |\
|
||||
grep -v af3288043a49bcc28f8ae3060852a09de552fe5f |\
|
||||
grep -v 3626003f680bad3e63677982576d3a05421e88e9
|
||||
}
|
||||
|
||||
print-missing-authors() {
|
||||
@@ -27,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)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -15,41 +16,71 @@ import (
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
folder := flag.String("folder", "default", "Folder ID")
|
||||
device := flag.String("device", "", "Device ID (blank for global)")
|
||||
flag.Parse()
|
||||
|
||||
ldb, err := leveldb.OpenFile(flag.Arg(0), nil)
|
||||
ldb, err := leveldb.OpenFile(flag.Arg(0), &opt.Options{
|
||||
ErrorIfMissing: true,
|
||||
Strict: opt.StrictAll,
|
||||
OpenFilesCacheCapacity: 100,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fs := db.NewFileSet(*folder, ldb)
|
||||
it := ldb.NewIterator(nil, nil)
|
||||
var dev protocol.DeviceID
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
case db.KeyTypeDevice:
|
||||
folder := nulString(key[1 : 1+64])
|
||||
devBytes := key[1+64 : 1+64+32]
|
||||
name := nulString(key[1+64+32:])
|
||||
copy(dev[:], devBytes)
|
||||
fmt.Printf("[device] F:%q N:%q D:%v\n", folder, name, dev)
|
||||
|
||||
if *device == "" {
|
||||
log.Printf("*** Global index for folder %q", *folder)
|
||||
fs.WithGlobalTruncated(func(fi db.FileIntf) bool {
|
||||
f := fi.(db.FileInfoTruncated)
|
||||
fmt.Println(f)
|
||||
fmt.Println("\t", fs.Availability(f.Name))
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
n, err := protocol.DeviceIDFromString(*device)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
var f protocol.FileInfo
|
||||
err := f.UnmarshalXDR(it.Value())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf(" N:%q\n F:%#o\n M:%d\n V:%v\n S:%d\n B:%d\n", f.Name, f.Flags, f.Modified, f.Version, f.Size(), len(f.Blocks))
|
||||
|
||||
case db.KeyTypeGlobal:
|
||||
folder := nulString(key[1 : 1+64])
|
||||
name := nulString(key[1+64:])
|
||||
fmt.Printf("[global] F:%q N:%q V:%x\n", folder, name, it.Value())
|
||||
|
||||
case db.KeyTypeBlock:
|
||||
folder := nulString(key[1 : 1+64])
|
||||
hash := key[1+64 : 1+64+32]
|
||||
name := nulString(key[1+64+32:])
|
||||
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\n", it.Key(), it.Value())
|
||||
|
||||
case db.KeyTypeFolderStatistic:
|
||||
fmt.Printf("[fstat]\n %x\n %x\n", it.Key(), it.Value())
|
||||
|
||||
default:
|
||||
fmt.Printf("[???]\n %x\n %x\n", it.Key(), it.Value())
|
||||
}
|
||||
log.Printf("*** Have index for folder %q device %q", *folder, n)
|
||||
fs.WithHaveTruncated(n, func(fi db.FileIntf) bool {
|
||||
f := fi.(db.FileInfoTruncated)
|
||||
fmt.Println(f)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func nulString(bs []byte) string {
|
||||
for i := range bs {
|
||||
if bs[i] == 0 {
|
||||
return string(bs[:i])
|
||||
}
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// The connection service listens on TLS and dials configured unconnected
|
||||
// devices. Successfull connections are handed to the model.
|
||||
// devices. Successful connections are handed to the model.
|
||||
type connectionSvc struct {
|
||||
*suture.Supervisor
|
||||
cfg *config.Wrapper
|
||||
@@ -116,7 +116,7 @@ next:
|
||||
remoteID := protocol.NewDeviceID(remoteCert.Raw)
|
||||
|
||||
// The device ID should not be that of ourselves. It can happen
|
||||
// though, especially in the presense of NAT hairpinning, multiple
|
||||
// though, especially in the presence of NAT hairpinning, multiple
|
||||
// clients between the same NAT gateway, and global discovery.
|
||||
if remoteID == myID {
|
||||
l.Infof("Connected to myself (%s) - should not happen", remoteID)
|
||||
@@ -128,7 +128,7 @@ next:
|
||||
// could use some better handling. If the old connection is dead but
|
||||
// hasn't timed out yet we may want to drop *that* connection and keep
|
||||
// this one. But in case we are two devices connecting to each other
|
||||
// in parallell we don't want to do that or we end up with no
|
||||
// in parallel we don't want to do that or we end up with no
|
||||
// connections still established...
|
||||
if s.model.ConnectedTo(remoteID) {
|
||||
l.Infof("Connected to already connected device (%s)", remoteID)
|
||||
@@ -353,3 +353,24 @@ func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
|
||||
}
|
||||
return !tcpaddr.IP.IsLoopback()
|
||||
}
|
||||
|
||||
func (s *connectionSvc) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// We require a restart if a device as been removed.
|
||||
|
||||
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
|
||||
for _, dev := range to.Devices {
|
||||
newDevices[dev.DeviceID] = true
|
||||
}
|
||||
|
||||
for _, dev := range from.Devices {
|
||||
if !newDevices[dev.DeviceID] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
|
||||
debugHTTP = strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all"
|
||||
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
|
||||
debugHTTP = strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all"
|
||||
debugSuture = strings.Contains(os.Getenv("STTRACE"), "suture") || os.Getenv("STTRACE") == "all"
|
||||
)
|
||||
|
||||
@@ -45,25 +45,37 @@ type guiError struct {
|
||||
}
|
||||
|
||||
var (
|
||||
configInSync = true
|
||||
guiErrors = []guiError{}
|
||||
guiErrorsMut sync.Mutex = sync.NewMutex()
|
||||
startTime = time.Now()
|
||||
configInSync = true
|
||||
guiErrors = []guiError{}
|
||||
guiErrorsMut = sync.NewMutex()
|
||||
startTime = time.Now()
|
||||
eventSub *events.BufferedSubscription
|
||||
)
|
||||
|
||||
var (
|
||||
lastEventRequest time.Time
|
||||
lastEventRequestMut sync.Mutex = sync.NewMutex()
|
||||
)
|
||||
type apiSvc struct {
|
||||
cfg config.GUIConfiguration
|
||||
assetDir string
|
||||
model *model.Model
|
||||
listener net.Listener
|
||||
fss *folderSummarySvc
|
||||
stop chan struct{}
|
||||
systemConfigMut sync.Mutex
|
||||
}
|
||||
|
||||
func newAPISvc(cfg config.GUIConfiguration, assetDir string, m *model.Model) (*apiSvc, error) {
|
||||
svc := &apiSvc{
|
||||
cfg: cfg,
|
||||
assetDir: assetDir,
|
||||
model: m,
|
||||
systemConfigMut: sync.NewMutex(),
|
||||
}
|
||||
|
||||
func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) error {
|
||||
var err error
|
||||
svc.listener, err = svc.getListener(cfg)
|
||||
return svc, err
|
||||
}
|
||||
|
||||
l.AddHandler(logger.LevelWarn, showGuiError)
|
||||
sub := events.Default.Subscribe(events.AllEvents)
|
||||
eventSub = events.NewBufferedSubscription(sub, 1000)
|
||||
|
||||
func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error) {
|
||||
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
|
||||
if err != nil {
|
||||
l.Infoln("Loading HTTPS certificate:", err)
|
||||
@@ -80,7 +92,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
cert, err = newCertificate(locations[locHTTPSCertFile], locations[locHTTPSKeyFile], name)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
tlsCfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
@@ -102,53 +114,64 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
|
||||
rawListener, err := net.Listen("tcp", cfg.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listener := &DowngradingListener{rawListener, tlsCfg}
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func (s *apiSvc) Serve() {
|
||||
s.stop = make(chan struct{})
|
||||
|
||||
l.AddHandler(logger.LevelWarn, s.showGuiError)
|
||||
sub := events.Default.Subscribe(events.AllEvents)
|
||||
eventSub = events.NewBufferedSubscription(sub, 1000)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
|
||||
// The GET handlers
|
||||
getRestMux := http.NewServeMux()
|
||||
getRestMux.HandleFunc("/rest/db/completion", withModel(m, restGetDBCompletion)) // device folder
|
||||
getRestMux.HandleFunc("/rest/db/file", withModel(m, restGetDBFile)) // folder file
|
||||
getRestMux.HandleFunc("/rest/db/ignores", withModel(m, restGetDBIgnores)) // folder
|
||||
getRestMux.HandleFunc("/rest/db/need", withModel(m, restGetDBNeed)) // folder [perpage] [page]
|
||||
getRestMux.HandleFunc("/rest/db/status", withModel(m, restGetDBStatus)) // folder
|
||||
getRestMux.HandleFunc("/rest/db/browse", withModel(m, restGetDBBrowse)) // folder [prefix] [dirsonly] [levels]
|
||||
getRestMux.HandleFunc("/rest/events", restGetEvents) // since [limit]
|
||||
getRestMux.HandleFunc("/rest/stats/device", withModel(m, restGetDeviceStats)) // -
|
||||
getRestMux.HandleFunc("/rest/stats/folder", withModel(m, restGetFolderStats)) // -
|
||||
getRestMux.HandleFunc("/rest/svc/deviceid", restGetDeviceID) // id
|
||||
getRestMux.HandleFunc("/rest/svc/lang", restGetLang) // -
|
||||
getRestMux.HandleFunc("/rest/svc/report", withModel(m, restGetReport)) // -
|
||||
getRestMux.HandleFunc("/rest/system/browse", restGetSystemBrowse) // current
|
||||
getRestMux.HandleFunc("/rest/system/config", restGetSystemConfig) // -
|
||||
getRestMux.HandleFunc("/rest/system/config/insync", RestGetSystemConfigInsync) // -
|
||||
getRestMux.HandleFunc("/rest/system/connections", withModel(m, restGetSystemConnections)) // -
|
||||
getRestMux.HandleFunc("/rest/system/discovery", restGetSystemDiscovery) // -
|
||||
getRestMux.HandleFunc("/rest/system/error", restGetSystemError) // -
|
||||
getRestMux.HandleFunc("/rest/system/ping", restPing) // -
|
||||
getRestMux.HandleFunc("/rest/system/status", restGetSystemStatus) // -
|
||||
getRestMux.HandleFunc("/rest/system/upgrade", restGetSystemUpgrade) // -
|
||||
getRestMux.HandleFunc("/rest/system/version", restGetSystemVersion) // -
|
||||
getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
|
||||
getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
|
||||
getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
|
||||
getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
|
||||
getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
|
||||
getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
|
||||
getRestMux.HandleFunc("/rest/events", s.getEvents) // since [limit]
|
||||
getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
|
||||
getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // -
|
||||
getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
|
||||
getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
|
||||
getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
|
||||
getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
|
||||
getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
|
||||
getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
|
||||
getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // -
|
||||
getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // -
|
||||
getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // -
|
||||
getRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
|
||||
getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
|
||||
getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
|
||||
getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
|
||||
|
||||
// The POST handlers
|
||||
postRestMux := http.NewServeMux()
|
||||
postRestMux.HandleFunc("/rest/db/prio", withModel(m, restPostDBPrio)) // folder file [perpage] [page]
|
||||
postRestMux.HandleFunc("/rest/db/ignores", withModel(m, restPostDBIgnores)) // folder
|
||||
postRestMux.HandleFunc("/rest/db/override", withModel(m, restPostDBOverride)) // folder
|
||||
postRestMux.HandleFunc("/rest/db/scan", withModel(m, restPostDBScan)) // folder [sub...]
|
||||
postRestMux.HandleFunc("/rest/system/config", withModel(m, restPostSystemConfig)) // <body>
|
||||
postRestMux.HandleFunc("/rest/system/discovery", restPostSystemDiscovery) // device addr
|
||||
postRestMux.HandleFunc("/rest/system/error", restPostSystemError) // <body>
|
||||
postRestMux.HandleFunc("/rest/system/error/clear", restPostSystemErrorClear) // -
|
||||
postRestMux.HandleFunc("/rest/system/ping", restPing) // -
|
||||
postRestMux.HandleFunc("/rest/system/reset", withModel(m, restPostSystemReset)) // [folder]
|
||||
postRestMux.HandleFunc("/rest/system/restart", restPostSystemRestart) // -
|
||||
postRestMux.HandleFunc("/rest/system/shutdown", restPostSystemShutdown) // -
|
||||
postRestMux.HandleFunc("/rest/system/upgrade", restPostSystemUpgrade) // -
|
||||
postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
|
||||
postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
|
||||
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
|
||||
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
|
||||
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
|
||||
postRestMux.HandleFunc("/rest/system/discovery", s.postSystemDiscovery) // device addr
|
||||
postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
|
||||
postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
|
||||
postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
|
||||
postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
|
||||
postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
|
||||
postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
|
||||
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
|
||||
|
||||
// Debug endpoints, not for general use
|
||||
getRestMux.HandleFunc("/rest/debug/peerCompletion", withModel(m, restGetPeerCompletion))
|
||||
getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
|
||||
|
||||
// A handler that splits requests between the two above and disables
|
||||
// caching
|
||||
@@ -157,25 +180,28 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
// The main routing handler
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/rest/", restMux)
|
||||
mux.HandleFunc("/qr/", getQR)
|
||||
mux.HandleFunc("/qr/", s.getQR)
|
||||
|
||||
// Serve compiled in assets unless an asset directory was set (for development)
|
||||
mux.Handle("/", embeddedStatic(assetDir))
|
||||
mux.Handle("/", embeddedStatic{
|
||||
assetDir: s.assetDir,
|
||||
assets: auto.Assets(),
|
||||
})
|
||||
|
||||
// Wrap everything in CSRF protection. The /rest prefix should be
|
||||
// protected, other requests will grant cookies.
|
||||
handler := csrfMiddleware("/rest", cfg.APIKey, mux)
|
||||
handler := csrfMiddleware("/rest", s.cfg.APIKey, mux)
|
||||
|
||||
// Add our version as a header to responses
|
||||
handler = withVersionMiddleware(handler)
|
||||
|
||||
// Wrap everything in basic auth, if user/password is set.
|
||||
if len(cfg.User) > 0 && len(cfg.Password) > 0 {
|
||||
handler = basicAuthAndSessionMiddleware(cfg, handler)
|
||||
if len(s.cfg.User) > 0 && len(s.cfg.Password) > 0 {
|
||||
handler = basicAuthAndSessionMiddleware(s.cfg, handler)
|
||||
}
|
||||
|
||||
// Redirect to HTTPS if we are supposed to
|
||||
if cfg.UseTLS {
|
||||
if s.cfg.UseTLS {
|
||||
handler = redirectToHTTPSMiddleware(handler)
|
||||
}
|
||||
|
||||
@@ -188,18 +214,65 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
ReadTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
csrv := &folderSummarySvc{model: m}
|
||||
go csrv.Serve()
|
||||
s.fss = newFolderSummarySvc(s.model)
|
||||
defer s.fss.Stop()
|
||||
s.fss.ServeBackground()
|
||||
|
||||
go func() {
|
||||
err := srv.Serve(listener)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
l.Infoln("API listening on", s.listener.Addr())
|
||||
err := srv.Serve(s.listener)
|
||||
|
||||
// The return could be due to an intentional close. Wait for the stop
|
||||
// signal before returning. IF there is no stop signal within a second, we
|
||||
// assume it was unintentional and log the error before retrying.
|
||||
select {
|
||||
case <-s.stop:
|
||||
case <-time.After(time.Second):
|
||||
l.Warnln("API:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiSvc) Stop() {
|
||||
close(s.stop)
|
||||
s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *apiSvc) String() string {
|
||||
return fmt.Sprintf("apiSvc@%p", s)
|
||||
}
|
||||
|
||||
func (s *apiSvc) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *apiSvc) CommitConfiguration(from, to config.Configuration) bool {
|
||||
if to.GUI == from.GUI {
|
||||
return true
|
||||
}
|
||||
|
||||
// Order here is important. We must close the listener to stop Serve(). We
|
||||
// must create a new listener before Serve() starts again. We can't create
|
||||
// a new listener on the same port before the previous listener is closed.
|
||||
// To assist in this little dance the Serve() method will wait for a
|
||||
// signal on the stop channel after the listener has closed.
|
||||
|
||||
s.listener.Close()
|
||||
|
||||
var err error
|
||||
s.listener, err = s.getListener(to.GUI)
|
||||
if err != nil {
|
||||
// Ideally this should be a verification error, but we check it by
|
||||
// creating a new listener which requires shutting down the previous
|
||||
// one first, which is too destructive for the VerifyConfiguration
|
||||
// method.
|
||||
return false
|
||||
}
|
||||
s.cfg = to.GUI
|
||||
|
||||
close(s.stop)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getPostHandler(get, post http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
@@ -256,7 +329,9 @@ func redirectToHTTPSMiddleware(h http.Handler) http.Handler {
|
||||
|
||||
func noCacheMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
|
||||
w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -268,20 +343,14 @@ func withVersionMiddleware(h http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func withModel(m *model.Model, h func(m *model.Model, w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h(m, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func restPing(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) restPing(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"ping": "pong",
|
||||
})
|
||||
}
|
||||
|
||||
func restGetSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"version": Version,
|
||||
@@ -291,7 +360,7 @@ func restGetSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func restGetDBBrowse(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
prefix := qs.Get("prefix")
|
||||
@@ -304,12 +373,12 @@ func restGetDBBrowse(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
tree := m.GlobalDirectoryTree(folder, prefix, levels, dirsonly)
|
||||
tree := s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly)
|
||||
|
||||
json.NewEncoder(w).Encode(tree)
|
||||
}
|
||||
|
||||
func restGetDBCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var folder = qs.Get("folder")
|
||||
var deviceStr = qs.Get("device")
|
||||
@@ -321,17 +390,17 @@ func restGetDBCompletion(m *model.Model, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
res := map[string]float64{
|
||||
"completion": m.Completion(device, folder),
|
||||
"completion": s.model.Completion(device, folder),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetDBStatus(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBStatus(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
res := folderSummary(m, folder)
|
||||
res := folderSummary(s.model, folder)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
@@ -372,13 +441,13 @@ func folderSummary(m *model.Model, folder string) map[string]interface{} {
|
||||
return res
|
||||
}
|
||||
|
||||
func restPostDBOverride(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postDBOverride(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var folder = qs.Get("folder")
|
||||
go m.Override(folder)
|
||||
go s.model.Override(folder)
|
||||
}
|
||||
|
||||
func restGetDBNeed(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBNeed(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
folder := qs.Get("folder")
|
||||
@@ -392,13 +461,13 @@ func restGetDBNeed(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
perpage = 1 << 16
|
||||
}
|
||||
|
||||
progress, queued, rest, total := m.NeedFolderFiles(folder, page, perpage)
|
||||
progress, queued, rest, total := s.model.NeedFolderFiles(folder, page, perpage)
|
||||
|
||||
// Convert the struct to a more loose structure, and inject the size.
|
||||
output := map[string]interface{}{
|
||||
"progress": toNeedSlice(progress),
|
||||
"queued": toNeedSlice(queued),
|
||||
"rest": toNeedSlice(rest),
|
||||
"progress": s.toNeedSlice(progress),
|
||||
"queued": s.toNeedSlice(queued),
|
||||
"rest": s.toNeedSlice(rest),
|
||||
"total": total,
|
||||
"page": page,
|
||||
"perpage": perpage,
|
||||
@@ -408,32 +477,32 @@ func restGetDBNeed(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func restGetSystemConnections(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
var res = m.ConnectionStats()
|
||||
func (s *apiSvc) getSystemConnections(w http.ResponseWriter, r *http.Request) {
|
||||
var res = s.model.ConnectionStats()
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetDeviceStats(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
var res = m.DeviceStatistics()
|
||||
func (s *apiSvc) getDeviceStats(w http.ResponseWriter, r *http.Request) {
|
||||
var res = s.model.DeviceStatistics()
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetFolderStats(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
var res = m.FolderStatistics()
|
||||
func (s *apiSvc) getFolderStats(w http.ResponseWriter, r *http.Request) {
|
||||
var res = s.model.FolderStatistics()
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetDBFile(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
file := qs.Get("file")
|
||||
gf, _ := m.CurrentGlobalFile(folder, file)
|
||||
lf, _ := m.CurrentFolderFile(folder, file)
|
||||
gf, _ := s.model.CurrentGlobalFile(folder, file)
|
||||
lf, _ := s.model.CurrentFolderFile(folder, file)
|
||||
|
||||
av := m.Availability(folder, file)
|
||||
av := s.model.Availability(folder, file)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"global": jsonFileInfo(gf),
|
||||
"local": jsonFileInfo(lf),
|
||||
@@ -441,104 +510,101 @@ func restGetDBFile(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func restGetSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(cfg.Raw())
|
||||
}
|
||||
|
||||
func restPostSystemConfig(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
var newCfg config.Configuration
|
||||
err := json.NewDecoder(r.Body).Decode(&newCfg)
|
||||
func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
s.systemConfigMut.Lock()
|
||||
defer s.systemConfigMut.Unlock()
|
||||
|
||||
var to config.Configuration
|
||||
err := json.NewDecoder(r.Body).Decode(&to)
|
||||
if err != nil {
|
||||
l.Warnln("decoding posted config:", err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
if newCfg.GUI.Password != cfg.GUI().Password {
|
||||
if newCfg.GUI.Password != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(newCfg.GUI.Password), 0)
|
||||
if to.GUI.Password != cfg.GUI().Password {
|
||||
if to.GUI.Password != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
|
||||
if err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
newCfg.GUI.Password = string(hash)
|
||||
to.GUI.Password = string(hash)
|
||||
}
|
||||
}
|
||||
|
||||
// Start or stop usage reporting as appropriate
|
||||
// Fixup usage reporting settings
|
||||
|
||||
if curAcc := cfg.Options().URAccepted; newCfg.Options.URAccepted > curAcc {
|
||||
if curAcc := cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
|
||||
// UR was enabled
|
||||
newCfg.Options.URAccepted = usageReportVersion
|
||||
newCfg.Options.URUniqueID = randomString(8)
|
||||
err := sendUsageReport(m)
|
||||
if err != nil {
|
||||
l.Infoln("Usage report:", err)
|
||||
}
|
||||
go usageReportingLoop(m)
|
||||
} else if newCfg.Options.URAccepted < curAcc {
|
||||
to.Options.URAccepted = usageReportVersion
|
||||
to.Options.URUniqueID = randomString(8)
|
||||
} else if to.Options.URAccepted < curAcc {
|
||||
// UR was disabled
|
||||
newCfg.Options.URAccepted = -1
|
||||
newCfg.Options.URUniqueID = ""
|
||||
stopUsageReporting()
|
||||
to.Options.URAccepted = -1
|
||||
to.Options.URUniqueID = ""
|
||||
}
|
||||
|
||||
// Activate and save
|
||||
|
||||
configInSync = !config.ChangeRequiresRestart(cfg.Raw(), newCfg)
|
||||
cfg.Replace(newCfg)
|
||||
resp := cfg.Replace(to)
|
||||
configInSync = !resp.RequiresRestart
|
||||
cfg.Save()
|
||||
}
|
||||
|
||||
func RestGetSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]bool{"configInSync": configInSync})
|
||||
}
|
||||
|
||||
func restPostSystemRestart(w http.ResponseWriter, r *http.Request) {
|
||||
flushResponse(`{"ok": "restarting"}`, w)
|
||||
func (s *apiSvc) postSystemRestart(w http.ResponseWriter, r *http.Request) {
|
||||
s.flushResponse(`{"ok": "restarting"}`, w)
|
||||
go restart()
|
||||
}
|
||||
|
||||
func restPostSystemReset(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
var err error
|
||||
if len(folder) == 0 {
|
||||
err = resetDB()
|
||||
} else {
|
||||
err = m.ResetFolder(folder)
|
||||
err = s.model.ResetFolder(folder)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if len(folder) == 0 {
|
||||
flushResponse(`{"ok": "resetting database"}`, w)
|
||||
s.flushResponse(`{"ok": "resetting database"}`, w)
|
||||
} else {
|
||||
flushResponse(`{"ok": "resetting folder " + folder}`, w)
|
||||
s.flushResponse(`{"ok": "resetting folder " + folder}`, w)
|
||||
}
|
||||
go restart()
|
||||
}
|
||||
|
||||
func restPostSystemShutdown(w http.ResponseWriter, r *http.Request) {
|
||||
flushResponse(`{"ok": "shutting down"}`, w)
|
||||
func (s *apiSvc) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
|
||||
s.flushResponse(`{"ok": "shutting down"}`, w)
|
||||
go shutdown()
|
||||
}
|
||||
|
||||
func flushResponse(s string, w http.ResponseWriter) {
|
||||
w.Write([]byte(s + "\n"))
|
||||
func (s *apiSvc) flushResponse(resp string, w http.ResponseWriter) {
|
||||
w.Write([]byte(resp + "\n"))
|
||||
f := w.(http.Flusher)
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
var cpuUsagePercent [10]float64 // The last ten seconds
|
||||
var cpuUsageLock sync.RWMutex = sync.NewRWMutex()
|
||||
var cpuUsageLock = sync.NewRWMutex()
|
||||
|
||||
func restGetSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
@@ -566,26 +632,26 @@ func restGetSystemStatus(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
guiErrorsMut.Lock()
|
||||
json.NewEncoder(w).Encode(map[string][]guiError{"errors": guiErrors})
|
||||
guiErrorsMut.Unlock()
|
||||
}
|
||||
|
||||
func restPostSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
bs, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
showGuiError(0, string(bs))
|
||||
s.showGuiError(0, string(bs))
|
||||
}
|
||||
|
||||
func restPostSystemErrorClear(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
|
||||
guiErrorsMut.Lock()
|
||||
guiErrors = []guiError{}
|
||||
guiErrorsMut.Unlock()
|
||||
}
|
||||
|
||||
func showGuiError(l logger.LogLevel, err string) {
|
||||
func (s *apiSvc) showGuiError(l logger.LogLevel, err string) {
|
||||
guiErrorsMut.Lock()
|
||||
guiErrors = append(guiErrors, guiError{time.Now(), err})
|
||||
if len(guiErrors) > 5 {
|
||||
@@ -594,7 +660,7 @@ func showGuiError(l logger.LogLevel, err string) {
|
||||
guiErrorsMut.Unlock()
|
||||
}
|
||||
|
||||
func restPostSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var device = qs.Get("device")
|
||||
var addr = qs.Get("addr")
|
||||
@@ -603,7 +669,7 @@ func restPostSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func restGetSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
devices := map[string][]discover.CacheEntry{}
|
||||
|
||||
@@ -619,16 +685,16 @@ func restGetSystemDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(devices)
|
||||
}
|
||||
|
||||
func restGetReport(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getReport(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(reportData(m))
|
||||
json.NewEncoder(w).Encode(reportData(s.model))
|
||||
}
|
||||
|
||||
func restGetDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
ignores, patterns, err := m.GetIgnores(qs.Get("folder"))
|
||||
ignores, patterns, err := s.model.GetIgnores(qs.Get("folder"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
@@ -640,7 +706,7 @@ func restGetDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func restPostDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
var data map[string][]string
|
||||
@@ -652,25 +718,23 @@ func restPostDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = m.SetIgnores(qs.Get("folder"), data["ignore"])
|
||||
err = s.model.SetIgnores(qs.Get("folder"), data["ignore"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
restGetDBIgnores(m, w, r)
|
||||
s.getDBIgnores(w, r)
|
||||
}
|
||||
|
||||
func restGetEvents(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getEvents(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
sinceStr := qs.Get("since")
|
||||
limitStr := qs.Get("limit")
|
||||
since, _ := strconv.Atoi(sinceStr)
|
||||
limit, _ := strconv.Atoi(limitStr)
|
||||
|
||||
lastEventRequestMut.Lock()
|
||||
lastEventRequest = time.Now()
|
||||
lastEventRequestMut.Unlock()
|
||||
s.fss.gotEventRequest()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
@@ -687,7 +751,7 @@ func restGetEvents(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(evs)
|
||||
}
|
||||
|
||||
func restGetSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
if noUpgrade {
|
||||
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
|
||||
return
|
||||
@@ -707,7 +771,7 @@ func restGetSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetDeviceID(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getDeviceID(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
idStr := qs.Get("id")
|
||||
id, err := protocol.DeviceIDFromString(idStr)
|
||||
@@ -723,7 +787,7 @@ func restGetDeviceID(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func restGetLang(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getLang(w http.ResponseWriter, r *http.Request) {
|
||||
lang := r.Header.Get("Accept-Language")
|
||||
var langs []string
|
||||
for _, l := range strings.Split(lang, ",") {
|
||||
@@ -734,7 +798,7 @@ func restGetLang(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(langs)
|
||||
}
|
||||
|
||||
func restPostSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
rel, err := upgrade.LatestRelease(Version)
|
||||
if err != nil {
|
||||
l.Warnln("getting latest release:", err)
|
||||
@@ -750,39 +814,47 @@ func restPostSystemUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
flushResponse(`{"ok": "restarting"}`, w)
|
||||
s.flushResponse(`{"ok": "restarting"}`, w)
|
||||
l.Infoln("Upgrading")
|
||||
stop <- exitUpgrading
|
||||
}
|
||||
}
|
||||
|
||||
func restPostDBScan(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postDBScan(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
if folder != "" {
|
||||
nextStr := qs.Get("next")
|
||||
next, err := strconv.Atoi(nextStr)
|
||||
if err == nil {
|
||||
s.model.DelayScan(folder, time.Duration(next)*time.Second)
|
||||
}
|
||||
|
||||
subs := qs["sub"]
|
||||
err := m.ScanFolderSubs(folder, subs)
|
||||
err = s.model.ScanFolderSubs(folder, subs)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
errors := m.ScanFolders()
|
||||
errors := s.model.ScanFolders()
|
||||
if len(errors) > 0 {
|
||||
http.Error(w, "Error scanning folders", 500)
|
||||
json.NewEncoder(w).Encode(errors)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restPostDBPrio(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) postDBPrio(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
folder := qs.Get("folder")
|
||||
file := qs.Get("file")
|
||||
m.BringToFront(folder, file)
|
||||
restGetDBNeed(m, w, r)
|
||||
s.model.BringToFront(folder, file)
|
||||
s.getDBNeed(w, r)
|
||||
}
|
||||
|
||||
func getQR(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getQR(w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var text = qs.Get("text")
|
||||
code, err := qr.Encode(text, qr.M)
|
||||
@@ -795,15 +867,15 @@ func getQR(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(code.PNG())
|
||||
}
|
||||
|
||||
func restGetPeerCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
|
||||
tot := map[string]float64{}
|
||||
count := map[string]float64{}
|
||||
|
||||
for _, folder := range cfg.Folders() {
|
||||
for _, device := range folder.DeviceIDs() {
|
||||
deviceStr := device.String()
|
||||
if m.ConnectedTo(device) {
|
||||
tot[deviceStr] += m.Completion(device, folder.ID)
|
||||
if s.model.ConnectedTo(device) {
|
||||
tot[deviceStr] += s.model.Completion(device, folder.ID)
|
||||
} else {
|
||||
tot[deviceStr] = 0
|
||||
}
|
||||
@@ -820,7 +892,7 @@ func restGetPeerCompletion(m *model.Model, w http.ResponseWriter, r *http.Reques
|
||||
json.NewEncoder(w).Encode(comp)
|
||||
}
|
||||
|
||||
func restGetSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *apiSvc) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
qs := r.URL.Query()
|
||||
current := qs.Get("current")
|
||||
@@ -829,7 +901,7 @@ func restGetSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(current, pathSeparator) && !strings.HasSuffix(search, pathSeparator) {
|
||||
search = search + pathSeparator
|
||||
}
|
||||
subdirectories, _ := filepath.Glob(search + "*")
|
||||
subdirectories, _ := osutil.Glob(search + "*")
|
||||
ret := make([]string, 0, 10)
|
||||
for _, subdirectory := range subdirectories {
|
||||
info, err := os.Stat(subdirectory)
|
||||
@@ -843,56 +915,57 @@ func restGetSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(ret)
|
||||
}
|
||||
|
||||
func embeddedStatic(assetDir string) http.Handler {
|
||||
assets := auto.Assets()
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
file := r.URL.Path
|
||||
|
||||
if file[0] == '/' {
|
||||
file = file[1:]
|
||||
}
|
||||
|
||||
if len(file) == 0 {
|
||||
file = "index.html"
|
||||
}
|
||||
|
||||
if assetDir != "" {
|
||||
p := filepath.Join(assetDir, filepath.FromSlash(file))
|
||||
_, err := os.Stat(p)
|
||||
if err == nil {
|
||||
http.ServeFile(w, r, p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bs, ok := assets[file]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
mtype := mimeTypeForFile(file)
|
||||
if len(mtype) != 0 {
|
||||
w.Header().Set("Content-Type", mtype)
|
||||
}
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
} else {
|
||||
// ungzip if browser not send gzip accepted header
|
||||
var gr *gzip.Reader
|
||||
gr, _ = gzip.NewReader(bytes.NewReader(bs))
|
||||
bs, _ = ioutil.ReadAll(gr)
|
||||
gr.Close()
|
||||
}
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
|
||||
w.Header().Set("Last-Modified", auto.AssetsBuildDate)
|
||||
|
||||
w.Write(bs)
|
||||
})
|
||||
type embeddedStatic struct {
|
||||
assetDir string
|
||||
assets map[string][]byte
|
||||
}
|
||||
|
||||
func mimeTypeForFile(file string) string {
|
||||
func (s embeddedStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
file := r.URL.Path
|
||||
|
||||
if file[0] == '/' {
|
||||
file = file[1:]
|
||||
}
|
||||
|
||||
if len(file) == 0 {
|
||||
file = "index.html"
|
||||
}
|
||||
|
||||
if s.assetDir != "" {
|
||||
p := filepath.Join(s.assetDir, filepath.FromSlash(file))
|
||||
_, err := os.Stat(p)
|
||||
if err == nil {
|
||||
http.ServeFile(w, r, p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bs, ok := s.assets[file]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
mtype := s.mimeTypeForFile(file)
|
||||
if len(mtype) != 0 {
|
||||
w.Header().Set("Content-Type", mtype)
|
||||
}
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
} else {
|
||||
// ungzip if browser not send gzip accepted header
|
||||
var gr *gzip.Reader
|
||||
gr, _ = gzip.NewReader(bytes.NewReader(bs))
|
||||
bs, _ = ioutil.ReadAll(gr)
|
||||
gr.Close()
|
||||
}
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
|
||||
w.Header().Set("Last-Modified", auto.AssetsBuildDate)
|
||||
|
||||
w.Write(bs)
|
||||
}
|
||||
|
||||
func (s embeddedStatic) mimeTypeForFile(file string) string {
|
||||
// We use a built in table of the common types since the system
|
||||
// TypeByExtension might be unreliable. But if we don't know, we delegate
|
||||
// to the system.
|
||||
@@ -919,7 +992,7 @@ func mimeTypeForFile(file string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
|
||||
func (s *apiSvc) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
|
||||
res := make([]jsonDBFileInfo, len(fs))
|
||||
for i, f := range fs {
|
||||
res[i] = jsonDBFileInfo(f)
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
sessions = make(map[string]bool)
|
||||
sessionsMut sync.Mutex = sync.NewMutex()
|
||||
sessions = make(map[string]bool)
|
||||
sessionsMut = sync.NewMutex()
|
||||
)
|
||||
|
||||
func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
var csrfTokens []string
|
||||
var csrfMut sync.Mutex = sync.NewMutex()
|
||||
var csrfMut = sync.NewMutex()
|
||||
|
||||
// Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
|
||||
// the request with 403. For / and /index.html, set a new CSRF cookie if none
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -35,7 +36,6 @@ import (
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"github.com/syncthing/syncthing/internal/symlinks"
|
||||
"github.com/syncthing/syncthing/internal/upgrade"
|
||||
"github.com/syncthing/syncthing/internal/upnp"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
@@ -109,8 +109,6 @@ var (
|
||||
readRateLimit *ratelimit.Bucket
|
||||
stop = make(chan int)
|
||||
discoverer *discover.Discoverer
|
||||
externalPort int
|
||||
igd *upnp.IGD
|
||||
cert tls.Certificate
|
||||
lans []*net.IPNet
|
||||
)
|
||||
@@ -157,6 +155,7 @@ are mostly useful for developers. Use with care.
|
||||
- "model" (the model package)
|
||||
- "scanner" (the scanner package)
|
||||
- "stats" (the stats package)
|
||||
- "suture" (the suture package; service management)
|
||||
- "upnp" (the upnp package)
|
||||
- "xdr" (the xdr package)
|
||||
- "all" (all of the above)
|
||||
@@ -197,6 +196,7 @@ var (
|
||||
generateDir string
|
||||
logFile string
|
||||
auditEnabled bool
|
||||
verbose bool
|
||||
noRestart = os.Getenv("STNORESTART") != ""
|
||||
noUpgrade = os.Getenv("STNOUPGRADE") != ""
|
||||
guiAddress = os.Getenv("STGUIADDRESS") // legacy
|
||||
@@ -234,6 +234,7 @@ func main() {
|
||||
flag.BoolVar(&showVersion, "version", false, "Show version")
|
||||
flag.StringVar(&upgradeTo, "upgrade-to", upgradeTo, "Force upgrade directly from specified URL")
|
||||
flag.BoolVar(&auditEnabled, "audit", false, "Write events to audit file")
|
||||
flag.BoolVar(&verbose, "verbose", false, "Print verbose log output")
|
||||
|
||||
flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, baseDirs["config"]))
|
||||
flag.Parse()
|
||||
@@ -279,7 +280,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)
|
||||
}
|
||||
@@ -350,7 +351,13 @@ func main() {
|
||||
// Use leveldb database locks to protect against concurrent upgrades
|
||||
_, err = leveldb.OpenFile(locations[locDatabase], &opt.Options{OpenFilesCacheCapacity: 100})
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot upgrade, database seems to be locked. Is another copy of Syncthing already running?")
|
||||
l.Infoln("Attempting upgrade through running Syncthing...")
|
||||
err = upgradeViaRest()
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err)
|
||||
}
|
||||
l.Okln("Syncthing upgrading")
|
||||
return
|
||||
}
|
||||
|
||||
err = upgrade.To(rel)
|
||||
@@ -375,13 +382,51 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func upgradeViaRest() error {
|
||||
cfg, err := config.Load(locations[locConfigFile], protocol.LocalDeviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target := cfg.GUI().Address
|
||||
if cfg.GUI().UseTLS {
|
||||
target = "https://" + target
|
||||
} else {
|
||||
target = "http://" + target
|
||||
}
|
||||
r, _ := http.NewRequest("POST", target+"/rest/system/upgrade", nil)
|
||||
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: 60 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(string(bs))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func syncthingMain() {
|
||||
// Create a main service manager. We'll add things to this as we go along.
|
||||
// We want any logging it does to go through our log system, with INFO
|
||||
// severity.
|
||||
// We want any logging it does to go through our log system.
|
||||
mainSvc := suture.New("main", suture.Spec{
|
||||
Log: func(line string) {
|
||||
l.Infoln(line)
|
||||
if debugSuture {
|
||||
l.Debugln(line)
|
||||
}
|
||||
},
|
||||
})
|
||||
mainSvc.ServeBackground()
|
||||
@@ -394,6 +439,10 @@ func syncthingMain() {
|
||||
startAuditing(mainSvc)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
mainSvc.Add(newVerboseSvc())
|
||||
}
|
||||
|
||||
if len(os.Getenv("GOMAXPROCS")) == 0 {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
@@ -521,10 +570,9 @@ func syncthingMain() {
|
||||
}
|
||||
|
||||
dbFile := locations[locDatabase]
|
||||
dbOpts := &opt.Options{OpenFilesCacheCapacity: 100}
|
||||
ldb, err := leveldb.OpenFile(dbFile, dbOpts)
|
||||
ldb, err := leveldb.OpenFile(dbFile, dbOpts())
|
||||
if err != nil && errors.IsCorrupted(err) {
|
||||
ldb, err = leveldb.RecoverFile(dbFile, dbOpts)
|
||||
ldb, err = leveldb.RecoverFile(dbFile, dbOpts())
|
||||
}
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
|
||||
@@ -540,6 +588,8 @@ func syncthingMain() {
|
||||
}
|
||||
|
||||
m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb)
|
||||
cfg.Subscribe(m)
|
||||
mainSvc.Add(m)
|
||||
|
||||
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
|
||||
it, err := strconv.Atoi(t)
|
||||
@@ -550,10 +600,6 @@ func syncthingMain() {
|
||||
m.StartDeadlockDetector(20 * 60 * time.Second)
|
||||
}
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(cfg, m)
|
||||
|
||||
// Clear out old indexes for other devices. Otherwise we'll start up and
|
||||
// start needing a bunch of files which are nowhere to be found. This
|
||||
// needs to be changed when we correctly do persistent indexes.
|
||||
@@ -565,41 +611,45 @@ func syncthingMain() {
|
||||
}
|
||||
m.Index(device, folderCfg.ID, nil, 0, nil)
|
||||
}
|
||||
// Routine to pull blocks from other devices to synchronize the local
|
||||
// folder. Does not run when we are in read only (publish only) mode.
|
||||
if folderCfg.ReadOnly {
|
||||
l.Okf("Ready to synchronize %s (read only; no external updates accepted)", folderCfg.ID)
|
||||
m.StartFolderRO(folderCfg.ID)
|
||||
} else {
|
||||
l.Okf("Ready to synchronize %s (read-write)", folderCfg.ID)
|
||||
m.StartFolderRW(folderCfg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(mainSvc, cfg, m)
|
||||
|
||||
// The default port we announce, possibly modified by setupUPnP next.
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", opts.ListenAddress[0])
|
||||
if err != nil {
|
||||
l.Fatalln("Bad listen address:", err)
|
||||
}
|
||||
externalPort = addr.Port
|
||||
|
||||
// UPnP
|
||||
igd = nil
|
||||
// Start discovery
|
||||
|
||||
localPort := addr.Port
|
||||
discoverer = discovery(localPort)
|
||||
|
||||
// Start UPnP. The UPnP service will restart global discovery if the
|
||||
// external port changes.
|
||||
|
||||
if opts.UPnPEnabled {
|
||||
setupUPnP()
|
||||
upnpSvc := newUPnPSvc(cfg, localPort)
|
||||
mainSvc.Add(upnpSvc)
|
||||
}
|
||||
|
||||
// Routine to connect out to configured devices
|
||||
discoverer = discovery(externalPort)
|
||||
|
||||
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
|
||||
cfg.Subscribe(connectionSvc)
|
||||
mainSvc.Add(connectionSvc)
|
||||
|
||||
for _, folder := range cfg.Folders() {
|
||||
// Routine to pull blocks from other devices to synchronize the local
|
||||
// folder. Does not run when we are in read only (publish only) mode.
|
||||
if folder.ReadOnly {
|
||||
l.Okf("Ready to synchronize %s (read only; no external updates accepted)", folder.ID)
|
||||
m.StartFolderRO(folder.ID)
|
||||
} else {
|
||||
l.Okf("Ready to synchronize %s (read-write)", folder.ID)
|
||||
m.StartFolderRW(folder.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if cpuProfile {
|
||||
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
|
||||
if err != nil {
|
||||
@@ -629,16 +679,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()
|
||||
}
|
||||
@@ -666,6 +713,40 @@ func syncthingMain() {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func dbOpts() *opt.Options {
|
||||
// Calculate a suitable database block cache capacity.
|
||||
|
||||
// Default is 8 MiB.
|
||||
blockCacheCapacity := 8 << 20
|
||||
// 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")
|
||||
}
|
||||
|
||||
return &opt.Options{
|
||||
OpenFilesCacheCapacity: 100,
|
||||
BlockCacheCapacity: blockCacheCapacity,
|
||||
WriteBuffer: 4 << 20,
|
||||
}
|
||||
}
|
||||
|
||||
func startAuditing(mainSvc *suture.Supervisor) {
|
||||
auditFile := timestampedLoc(locAuditLog)
|
||||
fd, err := os.OpenFile(auditFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
@@ -683,7 +764,7 @@ func startAuditing(mainSvc *suture.Supervisor) {
|
||||
l.Infoln("Audit log in", auditFile)
|
||||
}
|
||||
|
||||
func setupGUI(cfg *config.Wrapper, m *model.Model) {
|
||||
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model) {
|
||||
opts := cfg.Options()
|
||||
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
|
||||
|
||||
@@ -712,10 +793,13 @@ func setupGUI(cfg *config.Wrapper, m *model.Model) {
|
||||
|
||||
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
|
||||
l.Infoln("Starting web GUI on", urlShow)
|
||||
err := startGUI(guiCfg, guiAssets, m)
|
||||
api, err := newAPISvc(guiCfg, guiAssets, m)
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot start GUI:", err)
|
||||
}
|
||||
cfg.Subscribe(api)
|
||||
mainSvc.Add(api)
|
||||
|
||||
if opts.StartBrowser && !noBrowser && !stRestarting {
|
||||
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
|
||||
// Can potentially block if the utility we are invoking doesn't
|
||||
@@ -765,110 +849,6 @@ func generatePingEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
func setupUPnP() {
|
||||
if opts := cfg.Options(); len(opts.ListenAddress) == 1 {
|
||||
_, portStr, err := net.SplitHostPort(opts.ListenAddress[0])
|
||||
if err != nil {
|
||||
l.Warnln("Bad listen address:", err)
|
||||
} else {
|
||||
// Set up incoming port forwarding, if necessary and possible
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
igds := upnp.Discover(time.Duration(cfg.Options().UPnPTimeoutS) * time.Second)
|
||||
if len(igds) > 0 {
|
||||
// Configure the first discovered IGD only. This is a work-around until we have a better mechanism
|
||||
// for handling multiple IGDs, which will require changes to the global discovery service
|
||||
igd = &igds[0]
|
||||
|
||||
externalPort = setupExternalPort(igd, port)
|
||||
if externalPort == 0 {
|
||||
l.Warnln("Failed to create UPnP port mapping")
|
||||
} else {
|
||||
l.Infof("Created UPnP port mapping for external port %d on UPnP device %s.", externalPort, igd.FriendlyIdentifier())
|
||||
|
||||
if opts.UPnPRenewalM > 0 {
|
||||
go renewUPnP(port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
l.Warnln("Multiple listening addresses; not attempting UPnP port mapping")
|
||||
}
|
||||
}
|
||||
|
||||
func setupExternalPort(igd *upnp.IGD, port int) int {
|
||||
if igd == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
r := 1024 + predictableRandom.Intn(65535-1024)
|
||||
err := igd.AddPortMapping(upnp.TCP, r, port, fmt.Sprintf("syncthing-%d", r), cfg.Options().UPnPLeaseM*60)
|
||||
if err == nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func renewUPnP(port int) {
|
||||
for {
|
||||
opts := cfg.Options()
|
||||
time.Sleep(time.Duration(opts.UPnPRenewalM) * time.Minute)
|
||||
// Some values might have changed while we were sleeping
|
||||
opts = cfg.Options()
|
||||
|
||||
// Make sure our IGD reference isn't nil
|
||||
if igd == nil {
|
||||
if debugNet {
|
||||
l.Debugln("Undefined IGD during UPnP port renewal. Re-discovering...")
|
||||
}
|
||||
igds := upnp.Discover(time.Duration(opts.UPnPTimeoutS) * time.Second)
|
||||
if len(igds) > 0 {
|
||||
// Configure the first discovered IGD only. This is a work-around until we have a better mechanism
|
||||
// for handling multiple IGDs, which will require changes to the global discovery service
|
||||
igd = &igds[0]
|
||||
} else {
|
||||
if debugNet {
|
||||
l.Debugln("Failed to discover IGD during UPnP port mapping renewal.")
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Just renew the same port that we already have
|
||||
if externalPort != 0 {
|
||||
err := igd.AddPortMapping(upnp.TCP, externalPort, port, "syncthing", opts.UPnPLeaseM*60)
|
||||
if err != nil {
|
||||
l.Warnf("Error renewing UPnP port mapping for external port %d on device %s: %s", externalPort, igd.FriendlyIdentifier(), err.Error())
|
||||
} else if debugNet {
|
||||
l.Debugf("Renewed UPnP port mapping for external port %d on device %s.", externalPort, igd.FriendlyIdentifier())
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Something strange has happened. We didn't have an external port before?
|
||||
// Or perhaps the gateway has changed?
|
||||
// Retry the same port sequence from the beginning.
|
||||
if debugNet {
|
||||
l.Debugln("No UPnP port mapping defined, updating...")
|
||||
}
|
||||
|
||||
forwardedPort := setupExternalPort(igd, port)
|
||||
if forwardedPort != 0 {
|
||||
externalPort = forwardedPort
|
||||
discoverer.StopGlobal()
|
||||
discoverer.StartGlobal(opts.GlobalAnnServers, uint16(forwardedPort))
|
||||
if debugNet {
|
||||
l.Debugf("Updated UPnP port mapping for external port %d on device %s.", forwardedPort, igd.FriendlyIdentifier())
|
||||
}
|
||||
} else {
|
||||
l.Warnf("Failed to update UPnP port mapping for external port on device " + igd.FriendlyIdentifier() + ".")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resetDB() error {
|
||||
return os.RemoveAll(locations[locDatabase])
|
||||
}
|
||||
@@ -903,7 +883,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)
|
||||
}
|
||||
@@ -1061,7 +1041,7 @@ func cleanConfigDirectory() {
|
||||
|
||||
for pat, dur := range patterns {
|
||||
pat = filepath.Join(baseDirs["config"], pat)
|
||||
files, err := filepath.Glob(pat)
|
||||
files, err := osutil.Glob(pat)
|
||||
if err != nil {
|
||||
l.Infoln("Cleaning:", err)
|
||||
continue
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
stdoutFirstLines []string // The first 10 lines of stdout
|
||||
stdoutLastLines []string // The last 50 lines of stdout
|
||||
stdoutMut sync.Mutex = sync.NewMutex()
|
||||
stdoutFirstLines []string // The first 10 lines of stdout
|
||||
stdoutLastLines []string // The last 50 lines of stdout
|
||||
stdoutMut = sync.NewMutex()
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -106,12 +106,24 @@ func monitorMain() {
|
||||
stdoutLastLines = make([]string, 0, 50)
|
||||
stdoutMut.Unlock()
|
||||
|
||||
go copyStderr(stderr, dst)
|
||||
go copyStdout(stdout, dst)
|
||||
wg := sync.NewWaitGroup()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
copyStderr(stderr, dst)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
copyStdout(stdout, dst)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
exit := make(chan error)
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
exit <- cmd.Wait()
|
||||
}()
|
||||
|
||||
@@ -124,7 +136,7 @@ func monitorMain() {
|
||||
|
||||
case err = <-exit:
|
||||
if err == nil {
|
||||
// Successfull exit indicates an intentional shutdown
|
||||
// Successful exit indicates an intentional shutdown
|
||||
return
|
||||
} else if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
@@ -149,7 +161,7 @@ func monitorMain() {
|
||||
}
|
||||
}
|
||||
|
||||
func copyStderr(stderr io.ReadCloser, dst io.Writer) {
|
||||
func copyStderr(stderr io.Reader, dst io.Writer) {
|
||||
br := bufio.NewReader(stderr)
|
||||
|
||||
var panicFd *os.File
|
||||
@@ -192,7 +204,7 @@ func copyStderr(stderr io.ReadCloser, dst io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
func copyStdout(stdout io.ReadCloser, dst io.Writer) {
|
||||
func copyStdout(stdout io.Reader, dst io.Writer) {
|
||||
br := bufio.NewReader(stdout)
|
||||
for {
|
||||
line, err := br.ReadString('\n')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,35 +18,40 @@ import (
|
||||
// The folderSummarySvc adds summary information events (FolderSummary and
|
||||
// FolderCompletion) into the event stream at certain intervals.
|
||||
type folderSummarySvc struct {
|
||||
*suture.Supervisor
|
||||
|
||||
model *model.Model
|
||||
srv suture.Service
|
||||
stop chan struct{}
|
||||
immediate chan string
|
||||
|
||||
// For keeping track of folders to recalculate for
|
||||
foldersMut sync.Mutex
|
||||
folders map[string]struct{}
|
||||
|
||||
// For keeping track of when the last event request on the API was
|
||||
lastEventReq time.Time
|
||||
lastEventReqMut sync.Mutex
|
||||
}
|
||||
|
||||
func (c *folderSummarySvc) Serve() {
|
||||
srv := suture.NewSimple("folderSummarySvc")
|
||||
srv.Add(serviceFunc(c.listenForUpdates))
|
||||
srv.Add(serviceFunc(c.calculateSummaries))
|
||||
func newFolderSummarySvc(m *model.Model) *folderSummarySvc {
|
||||
svc := &folderSummarySvc{
|
||||
Supervisor: suture.NewSimple("folderSummarySvc"),
|
||||
model: m,
|
||||
stop: make(chan struct{}),
|
||||
immediate: make(chan string),
|
||||
folders: make(map[string]struct{}),
|
||||
foldersMut: sync.NewMutex(),
|
||||
lastEventReqMut: sync.NewMutex(),
|
||||
}
|
||||
|
||||
c.immediate = make(chan string)
|
||||
c.stop = make(chan struct{})
|
||||
c.folders = make(map[string]struct{})
|
||||
c.srv = srv
|
||||
c.foldersMut = sync.NewMutex()
|
||||
svc.Add(serviceFunc(svc.listenForUpdates))
|
||||
svc.Add(serviceFunc(svc.calculateSummaries))
|
||||
|
||||
srv.Serve()
|
||||
return svc
|
||||
}
|
||||
|
||||
func (c *folderSummarySvc) Stop() {
|
||||
// c.srv.Stop() is mostly a no-op here, but we need to call it anyway so
|
||||
// c.srv doesn't try to restart the serviceFuncs when they exit after we
|
||||
// close the stop channel.
|
||||
c.srv.Stop()
|
||||
c.Supervisor.Stop()
|
||||
close(c.stop)
|
||||
}
|
||||
|
||||
@@ -132,16 +137,13 @@ func (c *folderSummarySvc) calculateSummaries() {
|
||||
// foldersToHandle returns the list of folders needing a summary update, and
|
||||
// clears the list.
|
||||
func (c *folderSummarySvc) foldersToHandle() []string {
|
||||
// We only recalculate sumamries if someone is listening to events
|
||||
// We only recalculate summaries if someone is listening to events
|
||||
// (a request to /rest/events has been made within the last
|
||||
// pingEventInterval).
|
||||
|
||||
lastEventRequestMut.Lock()
|
||||
// XXX: Reaching out to a global var here is very ugly :( Should
|
||||
// we make the gui stuff a proper object with methods on it that
|
||||
// we can query about this kind of thing?
|
||||
last := lastEventRequest
|
||||
lastEventRequestMut.Unlock()
|
||||
c.lastEventReqMut.Lock()
|
||||
last := c.lastEventReq
|
||||
c.lastEventReqMut.Unlock()
|
||||
if time.Since(last) > pingEventInterval {
|
||||
return nil
|
||||
}
|
||||
@@ -187,6 +189,12 @@ func (c *folderSummarySvc) sendSummary(folder string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *folderSummarySvc) gotEventRequest() {
|
||||
c.lastEventReqMut.Lock()
|
||||
c.lastEventReq = time.Now()
|
||||
c.lastEventReqMut.Unlock()
|
||||
}
|
||||
|
||||
// serviceFunc wraps a function to create a suture.Service without stop
|
||||
// functionality.
|
||||
type serviceFunc func()
|
||||
|
||||
120
cmd/syncthing/upnpsvc.go
Normal file
120
cmd/syncthing/upnpsvc.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
"github.com/syncthing/syncthing/internal/upnp"
|
||||
)
|
||||
|
||||
// The UPnP service runs a loop for discovery of IGDs (Internet Gateway
|
||||
// Devices) and setup/renewal of a port mapping.
|
||||
type upnpSvc struct {
|
||||
cfg *config.Wrapper
|
||||
localPort int
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func newUPnPSvc(cfg *config.Wrapper, localPort int) *upnpSvc {
|
||||
return &upnpSvc{
|
||||
cfg: cfg,
|
||||
localPort: localPort,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *upnpSvc) Serve() {
|
||||
extPort := 0
|
||||
foundIGD := true
|
||||
s.stop = make(chan struct{})
|
||||
|
||||
for {
|
||||
igds := upnp.Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
|
||||
if len(igds) > 0 {
|
||||
foundIGD = true
|
||||
extPort = s.tryIGDs(igds, extPort)
|
||||
} else if foundIGD {
|
||||
// Only print a notice if we've previously found an IGD or this is
|
||||
// the first time around.
|
||||
foundIGD = false
|
||||
l.Infof("No UPnP device detected")
|
||||
}
|
||||
|
||||
d := time.Duration(s.cfg.Options().UPnPRenewalM) * time.Minute
|
||||
if d == 0 {
|
||||
// We always want to do renewal so lets just pick a nice sane number.
|
||||
d = 30 * time.Minute
|
||||
}
|
||||
|
||||
select {
|
||||
case <-s.stop:
|
||||
return
|
||||
case <-time.After(d):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *upnpSvc) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
|
||||
// Lets try all the IGDs we found and use the first one that works.
|
||||
// TODO: Use all of them, and sort out the resulting mess to the
|
||||
// discovery announcement code...
|
||||
for _, igd := range igds {
|
||||
extPort, err := s.tryIGD(igd, prevExtPort)
|
||||
if err != nil {
|
||||
l.Warnf("Failed to set UPnP port mapping: external port %d on device %s.", extPort, igd.FriendlyIdentifier())
|
||||
continue
|
||||
}
|
||||
|
||||
if extPort != prevExtPort {
|
||||
// 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)
|
||||
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())
|
||||
}
|
||||
return extPort
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *upnpSvc) tryIGD(igd upnp.IGD, suggestedPort int) (int, error) {
|
||||
var err error
|
||||
leaseTime := s.cfg.Options().UPnPLeaseM * 60
|
||||
|
||||
if suggestedPort != 0 {
|
||||
// First try renewing our existing mapping.
|
||||
name := fmt.Sprintf("syncthing-%d", suggestedPort)
|
||||
err = igd.AddPortMapping(upnp.TCP, suggestedPort, s.localPort, name, leaseTime)
|
||||
if err == nil {
|
||||
return suggestedPort, nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
// Then try up to ten random ports.
|
||||
extPort := 1024 + predictableRandom.Intn(65535-1024)
|
||||
name := fmt.Sprintf("syncthing-%d", extPort)
|
||||
err = igd.AddPortMapping(upnp.TCP, extPort, s.localPort, name, leaseTime)
|
||||
if err == nil {
|
||||
return extPort, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
@@ -11,12 +11,15 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"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 +27,54 @@ 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.CommitConfiguration(config.Configuration{}, cfg.Raw())
|
||||
|
||||
// Listen to future config changes so that we can start and stop as
|
||||
// appropriate.
|
||||
cfg.Subscribe(mgr)
|
||||
|
||||
return mgr
|
||||
}
|
||||
|
||||
func (m *usageReportingManager) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *usageReportingManager) CommitConfiguration(from, to config.Configuration) bool {
|
||||
if to.Options.URAccepted >= usageReportVersion && m.sup == nil {
|
||||
// Usage reporting was turned on; lets start it.
|
||||
svc := &usageReportingService{
|
||||
model: m.model,
|
||||
}
|
||||
m.sup = suture.NewSimple("usageReporting")
|
||||
m.sup.Add(svc)
|
||||
m.sup.ServeBackground()
|
||||
} else if to.Options.URAccepted < usageReportVersion && m.sup != nil {
|
||||
// Usage reporting was turned off
|
||||
m.sup.Stop()
|
||||
m.sup = nil
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *usageReportingManager) String() string {
|
||||
return fmt.Sprintf("usageReportingManager@%p", m)
|
||||
}
|
||||
|
||||
// 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 +124,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 +148,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()
|
||||
|
||||
129
cmd/syncthing/verbose.go
Normal file
129
cmd/syncthing/verbose.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
)
|
||||
|
||||
// The verbose logging service subscribes to events and prints these in
|
||||
// verbose format to the console using INFO level.
|
||||
type verboseSvc struct {
|
||||
stop chan struct{} // signals time to stop
|
||||
started chan struct{} // signals startup complete
|
||||
}
|
||||
|
||||
func newVerboseSvc() *verboseSvc {
|
||||
return &verboseSvc{
|
||||
stop: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Serve runs the verbose logging service.
|
||||
func (s *verboseSvc) Serve() {
|
||||
sub := events.Default.Subscribe(events.AllEvents)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
|
||||
// We're ready to start processing events.
|
||||
close(s.started)
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-sub.C():
|
||||
formatted := s.formatEvent(ev)
|
||||
if formatted != "" {
|
||||
l.Verboseln(formatted)
|
||||
}
|
||||
case <-s.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the verbose logging service.
|
||||
func (s *verboseSvc) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
// WaitForStart returns once the verbose logging service is ready to receive
|
||||
// events, or immediately if it's already running.
|
||||
func (s *verboseSvc) WaitForStart() {
|
||||
<-s.started
|
||||
}
|
||||
|
||||
func (s *verboseSvc) formatEvent(ev events.Event) string {
|
||||
switch ev.Type {
|
||||
case events.Ping, events.DownloadProgress:
|
||||
// Skip
|
||||
return ""
|
||||
|
||||
case events.Starting:
|
||||
return fmt.Sprintf("Starting up (%s)", ev.Data.(map[string]string)["home"])
|
||||
case events.StartupComplete:
|
||||
return "Startup complete"
|
||||
|
||||
case events.DeviceDiscovered:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Discovered device %v at %v", data["device"], data["addrs"])
|
||||
case events.DeviceConnected:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Connected to device %v at %v", data["id"], data["addr"])
|
||||
case events.DeviceDisconnected:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Disconnected from device %v", data["id"])
|
||||
|
||||
case events.StateChanged:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Folder %q is now %v", data["folder"], data["to"])
|
||||
|
||||
case events.RemoteIndexUpdated:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Device %v sent an index update for %q with %d items", data["device"], data["folder"], data["items"])
|
||||
case events.LocalIndexUpdated:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Updated index for folder %q with %v items", data["folder"], data["items"])
|
||||
|
||||
case events.DeviceRejected:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Rejected connection from device %v at %v", data["device"], data["address"])
|
||||
case events.FolderRejected:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Rejected unshared folder %q from device %v", data["folder"], data["device"])
|
||||
|
||||
case events.ItemStarted:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
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, ok := data["error"].(*string); ok && 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....
|
||||
return fmt.Sprintf("Finished syncing %q / %q (%v %v): %v", data["folder"], data["item"], data["action"], data["type"], *err)
|
||||
}
|
||||
return fmt.Sprintf("Finished syncing %q / %q (%v %v): Success", data["folder"], data["item"], data["action"], data["type"])
|
||||
|
||||
case events.ConfigSaved:
|
||||
return "Configuration was saved"
|
||||
|
||||
case events.FolderCompletion:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Completion for folder %q on device %v is %v%%", data["folder"], data["device"], data["completion"])
|
||||
case events.FolderSummary:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
sum := data["summary"].(map[string]interface{})
|
||||
delete(sum, "invalid")
|
||||
delete(sum, "ignorePatterns")
|
||||
delete(sum, "stateChanged")
|
||||
return fmt.Sprintf("Summary for folder %q is %v", data["folder"], data["summary"])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %#v", ev.Type, ev)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
This directory contains a configuration for running syncthing under the
|
||||
# Systemd Configuration
|
||||
|
||||
This directory contains configuration files for running syncthing under the
|
||||
"systemd" service manager on Linux both under either a systemd system service or
|
||||
systemd user service.
|
||||
systemd user service. For further documentation take a look at the [systemd
|
||||
section][1] on the Github Wiki.
|
||||
|
||||
1. Install systemd.
|
||||
|
||||
2. If you are running this as a system level service:
|
||||
|
||||
1. Create the user you will be running the service as (foo in this example).
|
||||
|
||||
2. Copy the syncthing@.service files to /etc/systemd/system
|
||||
|
||||
3. Enable and start the service
|
||||
systemctl enable syncthing@foo.service
|
||||
systemctl start syncthing@foo.service
|
||||
|
||||
3. If you are running this as a user level service:
|
||||
|
||||
1. Log in as the user you will be running the service as
|
||||
|
||||
2. Copy the syncthing.service files to /etc/systemd/user
|
||||
|
||||
3. Enable and start the service
|
||||
systemctl --user enable syncthing.service
|
||||
systemctl --user start syncthing.service
|
||||
|
||||
Log output is sent to the journal.
|
||||
[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,172 +0,0 @@
|
||||
{
|
||||
"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?",
|
||||
"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 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": "Трымаць вэрсій",
|
||||
"Last File Received": "Апошні атрыманы файл",
|
||||
"Last seen": "Апошні раз бачылі",
|
||||
"Later": "Later",
|
||||
"Local Discovery": "Лякальнае вызначэньне",
|
||||
"Local State": "Лякальны стан",
|
||||
"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",
|
||||
"No": "Не",
|
||||
"No File Versioning": "Не захоўваць вэрсіі",
|
||||
"Notice": "Notice",
|
||||
"OK": "Добра",
|
||||
"Off": "Off",
|
||||
"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 wait": "Please wait",
|
||||
"Preview": "Preview",
|
||||
"Preview Usage Report": "Preview Usage Report",
|
||||
"Quick guide to supported patterns": "Quick guide to supported patterns",
|
||||
"RAM Utilization": "Выкарыстаньне памяці",
|
||||
"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)",
|
||||
"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.",
|
||||
"Unknown": "Невядома",
|
||||
"Unshared": "Unshared",
|
||||
"Unused": "Unused",
|
||||
"Up to Date": "Найноўшае",
|
||||
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
||||
"Upgrading": "Абнаўленьне",
|
||||
"Upload Rate": "Хуткасьць запампоўваньня",
|
||||
"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}}\"."
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "Нова основна версия, която може да не е съвмеситима с предишни версии.",
|
||||
"API Key": "API Ключ",
|
||||
"About": "За Програмата",
|
||||
"Actions": "Действия",
|
||||
"Add": "Добави",
|
||||
"Add Device": "Добави устройство",
|
||||
"Add Folder": "Добави папка",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Адреси",
|
||||
"All Data": "Всички данни",
|
||||
"Allow Anonymous Usage Reporting?": "Разреши анонимен доклад за ползване на програмата?",
|
||||
"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.": "Устройства настроени на introducer компютъра също ще бъдат добавени към този компютър.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Бъгове",
|
||||
"CPU Utilization": "Натоварване на Процесора",
|
||||
"Changelog": "Сипъск с промени",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Затвори",
|
||||
"Command": "Команда",
|
||||
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Добави шаблони за игнориране, по един на ред.",
|
||||
"Error": "Грешка",
|
||||
"External File Versioning": "Външно упраление на версиите",
|
||||
"File Pull Order": "По ред на дърпане",
|
||||
"File Versioning": "Файлови Версии",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Битовете за права за достъп са игнорирани, когато се проверява за промени. Използвай с файлови системи тип FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Когато 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": "Идентификатор на папка",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Глобавно Откриване",
|
||||
"Global Discovery Server": "Сървър за Глобално Откриване",
|
||||
"Global State": "Глобално състояние",
|
||||
"Help": "Помощ",
|
||||
"Ignore": "Игнорирай",
|
||||
"Ignore Patterns": "Шаблони за Игнориране",
|
||||
"Ignore Permissions": "Игнорирай Права за Достъп",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Introducer",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
|
||||
"Keep Versions": "Пази Версии",
|
||||
"Largest First": "Най-големите първо",
|
||||
"Last File Received": "Последния получен файл",
|
||||
"Last seen": "Последно видян",
|
||||
"Later": "По-късно",
|
||||
"Local Discovery": "Локално Откриване",
|
||||
"Local State": "Локално състояние",
|
||||
"Major Upgrade": "Основно Обновяване",
|
||||
"Maximum Age": "Максимална Възраст",
|
||||
"Metadata Only": "Само мета информация",
|
||||
"Move to top of queue": "Премести в началото на опашката",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Никога",
|
||||
"New Device": "Ново устройство",
|
||||
"New Folder": "Нова папка",
|
||||
"Newest First": "Най-новите първо",
|
||||
"No": "Не",
|
||||
"No File Versioning": "Няма Файлови Версии",
|
||||
"Notice": "Известие",
|
||||
"OK": "ОК",
|
||||
"Off": "Изключено",
|
||||
"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 wait": "Моля изчакай",
|
||||
"Preview": "Преглед",
|
||||
"Preview Usage Report": "Разгледай Доклада за Използване",
|
||||
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
|
||||
"RAM Utilization": "RAM Натоварване",
|
||||
"Random": "Произволно",
|
||||
"Release Notes": "Бележки по обновяването",
|
||||
"Rescan": "Повторно Сканиране",
|
||||
"Rescan All": "Пълно повторно сканиране",
|
||||
"Rescan Interval": "Интервал за Повторно Сканиране",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Спирането завършено",
|
||||
"Simple File Versioning": "Опростени Файлови Версии",
|
||||
"Single level wildcard (matches within a directory only)": "Маска на едно ниво (покрива само в папка)",
|
||||
"Smallest First": "Най-малките първо",
|
||||
"Source Code": "Сорс Код",
|
||||
"Staggered File Versioning": "Наслагващи се Файлови Версии",
|
||||
"Start Browser": "Стартирай Браузъра",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Използва се следния интервал: за първия час се пази версия всеки 30 секунди, за първия ден се пази версия всеки час, за първите 30 дена се пази версия всеки ден, до максимума се пази една версия всяка седмица.",
|
||||
"The maximum age must be a number and cannot be blank.": "Максималната възраст трябва да е число и не може д ае празна.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максималното време да се пазят весрсии (в дни, сложи 0, за да пазиш версии завинаги).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Броят стари версии, които да бъдат пазени за всеки файл.",
|
||||
"The number of versions must be a number and cannot be blank.": "Броят версии трябва да бъде число и не може да бъде празно.",
|
||||
"The path cannot be blank.": "Пътят неможе да бъде празен.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
|
||||
"This is a major version upgrade.": "Това е нова основна версия.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Неясен",
|
||||
"Unshared": "Споделянето прекратено",
|
||||
"Unused": "Неизползван",
|
||||
"Up to Date": "Актуален",
|
||||
"Upgrade": "Обнови",
|
||||
"Upgrade To {%version%}": "Обновен До {{version}}",
|
||||
"Upgrading": "Обновяване",
|
||||
"Upload Rate": "Скорост на Качване",
|
||||
"Uptime": "Работи вече",
|
||||
"Use HTTPS for GUI": "Използвай HTTPS за Потребителския Интерфейс",
|
||||
"Version": "Версия",
|
||||
"Versions Path": "Път до Версиите",
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "Una nova versió major pot ser incompatible amb versions anteriors.",
|
||||
"API Key": "Clau API",
|
||||
"About": "Sobre",
|
||||
"Actions": "Accions",
|
||||
"Add": "Afegir",
|
||||
"Add Device": "Afegir dispositiu",
|
||||
"Add Folder": "Afegir carpeta",
|
||||
"Add new folder?": "Afegir nova carpeta?",
|
||||
"Address": "Adreça",
|
||||
"Addresses": "Adreces",
|
||||
"All Data": "All Data",
|
||||
"All Data": "Totes les dades",
|
||||
"Allow Anonymous Usage Reporting?": "Permetre l'enviament anònim d'informes d'ús?",
|
||||
"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": "Alfabètic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando extern s'encarrega del control de versions. Ha d'eliminar l'arxiu de la carpeta sincronitzada.",
|
||||
"Anonymous Usage Reporting": "Informe anònim d'ús",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Qualsevol dispositiu configurat com a dispositiu introductor s'afegirà a aquest dispositiu tambè.",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Qualsevol dispositiu configurat en un dispositiu introductor també s'afegirà a aquest dispositiu.",
|
||||
"Automatic upgrades": "Actualitzacions automàtiques",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Utilització del CPU",
|
||||
"Changelog": "Historial de canvis",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Tancar",
|
||||
"Command": "Command",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentari quan és usat al principi d'una línia",
|
||||
"Compression": "Compression",
|
||||
"Compression": "Compressió",
|
||||
"Connection Error": "Error de connexió",
|
||||
"Copied from elsewhere": "Copiat d'un altre lloc",
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 els següents col·laboradors:",
|
||||
"Delete": "Esborrar",
|
||||
"Device ID": "ID del dispositiu",
|
||||
"Device Identification": "Identificació del dispositiu",
|
||||
@@ -41,12 +46,14 @@
|
||||
"Editing": "Modificant",
|
||||
"Enable UPnP": "Habilitat UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduir, separat per comes, adreces \"ip:port\" o \"dynamic\" per descobrir automàticament les adreces.",
|
||||
"Enter ignore patterns, one per line.": "Introduïx els patrons d'ignoració, un per línia.",
|
||||
"Enter ignore patterns, one per line.": "Introduex patrons a ignorar, un per línia.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"External File Versioning": "Versionat de fitxers extern",
|
||||
"File Pull Order": "Ordre d'agafar fitxers",
|
||||
"File Versioning": "Versionat de Fitxers",
|
||||
"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.": "Els bits de permisos dels fitxers son ignorats quan es cerquen canvis. Utilitzar en sistemes de fitxers FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Els fitxers es mouen amb l'estampat de la data a la carpeta .stversions quan son substituïts 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 estan protegits de canvis fets per altres dispositius, però els canvis fets en aquest dispositiu seran enviats a la resta del cluster.",
|
||||
"Folder ID": "ID de carpeta",
|
||||
"Folder Master": "Carpeta mestre",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Descobriment Global",
|
||||
"Global Discovery Server": "Servidor de Descobriment Global",
|
||||
"Global State": "Estat global",
|
||||
"Help": "Ajuda",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrons d'ignoració",
|
||||
"Ignore Permissions": "Ignora Permisos",
|
||||
@@ -66,36 +74,43 @@
|
||||
"Introducer": "Introductor",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversió del patrò introduït",
|
||||
"Keep Versions": "Mantenir Versions",
|
||||
"Largest First": "Més gran primer",
|
||||
"Last File Received": "Últim fitxer rebut",
|
||||
"Last seen": "Vist per última vegada",
|
||||
"Later": "Després",
|
||||
"Local Discovery": "Descobriment Local",
|
||||
"Local State": "Estat local",
|
||||
"Major Upgrade": "Actualització major",
|
||||
"Maximum Age": "Antiguitat Màxima",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
"Metadata Only": "Només metadades",
|
||||
"Move to top of queue": "Moure al primer de la cua",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Caràcter comodí de nivell múltiple (aparella en carpetes de nivells múltiples)",
|
||||
"Never": "Mai",
|
||||
"New Device": "Nou dispositiu",
|
||||
"New Folder": "Nova carpeta",
|
||||
"Newest First": "Més nou primer",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sense Versionat de Fitxer",
|
||||
"Notice": "Avís",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
"Off": "Desactivar",
|
||||
"Oldest First": "Més antic primer",
|
||||
"Out Of Sync": "Fora de la Sincronització",
|
||||
"Out of Sync Items": "Arxius encara no sincronitzats",
|
||||
"Outgoing Rate Limit (KiB/s)": "Tasca Límit de Sortida (KiB/s)",
|
||||
"Override Changes": "Sobreescriure 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 de la carpeta a l'equip local. Si no existeix serà creada. El caràcter (~) es pot fer servir com a drecera de",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta on les versions s'haurien de guardar (deixa-ho buit per fer servir el directori .stversions per defecte a la carpeta)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Si us plau consulta les notes de llançament abans de realitzar una actualització major.",
|
||||
"Please wait": "Si-us-plau espera",
|
||||
"Preview": "Vista prèvia",
|
||||
"Preview Usage Report": "Vista Prèvia de l'Informe d'Ús",
|
||||
"Quick guide to supported patterns": "Guia ràpida per als possibles patrons",
|
||||
"RAM Utilization": "Utilització de la RAM",
|
||||
"Random": "Aleatori",
|
||||
"Release Notes": "Notes de llançament",
|
||||
"Rescan": "Re-escanejar",
|
||||
"Rescan All": "Rescan All",
|
||||
"Rescan All": "Re-escanejar tot",
|
||||
"Rescan Interval": "Interval de re-escaneig",
|
||||
"Restart": "Reiniciar",
|
||||
"Restart Needed": "És Necessari Reiniciar",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Apagat complet",
|
||||
"Simple File Versioning": "Versionat de Fitxers Senzill",
|
||||
"Single level wildcard (matches within a directory only)": "Caràcter comodí de nivell singular (aparella sóls en una carpeta)",
|
||||
"Smallest First": "Més petit primer",
|
||||
"Source Code": "Codi Font",
|
||||
"Staggered File Versioning": "Versionat de Fitxers Esglaonat",
|
||||
"Start Browser": "Arrancar Navegador",
|
||||
@@ -132,14 +148,14 @@
|
||||
"Syncthing is restarting.": "Reiniciant syncthing.",
|
||||
"Syncthing is upgrading.": "Actualitzant syncthing.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Synthing sembla parat, o hi ha algun problema amb la connexió a Internet. Reintentant...",
|
||||
"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.": "Sembla ser que Syncthing està tinguent problemes per processar la teva petició. Si us plau, refresca la pàgina o reinicia Syncthing si el problema persisteix.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Les estadístiques agregades estan públicament disponibles a {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuració s'ha guardar però no s'ha activat. S'ha de reiniciar el synthing per activar la nova configuració.",
|
||||
"The device ID cannot be blank.": "El ID del dispositiu no pot estar en blanc.",
|
||||
"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).": "El ID del dispositiu per introduir ací es pot trobar al diàleg \"Editar > Mostrar ID\" en l'altre dispositiu. Els espais i les barres son opcionals (s'ignoren).",
|
||||
"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 d'ús encriptat s'envia diàriament. Es fa servir per rastrejar plataformes habituals, mides de carpetes i versions de l'aplicació. Si es canvia el conjunt de dades reportades es demanarà amb aquest diàleg de nou.",
|
||||
"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.": "El ID del dispositiu introduït no sembla vàlid. Hauria de tenir 52 o 56 caràcters amb lletres i números, els espais i les barres son opcionals.",
|
||||
"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.": "El primer paràmetre de la línia de comandes és el camí a la carpeta i el segon paràmetre és el camí relatiu a la carpeta.",
|
||||
"The folder ID cannot be blank.": "El ID del dispositiu no pot estar en blanc.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "El ID de la carpeta ha de ser un identificador curt (64 caràcters o menys) format només per lletres, nombres i el punt (.), barra (-) i barra baixa (_).",
|
||||
"The folder ID must be unique.": "El ID de la carpeta ha de ser únic.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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 fan servir els següents intervals: per la primera hora es manté una versió cada 30 segons, pel primer dia es manté una versió cada hora, pel primer cada 30 dies es manté una versió cada dia, fins el màxim d'antiguitat es manté una versió cada setmana.",
|
||||
"The maximum age must be a number and cannot be blank.": "La màxima antiguitat ha de ser un número i no pot estar en blanc.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Temps màxim en mantenir una versió (en dies, si es deixa en 0 es mantenen les versions per sempre).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "El nombre de versions antigues que es mantenen per fitxer.",
|
||||
"The number of versions must be a number and cannot be blank.": "El nombre de versions ha de ser un número i no es pot deixar en blanc.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"The path cannot be blank.": "El camí no pot estar en blanc.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "El interval de re-escaneig ha der ser un nombre positiu de segons.",
|
||||
"This is a major version upgrade.": "Aquesta és una actualització de versió major.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Desconegut",
|
||||
"Unshared": "No compartit",
|
||||
"Unused": "No usat",
|
||||
"Up to Date": "Actualitzat",
|
||||
"Upgrade": "Actualització",
|
||||
"Upgrade To {%version%}": "Actualitzar a {{version}}",
|
||||
"Upgrading": "Actualitzant",
|
||||
"Upload Rate": "Tasca de Pujada",
|
||||
"Uptime": "Temps funcionant",
|
||||
"Use HTTPS for GUI": "Utilitzar HTTPS pel GUI",
|
||||
"Version": "Versió",
|
||||
"Versions Path": "Carpeta de les Versions",
|
||||
|
||||
194
gui/assets/lang/lang-ca@valencia.json
Normal file
194
gui/assets/lang/lang-ca@valencia.json
Normal file
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
"Clean out after": "Clean out after",
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "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 days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"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ó.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"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}}\"."
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "Záporný počet dní nedává smysl.",
|
||||
"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ář",
|
||||
@@ -9,13 +12,15 @@
|
||||
"Addresses": "Adresy",
|
||||
"All Data": "Všechna data",
|
||||
"Allow Anonymous Usage Reporting?": "Povolit anonymní hlášení o používání?",
|
||||
"Alphabetic": "Abecedně",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Verzování obstarává externí příkaz. Musí odstranit soubor ze sdíleného adresáře.",
|
||||
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Jakékoliv přístroje nakonfigurované na zavaděči budou přidány také na tento přístroj.",
|
||||
"Automatic upgrades": "Automatický upgrade",
|
||||
"Automatic upgrades": "Automatické aktualizace",
|
||||
"Bugs": "Chyby",
|
||||
"CPU Utilization": "Využití CPU",
|
||||
"Changelog": "Changelog",
|
||||
"Clean out after": "Vyčistit po",
|
||||
"Close": "Zavřít",
|
||||
"Command": "Příkaz",
|
||||
"Comment, when used at the start of a line": "Komentář, pokud použito na začátku řádku",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Vložit ignorované vzory, jeden na řádek.",
|
||||
"Error": "Chyba",
|
||||
"External File Versioning": "Externí verzování souborů",
|
||||
"File Versioning": "Verze souborů",
|
||||
"File Pull Order": "Pořadí stahování souborů",
|
||||
"File Versioning": "Verzování souborů",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Bity označující práva souborů jsou při hledání změn ignorovány. Použít pro souborové systémy FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do adresáře .stversions.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do verzí označených daty v adresáři .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.": "Soubory jsou chráněny před změnami na ostatních přístrojích, ale změny provedené z tohoto přístroje budou rozeslány na zbytek clusteru.",
|
||||
"Folder ID": "ID adresáře",
|
||||
@@ -59,6 +66,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í",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Zavaděč",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
|
||||
"Keep Versions": "Ponechat verze",
|
||||
"Largest First": "Od největšího",
|
||||
"Last File Received": "Poslední přijatý soubor",
|
||||
"Last seen": "Naposledy spatřen",
|
||||
"Later": "Později",
|
||||
"Local Discovery": "Místní oznamování",
|
||||
"Local State": "Místní status",
|
||||
"Major Upgrade": "Důležitá aktualizace",
|
||||
"Maximum Age": "Maximální časový limit",
|
||||
"Metadata Only": "Pouze metadata",
|
||||
"Move to top of queue": "Přesunout na začátek fronty",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Nikdy",
|
||||
"New Device": "Nový přístroj",
|
||||
"New Folder": "Nový adresář",
|
||||
"Newest First": "Od nejnovějšího",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Bez verzí souborů",
|
||||
"No File Versioning": "Bez verzování souborů",
|
||||
"Notice": "Oznámení",
|
||||
"OK": "OK",
|
||||
"Off": "Vypnuta",
|
||||
"Oldest First": "Od nejstaršího",
|
||||
"Out Of Sync": "Nesesynchronizováno",
|
||||
"Out of Sync Items": "Nesesynchronizované položky",
|
||||
"Outgoing Rate Limit (KiB/s)": "Omezení odchozí rychlosti (KiB/s)",
|
||||
"Override Changes": "Přepsat změny",
|
||||
"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": "Cesta k adresáři na lokálním počítači. Pokud neexistuje, bude vytvořen. Znak vlnovky (~) může být použit jako zkratka pro",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Cesta pro ukládání verzí (nechat prázdné pro výchozí podadresář .stversions v adresářích).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Před spuštěním důležité aktualizace si nejdříve přečtěte poznámky k vydání nové verze.",
|
||||
"Please wait": "Chvíli strpení",
|
||||
"Preview": "Náhled",
|
||||
"Preview Usage Report": "Náhled záznamu o využítí",
|
||||
"Quick guide to supported patterns": "Rychlá nápověda k podporovaným vzorům",
|
||||
"RAM Utilization": "Využití RAM",
|
||||
"Random": "Náhodně",
|
||||
"Release Notes": "Poznámky k vydání",
|
||||
"Rescan": "Opakovat skenování",
|
||||
"Rescan All": "Opakovat skenování všech",
|
||||
"Rescan Interval": "Interval opakování skenování",
|
||||
@@ -118,10 +133,11 @@
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Pokud nebude vyplněno, bude nastaveno na jméno, které přístroj odesílá.",
|
||||
"Shutdown": "Vypnout",
|
||||
"Shutdown Complete": "Vypnutí dokončeno",
|
||||
"Simple File Versioning": "Jednoduché verze souborů",
|
||||
"Simple File Versioning": "Jednoduché verzování souborů",
|
||||
"Single level wildcard (matches within a directory only)": "Jednoúrovňový zástupný znak (shody pouze uvnitř adresáře)",
|
||||
"Smallest First": "Od nejmenšího",
|
||||
"Source Code": "Zdrojový kód",
|
||||
"Staggered File Versioning": "Vícenásobné verze souborů",
|
||||
"Staggered File Versioning": "Postupné verzování souborů",
|
||||
"Start Browser": "Otevřít prohlížeč",
|
||||
"Stopped": "Pozastaveno",
|
||||
"Support": "Podpora",
|
||||
@@ -147,20 +163,26 @@
|
||||
"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.": "Jsou použity následující intervaly: za první hodinu jsou ponechány verze pro každých 30 sekund, za první den jsou ponechány verze pro každou hodinu, za prvních 30 dní jsou ponechány verze pro každý den a do nejvyššího nastaveného stáří jsou ponechány verze pro každý týden.",
|
||||
"The maximum age must be a number and cannot be blank.": "Nejvyšší stáří je třeba zadat v podobě čísla a nemůže být prázdné.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maximální doba pro zachování verze (dny, zapsáním hodnoty 0 bude ponecháno navždy).",
|
||||
"The number of days must be a number and cannot be blank.": "Počet dní musí být číslo a nesmí být prázdný.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Počet dní, po který budou soubory uchovány v koši. Nula znamená navždy.",
|
||||
"The number of old versions to keep, per file.": "Počet starších verzí k zachování pro každý soubor.",
|
||||
"The number of versions must be a number and cannot be blank.": "Počet verzí musí být číslo a nemůže být prázdné.",
|
||||
"The path cannot be blank.": "Cesta nesmí být prázdná.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Interval opakování skenování musí být pozitivní číslo.",
|
||||
"This is a major version upgrade.": "Toto je důležitá aktualizace.",
|
||||
"Trash Can File Versioning": "Verzování souborů v koši",
|
||||
"Unknown": "Neznámý",
|
||||
"Unshared": "Nesdílený",
|
||||
"Unused": "Nepoužitý",
|
||||
"Up to Date": "Aktuální",
|
||||
"Upgrade": "Aktualizace",
|
||||
"Upgrade To {%version%}": "Aktualizovat na {{version}}",
|
||||
"Upgrading": "Aktualizuji",
|
||||
"Upload Rate": "Rychlost odesílání",
|
||||
"Uptime": "Celkový čas běhu",
|
||||
"Use HTTPS for GUI": "Použít HTTPS pro grafické rozhraní",
|
||||
"Version": "Verze",
|
||||
"Versions Path": "Cesta verzí",
|
||||
"Versions Path": "Cesta k verzím",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Verze jsou automaticky smazány, pokud jsou starší než maximální časový limit nebo překročí počet souborů povolených pro interval.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového přístroje mějte na paměti, že je ho třeba také zadat na druhé straně.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Adressen",
|
||||
"All Data": "Alle Daten",
|
||||
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
|
||||
"Alphabetic": "alphabetisch",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem 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",
|
||||
@@ -16,12 +20,13 @@
|
||||
"Bugs": "Fehler",
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Changelog": "Änderungsprotokoll",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Schließen",
|
||||
"Command": "Kommando",
|
||||
"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",
|
||||
@@ -38,39 +43,44 @@
|
||||
"Edit": "Bearbeiten",
|
||||
"Edit Device": "Gerät bearbeiten",
|
||||
"Edit Folder": "Verzeichnis bearbeiten",
|
||||
"Editing": "Bearbeitung",
|
||||
"Editing": "Bearbeiten",
|
||||
"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": "Dateiübertragungsreihenfolge",
|
||||
"File Versioning": "Dateiversionierung",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "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",
|
||||
"GUI Authentication User": "Nutzername für Zugang zur Benutzeroberfläche",
|
||||
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
|
||||
"Generate": "Generieren",
|
||||
"Global Discovery": "Globale Auffindung",
|
||||
"Global Discovery Server": "Globaler Auffindungsserver",
|
||||
"Global Discovery": "verfügbare Indexserver",
|
||||
"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",
|
||||
"Last File Received": "Letzte Datei",
|
||||
"Largest First": "Größtes zuerst",
|
||||
"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",
|
||||
"Metadata Only": "Nur Metadaten",
|
||||
"Move to top of queue": "An den Anfang der Warteschlange setzen",
|
||||
@@ -78,31 +88,36 @@
|
||||
"Never": "Nie",
|
||||
"New Device": "Neues Gerät",
|
||||
"New Folder": "Neues Verzeichnis",
|
||||
"Newest First": "Neuestes zuerst",
|
||||
"No": "Nein",
|
||||
"No File Versioning": "Keine Dateiversionierung",
|
||||
"Notice": "Hinweis",
|
||||
"OK": "OK",
|
||||
"Off": "Aus",
|
||||
"Oldest First": "Ältestes zuerst",
|
||||
"Out Of Sync": "Nicht synchronisiert",
|
||||
"Out of Sync Items": "Nicht synchronisierte Objekte",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ausgehendes Datenratelimit (KiB/s)",
|
||||
"Override Changes": "Änderungen überschreiben",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem die Versionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
|
||||
"Please consult the release notes before performing a major upgrade.": "BItte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
|
||||
"Please wait": "Bitte warten",
|
||||
"Preview": "Vorschau",
|
||||
"Preview Usage Report": "Vorschau des Nutzungsberichts",
|
||||
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Suchstrukturen",
|
||||
"RAM Utilization": "RAM Auslastung",
|
||||
"Rescan": "Überprüfen",
|
||||
"Rescan All": "Alle überprüfen",
|
||||
"Rescan Interval": "Suchintervall",
|
||||
"Random": "Zufall",
|
||||
"Release Notes": "Veröffentlichungsnotizen",
|
||||
"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",
|
||||
@@ -114,12 +129,13 @@
|
||||
"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",
|
||||
"Simple File Versioning": "Einfache Dateiversionierung",
|
||||
"Single level wildcard (matches within a directory only)": "Einzelnes Maskenzeichen (wird für ein einzelnes Verzeichnis verwendet)",
|
||||
"Smallest First": "Kleinstes zuerst",
|
||||
"Source Code": "Quellcode",
|
||||
"Staggered File Versioning": "Stufenweise Dateiversionierung",
|
||||
"Start Browser": "Browser starten",
|
||||
@@ -134,36 +150,42 @@
|
||||
"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 days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"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.",
|
||||
"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.",
|
||||
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
|
||||
"Unknown": "Unbekannt",
|
||||
"Unshared": "Ungeteilt",
|
||||
"Unused": "Ungenutzt",
|
||||
"Up to Date": "Aktuell",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Update auf {{version}}",
|
||||
"Upgrading": "Wird aktualisiert",
|
||||
"Upload Rate": "Upload",
|
||||
"Uptime": "Betriebszeit",
|
||||
"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,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "Μια νέα σημαντική έκδοση μπορεί να μην είναι συμβατή με τις προηγούμενες εκδόσεις.",
|
||||
"API Key": "Κλειδί API",
|
||||
"About": "Σχετικά με το Syncthing",
|
||||
"Actions": "Actions",
|
||||
"Add": "Προσθήκη",
|
||||
"Add Device": "Προσθήκη συσκευής",
|
||||
"Add Folder": "Προσθήκη φακέλου",
|
||||
@@ -9,21 +12,23 @@
|
||||
"Addresses": "Διευθύνσεις",
|
||||
"All Data": "Όλα τα δεδομένα",
|
||||
"Allow Anonymous Usage Reporting?": "Να επιτρέπεται η αποστολή ανώνυμων στοιχείων χρήσης;",
|
||||
"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": "Αλφαβητικά",
|
||||
"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": "Αυτόματη αναβάθμιση",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
|
||||
"Changelog": "Πληροφορίες εκδόσεων",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Τέλος",
|
||||
"Command": "Command",
|
||||
"Command": "Εντολή",
|
||||
"Comment, when used at the start of a line": "Σχόλιο, όταν χρησιμοποιείται στην αρχή μιας γραμμής",
|
||||
"Compression": "Συμπίεση",
|
||||
"Connection Error": "Σφάλμα σύνδεσης",
|
||||
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
|
||||
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 από τους παρακάτω συνεισφορείς:",
|
||||
"Delete": "Διαγραφή",
|
||||
"Device ID": "Ταυτότητα συσκευής",
|
||||
"Device Identification": "Ταυτότητα συσκευής",
|
||||
@@ -43,10 +48,12 @@
|
||||
"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": "External File Versioning",
|
||||
"External File Versioning": "Εξωτερική τήρηση εκδόσεων",
|
||||
"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.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Τα δικαιώματα των αρχείων θα αγνοούνται όταν κοιτάζω για αλλαγές. Αφορά συστήματα αρχείων FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Τα αρχεία που σβήνονται ή αντικαθιστούνται από το 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": "Να μην επιτρέπονται αλλαγές",
|
||||
@@ -56,9 +63,10 @@
|
||||
"GUI Authentication User": "Χρηστώνυμο για την πρόσβαση στη διεπαφή",
|
||||
"GUI Listen Addresses": "Διευθύνσεις από τις οποίες θα είναι προσβάσιμη η διεπαφή",
|
||||
"Generate": "Δημιουργία",
|
||||
"Global Discovery": "Να μπορεί να ανευρεθεί καθολικά (από παντού)",
|
||||
"Global Discovery": "Καθολική ανεύρεση",
|
||||
"Global Discovery Server": "Διακομιστής καθολικής ανεύρεσης κόμβου",
|
||||
"Global State": "Καθολική κατάσταση",
|
||||
"Help": "Help",
|
||||
"Ignore": "Αγνόησε",
|
||||
"Ignore Patterns": "Πρότυπο για αγνόηση",
|
||||
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Βασικός κόμβος",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
|
||||
"Keep Versions": "Διατήρηση εκδόσεων",
|
||||
"Largest First": "Το μεγαλύτερο πρώτα",
|
||||
"Last File Received": "Πιο πρόσφατο αρχείο",
|
||||
"Last seen": "Τελευταία φορά συνδεδεμένος",
|
||||
"Later": "Αργότερα",
|
||||
"Local Discovery": "Τοπική ανεύρεση",
|
||||
"Local State": "Τοπική κατάσταση",
|
||||
"Major Upgrade": "Σημαντική αναβάθμιση",
|
||||
"Maximum Age": "Μέγιστη ηλικία",
|
||||
"Metadata Only": "Μόνο μεταδεδομένα",
|
||||
"Move to top of queue": "Μεταφορά στην αρχή της λίστας",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Ποτέ",
|
||||
"New Device": "Νέα συσκευή",
|
||||
"New Folder": "Νέος φάκελος",
|
||||
"Newest First": "Το νεότερο πρώτα",
|
||||
"No": "Όχι",
|
||||
"No File Versioning": "Να μην τηρούνται εκδόσεις",
|
||||
"Notice": "Σημείωση",
|
||||
"OK": "OK",
|
||||
"Off": "Απενεργοποιημένο",
|
||||
"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 wait": "Παρακαλώ περιμένετε",
|
||||
"Preview": "Προεπισκόπηση",
|
||||
"Preview Usage Report": "Προεπισκόπηση αναφοράς χρήσης",
|
||||
"Quick guide to supported patterns": "Σύντομη βοήθεια σχετικά με τα πρότυπα αναζήτησης που υποστηρίζονται",
|
||||
"RAM Utilization": "Επιβάρυνση RAM",
|
||||
"Random": "Τυχαία",
|
||||
"Release Notes": "Σημείωμα έκδοσης",
|
||||
"Rescan": "Έλεγξε για αλλαγές",
|
||||
"Rescan All": "Έλεγξέ τα όλα για αλλαγές",
|
||||
"Rescan Interval": "Κάθε πότε θα ελέγχεται για αλλαγές ",
|
||||
@@ -112,7 +127,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.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα ενημερώνεται αυτόματα αν αλλάξει το όνομα της συσκευής.",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Πλήρης απενεργοποίηση",
|
||||
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
|
||||
"Single level wildcard (matches within a directory only)": "Τελεστής μπαλαντέρ (*) για ένα επίπεδο (χρησιμοποιείται για έναν φάκελο μόνο)",
|
||||
"Smallest First": "Το μικρότερο πρώτα",
|
||||
"Source Code": "Πηγαίος κώδικας",
|
||||
"Staggered File Versioning": "Να τηρούνται κλιμακούμενες εκδόσεις",
|
||||
"Start Browser": "Εκκίνηση προγράμματος περιήγησης",
|
||||
@@ -132,14 +148,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 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 φαίνεται να αντιμετωπίζει ένα πρόβλημα με την επεξεργασία του αιτήματός σου. Παρακαλούμε, αν το πρόβλημα συνεχίζει, ανανέωσε την σελίδα ή επανεκκίνησε το 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 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.": "Η ταυτότητα του φακέλου πρέπει να είναι μοναδική.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Θα χρησιμοποιούνται τα εξής διαστήματα: Την πρώτη ώρα θα τηρείται μια έκδοση κάθε 30 δευτερόλεπτα. Την πρώτη ημέρα, μια έκδοση κάθε μια ώρα. Τις πρώτες 30 ημέρες, μία έκδοση κάθε ημέρα. Από εκεί και έπειτα μέχρι τη μέγιστη ηλικία, θα τηρείται μια έκδοση κάθε εβδομάδα.",
|
||||
"The maximum age must be a number and cannot be blank.": "Η μέγιστη ηλικία πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Η μέγιστη ηλικία παλιότερων εκδόσεων (σε ημέρες, αν δώσεις 0 οι παλιότερες εκδόσεις θα διατηρούνται για πάντα).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Πόσες παλιότερες εκδόσεις θα διατηρούνται, ανά αρχείο.",
|
||||
"The number of versions must be a number and cannot be blank.": "Ο αριθμός εκδόσεων πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
|
||||
"The path cannot be blank.": "Το μονοπάτι δεν μπορεί να είναι κενό.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Ο χρόνος επανελέγχου για αλλαγές είναι σε δευτερόλεπτα (δηλ. θετικός αριθμός).",
|
||||
"This is a major version upgrade.": "Αυτή είναι μιας σημαντική αναβάθμιση.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Άγνωστο",
|
||||
"Unshared": "Δε μοιράζεται",
|
||||
"Unused": "Δε χρησιμοποιείται",
|
||||
"Up to Date": "Ενημερωμένος",
|
||||
"Upgrade": "Αναβάθμιση",
|
||||
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
|
||||
"Upgrading": "Αναβάθμιση",
|
||||
"Upload Rate": "Ταχύτητα ανεβάσματος",
|
||||
"Uptime": "Χρόνος απρόσκοπτης λειτουργίας",
|
||||
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
|
||||
"Version": "Έκδοση",
|
||||
"Versions Path": "Φάκελος τήρησης εκδόσεων",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "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.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "CPU Utilisation",
|
||||
"Changelog": "Changelog",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Close",
|
||||
"Command": "Command",
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
@@ -44,8 +49,10 @@
|
||||
"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 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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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 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": "Folder ID",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Introducer",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
|
||||
"Keep Versions": "Keep Versions",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last seen": "Last seen",
|
||||
"Later": "Later",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Never",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"Newest First": "Newest First",
|
||||
"No": "No",
|
||||
"No File Versioning": "No File Versioning",
|
||||
"Notice": "Notice",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
"Oldest First": "Oldest First",
|
||||
"Out Of Sync": "Out of Sync",
|
||||
"Out of Sync Items": "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": "RAM Utilisation",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Rescan",
|
||||
"Rescan All": "Rescan All",
|
||||
"Rescan Interval": "Rescan Interval",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Shutdown Complete",
|
||||
"Simple File Versioning": "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": "Source Code",
|
||||
"Staggered File Versioning": "Staggered File Versioning",
|
||||
"Start Browser": "Start Browser",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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 days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "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.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Unknown",
|
||||
"Unshared": "Unshared",
|
||||
"Unused": "Unused",
|
||||
"Up to Date": "Up to Date",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Upgrade to {{version}}",
|
||||
"Upgrading": "Upgrading",
|
||||
"Upload Rate": "Upload Rate",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "Use HTTPS for GUI",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Versions Path",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -18,6 +20,7 @@
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "CPU Utilization",
|
||||
"Changelog": "Changelog",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Close",
|
||||
"Command": "Command",
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
@@ -49,6 +52,7 @@
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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 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": "Folder ID",
|
||||
@@ -62,6 +66,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",
|
||||
@@ -158,11 +163,14 @@
|
||||
"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 days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "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.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Unknown",
|
||||
"Unshared": "Unshared",
|
||||
"Unused": "Unused",
|
||||
|
||||
194
gui/assets/lang/lang-es-ES.json
Normal file
194
gui/assets/lang/lang-es-ES.json
Normal file
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
"Add new folder?": "¿Añadir una nueva carpeta?",
|
||||
"Address": "Dirección",
|
||||
"Addresses": "Direcciones",
|
||||
"All Data": "Todos los datos",
|
||||
"Allow Anonymous Usage Reporting?": "¿Deseas permitir el envío anónimo de informes de uso?",
|
||||
"Alphabetic": "Alfabético",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando externo controla la versión. El fichero debe ser eliminado de la carpeta sincronizada.",
|
||||
"Anonymous Usage Reporting": "Informe anónimo de uso",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo de introducción será añadido también.",
|
||||
"Automatic upgrades": "Actualizaciones automáticas",
|
||||
"Bugs": "Errores (bugs)",
|
||||
"CPU Utilization": "Uso de CPU",
|
||||
"Changelog": "Informe de cambios",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Cerrar",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentar, cuando se usa al comienzo de una línea",
|
||||
"Compression": "Compresión",
|
||||
"Connection Error": "Error de conexión",
|
||||
"Copied from elsewhere": "Copiado de otro sitio",
|
||||
"Copied from original": "Copiado del original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 los siguientes Colaboradores:",
|
||||
"Delete": "Borrar",
|
||||
"Device ID": "ID del dispositivo",
|
||||
"Device Identification": "Identificación del dispositivo",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) quiere conectarse. ¿Añadir nuevo dispositivo?",
|
||||
"Devices": "Dispositivos",
|
||||
"Disconnected": "Desconectado",
|
||||
"Documentation": "Documentación",
|
||||
"Download Rate": "Velocidad de descarga",
|
||||
"Downloaded": "Descargado",
|
||||
"Downloading": "Descargando",
|
||||
"Edit": "Editar",
|
||||
"Edit Device": "Editar dispositivo",
|
||||
"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.": "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",
|
||||
"File Pull Order": "Orden de ficheros del pull",
|
||||
"File Versioning": "Versionado de ficheros",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Los bits de permiso de ficheros son ignorados cuando se buscan cambios. Utilizar en sistemas de ficheros FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Los ficheros son cambiados a versiones con indicación de fecha en una carpeta \".stversions\" cuando son reemplazados o borrados por 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.": "Los ficheros son protegidos por los cambios hechos en otros dispositivos, pero los cambios hechos en este dispositivo serán enviados al resto del grupo (cluster).",
|
||||
"Folder ID": "ID de carpeta",
|
||||
"Folder Master": "Carpeta principal",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
"Folders": "Carpetas",
|
||||
"GUI Authentication Password": "Password de la Interfaz Gráfica de Usuario (GUI)",
|
||||
"GUI Authentication User": "Autentificación de usuario de la Interfaz Gráfica de Usuario (GUI)",
|
||||
"GUI Listen Addresses": "Direcciones de escucha de la Interfaz Gráfica de Usuario (GUI)",
|
||||
"Generate": "Generar",
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Límite de descarga (KiB/s)",
|
||||
"Introducer": "Presentador",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (por ejemplo, \"no excluir\")",
|
||||
"Keep Versions": "Mantener versiones",
|
||||
"Largest First": "Más grande primero",
|
||||
"Last File Received": "Último fichero recibido",
|
||||
"Last seen": "Visto por última vez",
|
||||
"Later": "Más tarde",
|
||||
"Local Discovery": "Descubrimiento local",
|
||||
"Local State": "Estado local",
|
||||
"Major Upgrade": "Actualización importante",
|
||||
"Maximum Age": "Edad máxima",
|
||||
"Metadata Only": "Sólo metadatos",
|
||||
"Move to top of queue": "Mover al principio de la cola",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Comodín multinivel (coincide con múltiples niveles de directorio)",
|
||||
"Never": "Nunca",
|
||||
"New Device": "Nuevo Dispositivo",
|
||||
"New Folder": "Nueva Carpeta",
|
||||
"Newest First": "El más nuevo primero",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sin versionado de fichero",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Off": "Desconectar",
|
||||
"Oldest First": "El más antiguo primero",
|
||||
"Out Of Sync": "No sincronizado",
|
||||
"Out of Sync Items": "Elementos no sincronizados",
|
||||
"Outgoing Rate Limit (KiB/s)": "Límite de subida (KiB/s)",
|
||||
"Override Changes": "Anular cambios",
|
||||
"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 en la máquina local. Se creará si no existe. El carácter de la tilde (~) puede usarse como atajo.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta donde se almacenarán las versiones (dejar vacío para usar la carpeta por defecto \".stversions\").",
|
||||
"Please consult the release notes before performing a major upgrade.": "Por favor, consultar las notas de la versión antes de realizar una actualización importante.",
|
||||
"Please wait": "Por favor, espere",
|
||||
"Preview": "Vista previa",
|
||||
"Preview Usage Report": "Informe de uso de vista previa",
|
||||
"Quick guide to supported patterns": "Guía rápida de patrones soportados",
|
||||
"RAM Utilization": "Uso de RAM",
|
||||
"Random": "Aleatorio",
|
||||
"Release Notes": "Notas de la versión",
|
||||
"Rescan": "Volver a buscar",
|
||||
"Rescan All": "Volver a buscar todo",
|
||||
"Rescan Interval": "Intervalo de nueva búsqueda",
|
||||
"Restart": "Reiniciar",
|
||||
"Restart Needed": "Reinicio necesario",
|
||||
"Restarting": "Reiniciando",
|
||||
"Reused": "Reutilizado",
|
||||
"Save": "Guardar",
|
||||
"Scanning": "Rastreando",
|
||||
"Select the devices to share this folder with.": "Selecciona los dispositivos con los que compartir esta carpeta.",
|
||||
"Select the folders to share with this device.": "Selecciona las carpetas para compartir con este dispositivo.",
|
||||
"Settings": "Ajustes",
|
||||
"Share": "Compartir",
|
||||
"Share Folder": "Compartir carpeta",
|
||||
"Share Folders With Device": "Compartir carpetas con dispositivo",
|
||||
"Share With Devices": "Compartir con dispositivos",
|
||||
"Share this folder?": "¿Deseas compartir esta carpeta?",
|
||||
"Shared With": "Compartir con",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador corto para la carpeta. Debe ser el mismo en todos los dispositivos del grupo (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.": "Se muestra en lugar del ID del dispositivo en el estado del grupo (cluster). Se notificará a los otros dispositivos como nombre opcional por defecto.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Se muestra en lugar del ID del dispositivo en el estado del grupo (cluster). Se actualizará al nombre que el dispositivo anuncia si se deja vacío.",
|
||||
"Shutdown": "Apagar",
|
||||
"Shutdown Complete": "Apagar completamente",
|
||||
"Simple File Versioning": "Versionado simple de fichero",
|
||||
"Single level wildcard (matches within a directory only)": "Comodín de nivel único (coincide solamente dentro de un directorio)",
|
||||
"Smallest First": "El más pequeño primero",
|
||||
"Source Code": "Código fuente",
|
||||
"Staggered File Versioning": "Versionado escalonado de fichero",
|
||||
"Start Browser": "Iniciar el navegador",
|
||||
"Stopped": "Detenido",
|
||||
"Support": "Soporte",
|
||||
"Sync Protocol Listen Addresses": "Direcciones de escucha del protocolo de sincronización",
|
||||
"Syncing": "Sincronizando",
|
||||
"Syncthing has been shut down.": "Syncthing se ha detenido.",
|
||||
"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…": "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.",
|
||||
"The device ID cannot be blank.": "La ID del dispositivo no puede estar vacía.",
|
||||
"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).": "La ID del dispositivo que hay que introducir aquí puede encontrarse en el menú \"Editar > Mostrar ID\" en el otro dispositivo. Los espacios y barras son opcionales (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.": "El informe encriptado de uso se envía diariamente. Se usa para rastrear plataformas comunes, tamaños de carpetas y versiones de la aplicación. Si el conjunto de datos enviados en el informes se cambia, se le pedirá a usted autorización de nuevo.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "La ID del dispositivo introducida no parece válida. Debe ser una cadena de 52 ó 56 caracteres formada por letras y números, con espacios y guiones opcionales.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "El primer parámetro del comando es la ruta de la carpeta y el segundo es la ruta relativa en la carpeta.",
|
||||
"The folder ID cannot be blank.": "La ID de la carpeta no puede estar vacía.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "La ID de la carpeta debe ser un identificador corto (64 caracteres o menos) y debe estar formado únicamente por letras, números y los símbolos punto (.), guión (-) y guión bajo (_).",
|
||||
"The folder ID must be unique.": "La ID de la carpeta debe ser única.",
|
||||
"The folder path cannot be blank.": "La ruta de la carpeta no puede estar en blanco.",
|
||||
"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.": "Se utilizan los siguientes intervalos: para la primera hora se mantiene una versión cada 30 segundos, para el primer día se mantiene una versión cada hora, para los primeros 30 días se mantiene una versión diaria hasta la edad máxima de una semana.",
|
||||
"The maximum age must be a number and cannot be blank.": "La edad máxima debe ser un número y no puede estar vacía.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El tiempo máximo para mantener una versión en días (introducir 0 para mantener las versiones indefinidamente).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "El número de versiones a antiguas a mantener para cada fichero.",
|
||||
"The number of versions must be a number and cannot be blank.": "El número de versiones debe ser un número y no puede estar vacío.",
|
||||
"The path cannot be blank.": "La ruta no puede estar vacía.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "El intervalo de actualización debe ser un número positivo de segundos.",
|
||||
"This is a major version upgrade.": "Hay una actualización importante.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Desconocido",
|
||||
"Unshared": "No compartido",
|
||||
"Unused": "No usado",
|
||||
"Up to Date": "Actualizado",
|
||||
"Upgrade": "Actualizar",
|
||||
"Upgrade To {%version%}": "Actualizar a {{version}}",
|
||||
"Upgrading": "Actualizando",
|
||||
"Upload Rate": "Velocidad de actualización",
|
||||
"Uptime": "Tiempo de funcionamiento",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para la Interfaz Gráfica de Usuario (GUI)",
|
||||
"Version": "Versión",
|
||||
"Versions Path": "Ruta de las versiones",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Las versiones se borran automáticamente si son más antiguas que la edad máxima o exceden el número de ficheros permitidos en un intervalo.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cuando añada una nueva carpeta, tenga en cuenta que su ID se usa para unir carpetas entre dispositivos. Son sensibles a las mayúsculas y deben coincidir exactamente entre todos los dispositivos.",
|
||||
"Yes": "Si",
|
||||
"You must keep at least one version.": "Debes mantener al menos una versión.",
|
||||
"full documentation": "Documentación completa",
|
||||
"items": "Elementos",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\"."
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "Una versión mayor nueva puede ser incompatible con versiones anteriores.",
|
||||
"API Key": "Clave API",
|
||||
"About": "Acerca de",
|
||||
"Actions": "Acciones",
|
||||
"Add": "Agregar",
|
||||
"Add Device": "Agregar Dispositivo",
|
||||
"Add Folder": "Agregar Repositorio",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Direcciones",
|
||||
"All Data": "Todos los datos",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir reporte anónimo de uso?",
|
||||
"Alphabetic": "Alfabético",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando exterior maneja el control de versiones. Éste tiene que eliminar el archivo de la carpeta sincronizada.",
|
||||
"Anonymous Usage Reporting": "Reporte anónimo de uso",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo introductor será también agregado a este dispositivo.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Errores",
|
||||
"CPU Utilization": "Uso de CPU",
|
||||
"Changelog": "Registro de cambios",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Cerrar",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Añadir patrones de exclusión, uno por línea.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "Control de versiones externo",
|
||||
"File Pull Order": "Orden para coger ficheros",
|
||||
"File Versioning": "Control de versiones",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Los permisos de archivo son ignorados al buscar cambios. Usar el sistemas de archivos FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Los archivos son movidos al directorio .stversions cuando son reemplazados o eliminados por Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Lo archivos son movidos al directorio .stversions y renombrados a versiones marcadas por fecha cuando son reemplazados o eliminados por 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.": "Los archivos están protegidos frente a los cambios realizados en otros dispositivos, peros los cambios realizados en este dispositivo serán envíados al resto del grupo",
|
||||
"Folder ID": "ID del repositorio",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Búsqueda en internet",
|
||||
"Global Discovery Server": "Servidor global de identificación",
|
||||
"Global State": "Estado global",
|
||||
"Help": "Ayuda",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrones de exclusión",
|
||||
"Ignore Permissions": "Ignorar permisos",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Introductor",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (es decir, no excluir)",
|
||||
"Keep Versions": "Conservar versiones",
|
||||
"Largest First": "Más grande primero",
|
||||
"Last File Received": "Último archivo recibido",
|
||||
"Last seen": "Visto por ultima vez",
|
||||
"Later": "Más tarde",
|
||||
"Local Discovery": "Búsqueda en red local",
|
||||
"Local State": "Estado local",
|
||||
"Major Upgrade": "Actualización mayor",
|
||||
"Maximum Age": "Edad máxima",
|
||||
"Metadata Only": "Sólo metadatos",
|
||||
"Move to top of queue": "Mover al principio de la cola.",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Nunca",
|
||||
"New Device": "Nuevo dispositivo",
|
||||
"New Folder": "Nuevo repositorio",
|
||||
"Newest First": "Nuevo primero",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sin control de versiones de archivos",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Off": "Apagado",
|
||||
"Oldest First": "Antiguo primero",
|
||||
"Out Of Sync": "Fuera de sincronización",
|
||||
"Out of Sync Items": "Ítems no sincronizados",
|
||||
"Outgoing Rate Limit (KiB/s)": "Tasa máxima de envío (KiB/s)",
|
||||
"Override Changes": "Reemplazar los cambios",
|
||||
"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 del repositorio en el equipo local. Será creado si no existe. El carácter tilde (~) puede ser utilizado como atajo de ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta donde serán guardas las versiones (dejar vacío para usar el directorio predifinido \".stversions\" en el repositorio)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Por favor consulta las notas de lanzamiento antes de realizar una actualizacón mayor.",
|
||||
"Please wait": "Aguarde por favor",
|
||||
"Preview": "Vista previa",
|
||||
"Preview Usage Report": "Ver reporte de uso",
|
||||
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
|
||||
"RAM Utilization": "Utilización de RAM",
|
||||
"Random": "Aleatorio",
|
||||
"Release Notes": "Notas de lanzamiento",
|
||||
"Rescan": "Reescanear",
|
||||
"Rescan All": "Reescanear todo",
|
||||
"Rescan Interval": "Intervalo de reescaneo",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Apagado completado",
|
||||
"Simple File Versioning": "Versiones simple de archivos",
|
||||
"Single level wildcard (matches within a directory only)": "Carácter comodín de un solo nivel (coincide sólo dentro de un directorio)",
|
||||
"Smallest First": "Más pequeño primero",
|
||||
"Source Code": "Código fuente",
|
||||
"Staggered File Versioning": "Versiones del archivo escalonado",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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.": "Los siguientes intervalos se utilizan: para la primera hora una versión se mantiene cada 30 segundos, para el primer día de una versión se mantiene cada hora, durante los primeros 30 días de la versión se mantiene todos los días, hasta que la edad máxima de una versión se mantiene cada semana.",
|
||||
"The maximum age must be a number and cannot be blank.": "La edad máxima debe ser un número y no puede estar en blanco.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El tiempo máximo para mantener una versión (en días, establece en 0 para mantener versiones para siempre).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "El numero de versiones anteriores a conservar, por archivo.",
|
||||
"The number of versions must be a number and cannot be blank.": "El número de versiones debe ser un número y no puede estar vacío.",
|
||||
"The path cannot be blank.": "La ruta no puede estar vacía.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "El intervalo de reescaneo debe ser un número no negativo de segundos.",
|
||||
"This is a major version upgrade.": "Esta es una actualización de version mayor.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Desconocido",
|
||||
"Unshared": "No compartido",
|
||||
"Unused": "No utilizado",
|
||||
"Up to Date": "Actualizado",
|
||||
"Upgrade": "Actualizar",
|
||||
"Upgrade To {%version%}": "Actualizar a {{version}}",
|
||||
"Upgrading": "Actualizando",
|
||||
"Upload Rate": "Tasa de subida",
|
||||
"Uptime": "Tiempo en funcionamiento",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para la GUI",
|
||||
"Version": "Versión",
|
||||
"Versions Path": "Ruta de versiones",
|
||||
|
||||
194
gui/assets/lang/lang-fi.json
Normal file
194
gui/assets/lang/lang-fi.json
Normal file
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
"Add new folder?": "Lisää uusi kansio?",
|
||||
"Address": "Osoite",
|
||||
"Addresses": "Osoitteet",
|
||||
"All Data": "Kaikki data",
|
||||
"Allow Anonymous Usage Reporting?": "Salli anonyymi käyttöraportointi?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ulkoinen komento hallitsee versionnin. Sen täytyy poistaa tiedosto synkronoidusta kansiosta.",
|
||||
"Anonymous Usage Reporting": "Anonyymi käyttöraportointi",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Kaikki esittelijäksi määritetyn laitteen tuntemat laitteet lisätään myös tähän laitteeseen.",
|
||||
"Automatic upgrades": "Automaattiset päivitykset",
|
||||
"Bugs": "Bugit",
|
||||
"CPU Utilization": "CPU:n käyttö",
|
||||
"Changelog": "Muutoshistoria",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Sulje",
|
||||
"Command": "Komento",
|
||||
"Comment, when used at the start of a line": "Kommentti, käytettäessä rivin alussa",
|
||||
"Compression": "Pakkaus",
|
||||
"Connection Error": "Yhteysvirhe",
|
||||
"Copied from elsewhere": "Kopioitu muualta",
|
||||
"Copied from original": "Kopioitu alkuperäisestä lähteestä",
|
||||
"Copyright © 2015 the following Contributors:": "Tekijänoikeus © 2015 seuraavat avustajat:",
|
||||
"Delete": "Poista",
|
||||
"Device ID": "Laitteen ID",
|
||||
"Device Identification": "Laitteen tunniste",
|
||||
"Device Name": "Laitteen nimi",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Laite {{device}} ({{address}}) haluaa yhdistää. Lisää uusi laite?",
|
||||
"Devices": "Laitteet",
|
||||
"Disconnected": "Yhteys katkaistu",
|
||||
"Documentation": "Dokumentaatio",
|
||||
"Download Rate": "Latausmäärä",
|
||||
"Downloaded": "Ladattu",
|
||||
"Downloading": "Ladataan",
|
||||
"Edit": "Muokkaa",
|
||||
"Edit Device": "Muokkaa laitetta",
|
||||
"Edit Folder": "Muokkaa kansiota",
|
||||
"Editing": "Muokkaus",
|
||||
"Enable UPnP": "Ota UPnP käyttöön",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Syötä pilkuin erotettuna osoitteet muodossa \"ip:portti\" tai syötä \"dynamic\" automaattista osoitteiden selvitystä varten.",
|
||||
"Enter ignore patterns, one per line.": "Syötä ohituslausekkeet, yksi riviä kohden.",
|
||||
"Error": "Virhe",
|
||||
"External File Versioning": "Ulkoinen tiedostoversionti",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "Tiedostoversiointi",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Tiedostojen oikeusbitit jätetään huomiotta etsittäessä muutoksia. Käytä FAT-tiedostojärjestelmissä.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Tiedostot siirretään päivämäärällä merkityiksi versioiksi .stversions-kansioon, kun Syncthing korvaa tai poistaa ne.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Tiedostot on suojattu muilla laitteilla tehdyiltä muutoksilta, mutta tällä laitteella tehdyt muutokset lähetetään muuhun ryhmään.",
|
||||
"Folder ID": "Kansion ID",
|
||||
"Folder Master": "Hallitsijakansio",
|
||||
"Folder Path": "Kansion polku",
|
||||
"Folders": "Kansiot",
|
||||
"GUI Authentication Password": "GUI:n salasana",
|
||||
"GUI Authentication User": "GUI:n käyttäjätunnus",
|
||||
"GUI Listen Addresses": "GUI:n kuunteluosoitteet",
|
||||
"Generate": "Generoi",
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Sisääntulevan liikenteen rajoitus (KiB/s)",
|
||||
"Introducer": "Esittelijä",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Käänteinen ehto (t.s. älä ohita)",
|
||||
"Keep Versions": "Säilytä versiot",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Viimeksi vastaanotettu tiedosto",
|
||||
"Last seen": "Nähty viimeksi",
|
||||
"Later": "Myöhemmin",
|
||||
"Local Discovery": "Paikallinen etsintä",
|
||||
"Local State": "Paikallinen tila",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maksimi-ikä",
|
||||
"Metadata Only": "Vain metadata",
|
||||
"Move to top of queue": "Siirrä jonon alkuun",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Monitasoinen jokerimerkki (vaikuttaa useassa kansiotasossa)",
|
||||
"Never": "Ei koskaan",
|
||||
"New Device": "Uusi laite",
|
||||
"New Folder": "Uusi kansio",
|
||||
"Newest First": "Newest First",
|
||||
"No": "Ei",
|
||||
"No File Versioning": "Ei tiedostoversiointia",
|
||||
"Notice": "Huomautus",
|
||||
"OK": "OK",
|
||||
"Off": "Pois",
|
||||
"Oldest First": "Oldest First",
|
||||
"Out Of Sync": "Ei ajan tasalla",
|
||||
"Out of Sync Items": "Kohteet, jotka eivät ole ajan tasalla",
|
||||
"Outgoing Rate Limit (KiB/s)": "Uloslähtevän liikenteen rajoitus (KiB/s)",
|
||||
"Override Changes": "Ohita muutokset",
|
||||
"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": "Polku kansioon paikallisella tietokoneella. Kansio luodaan, ellei sitä ole olemassa. Tilde-merkkiä (~) voidaan käyttää oikotienä polulle",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Polku jonne versiot tullaan tallentamaan (jätä tyhjäksi oletusvalintaa .stversions varten).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Ole hyvä ja odota",
|
||||
"Preview": "Esikatselu",
|
||||
"Preview Usage Report": "Esikatsele käyttöraportti",
|
||||
"Quick guide to supported patterns": "Tuettujen lausekkeiden pikaohje",
|
||||
"RAM Utilization": "RAM:n käyttö",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Skannaa uudelleen",
|
||||
"Rescan All": "Skannaa kaikki uudelleen",
|
||||
"Rescan Interval": "Uudelleenskannauksen aikaväli",
|
||||
"Restart": "Käynnistä uudelleen",
|
||||
"Restart Needed": "Uudelleenkäynnistys tarvitaan",
|
||||
"Restarting": "Käynnistetään uudelleen",
|
||||
"Reused": "Uudelleenkäytetty",
|
||||
"Save": "Tallenna",
|
||||
"Scanning": "Skannataan",
|
||||
"Select the devices to share this folder with.": "Valitse laitteet, joiden kanssa tämä kansio jaetaan.",
|
||||
"Select the folders to share with this device.": "Valitse kansiot jaettavaksi tämän laitteen kanssa.",
|
||||
"Settings": "Asetukset",
|
||||
"Share": "Jaa",
|
||||
"Share Folder": "Jaa kansio",
|
||||
"Share Folders With Device": "Jaa kansioita laitteen kanssa",
|
||||
"Share With Devices": "Jaa laitteiden kanssa",
|
||||
"Share this folder?": "Jaa tämä kansio?",
|
||||
"Shared With": "Jaettu seuraavien kanssa",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Lyhyt tunniste kansiolle. Tämän tulee olla sama kaikilla ryhmän laitteilla.",
|
||||
"Show ID": "Näytä ID",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Näytetään ryhmän tiedoissa laitteen ID:n sijaan. Ilmoitetaan muille laitteille vaihtoehtoisena oletusnimenä.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Näytetään ryhmän tiedoissa laitteen ID:n sijaan. Tyhjä nimi päivitetään laitteen ilmoittamaksi nimeksi.",
|
||||
"Shutdown": "Sammuta",
|
||||
"Shutdown Complete": "Sammutus valmis",
|
||||
"Simple File Versioning": "Yksinkertainen tiedostoversiointi",
|
||||
"Single level wildcard (matches within a directory only)": "Yksitasoinen jokerimerkki (vaikuttaa vain kyseisen kansion sisällä)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Lähdekoodi",
|
||||
"Staggered File Versioning": "Porrastettu tiedostoversiointi",
|
||||
"Start Browser": "Käynnistä selain",
|
||||
"Stopped": "Pysäytetty",
|
||||
"Support": "Tuki",
|
||||
"Sync Protocol Listen Addresses": "Synkronointiprotokollan kuunteluosoite",
|
||||
"Syncing": "Synkronoidaan",
|
||||
"Syncthing has been shut down.": "Syncthing on sammutettu.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing sisältää seuraavat ohjelmistot tai sen osat:",
|
||||
"Syncthing is restarting.": "Syncthing käynnistyy uudelleen.",
|
||||
"Syncthing is upgrading.": "Syncthing päivittyy.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing näyttää olevan alhaalla tai internetyhteydessä on ongelma. Yritetään uudelleen...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ei pysty käsittelemään pyyntöäsi. Ole hyvä ja päivitä sivu tai käynnistä Syncthing uudelleen, jos ongelma jatkuu.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Yhdistetyt tilastot ovat julkisesti saatavilla osoitteessa {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Asetukset on tallennettu, mutta niitä ei ole otettu käyttöön. Syncthingin täytyy käynnistyä uudelleen, jotta uudet asetukset saadaan käyttöön.",
|
||||
"The device ID cannot be blank.": "Laitteen ID ei voi olla tyhjä.",
|
||||
"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).": "Tähän kohtaan syötettävän ID:n löytää \"Muokkaa > Näytä ID\" -valikosta toisesta laitteesta. Välit ja viivat ovat valinnaisia (jätetään huomiotta).",
|
||||
"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.": "Salattu käyttöraportti lähetetään päivittäin. Sitä käytetään yleisimpien alustojen, kansioiden kokojen ja sovellusversioiden seuraamiseen. Jos raportitavan datan luonne muuttuu, sinua tullaan huomauttamaan tällä dialogilla uudelleen.",
|
||||
"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.": "Syötetty laite-ID ei näytä kelpaavalta. Sen tulisi olla 52 tai 56 merkkiä pitkä, joka koostuu kirjaimista ja numeroista, jossa välit ja viivat ovat valinnaisia.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Ensimmäinen komentoriviparametri on kansion polku, toinen parametri on suhteellinen polku kansion sisällä.",
|
||||
"The folder ID cannot be blank.": "Kansion ID ei voi olla tyhjä.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Kansion ID:n tulee olla lyhyt tunniste (64 merkkiä tai vähemmän), joka koostuu vain kirjaimista, numeroista, pisteistä (.), viivoista (-), ja alaviivoista (_).",
|
||||
"The folder ID must be unique.": "Kansion ID:n tulee olla uniikki.",
|
||||
"The folder path cannot be blank.": "Kansion polku ei voi olla tyhjä.",
|
||||
"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.": "Seuraavat aikavälit ovat käytössä: ensimmäisen tunnin ajalta uusi versio säilytetään joka 30 sekunti, ensimmäisen päivän ajalta uusi versio säilytetään tunneittain ja ensimmäisen 30 päivän aikana uusi versio säilytetään päivittäin. Lopulta uusi versio säilytetään viikoittain, kunnes maksimi-ikä saavutetaan.",
|
||||
"The maximum age must be a number and cannot be blank.": "Maksimi-iän tulee olla numero, eikä se voi olla tyhjä.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimiaika versioiden säilytykseen (päivissä, aseta 0 säilyttääksesi versiot ikuisesti).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Säilytettävien vanhojen versioiden määrä tiedostoa kohden.",
|
||||
"The number of versions must be a number and cannot be blank.": "Versioiden määrän rulee olla numero, eikä se voi olla tyhjä.",
|
||||
"The path cannot be blank.": "Polku ei voi olla tyhjä.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Uudelleenskannauksen aikavälin tulee olla ei-negatiivinen numero sekunteja.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Tuntematon",
|
||||
"Unshared": "Jakamaton",
|
||||
"Unused": "Käyttämätön",
|
||||
"Up to Date": "Ajan tasalla",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Päivitä versioon {{version}}",
|
||||
"Upgrading": "Päivitetään",
|
||||
"Upload Rate": "Lähetysmäärä",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "Käytä HTTPS:ää GUI:n kanssa",
|
||||
"Version": "Versio",
|
||||
"Versions Path": "Versioiden polku",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versiot poistetaan automaattisesti mikäli ne ovat vanhempia kuin maksimi-ikä tai niiden määrä ylittää sallitun määrän tietyllä aikavälillä.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lisättäessä laitetta, muista että tämä laite tulee myös lisätä toiseen laitteeseen.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lisättäessä uutta kansiota, muista että kansion ID:tä käytetään solmimaan kansiot yhteen laitteiden välillä. Ne ovat riippuvaisia kirjankoosta ja niiden tulee täsmätä kaikkien laitteiden välillä.",
|
||||
"Yes": "Kyllä",
|
||||
"You must keep at least one version.": "Sinun tulee säilyttää ainakin yksi versio.",
|
||||
"full documentation": "täysi dokumentaatio",
|
||||
"items": "kohteet",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\"."
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Adresses",
|
||||
"All Data": "Toutes les données",
|
||||
"Allow Anonymous Usage Reporting?": "Autoriser le rapport anonyme de statistiques d'utilisation ?",
|
||||
"Alphabetic": "Alphabétique",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Une commande externe gère les versions de fichiers. Elle supprime les fichiers dans le dossier synchronisé.",
|
||||
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Toute machine ajoutée depuis une machine initiatrice sera aussi ajoutée sur cette machine.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Utilisation du CPU",
|
||||
"Changelog": "Nouveautés",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Fermer",
|
||||
"Command": "Commande",
|
||||
"Comment, when used at the start of a line": "Commentaire, lorsque utilisé en début de ligne",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Entrer les masques de filtrage, un par ligne.",
|
||||
"Error": "Erreur",
|
||||
"External File Versioning": "Gestion externe des versions de fichiers",
|
||||
"File Pull Order": "Ordre d'envoi de fichier",
|
||||
"File Versioning": "Versions de fichier",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Les bits de permission de fichier sont ignorés lors de la recherche de changements. Utilisé sur les systèmes de fichiers FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Les fichiers sont déplacés, avec horodatage, dans un dossier .stversions quand ils sont remplacés ou supprimés par 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.": "Les fichiers sont protégés des changements réalisés sur les autres appareils, mais les changements réalisés sur cet appareil seront transférés au reste du groupe.",
|
||||
"Folder ID": "ID du répertoire",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Recherche globale",
|
||||
"Global Discovery Server": "Serveur global de recherche",
|
||||
"Global State": "État global",
|
||||
"Help": "Aide",
|
||||
"Ignore": "Ignorer",
|
||||
"Ignore Patterns": "Modèles à éviter",
|
||||
"Ignore Permissions": "Ignorer les permissions",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Initiateur",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
|
||||
"Keep Versions": "Conserver les versions",
|
||||
"Largest First": "Les plus volumineux en premier",
|
||||
"Last File Received": "Dernier fichier reçu",
|
||||
"Last seen": "Dernière apparition",
|
||||
"Later": "Plus tard",
|
||||
"Local Discovery": "Recherche locale",
|
||||
"Local State": "État local",
|
||||
"Major Upgrade": "Mise à jour majeure",
|
||||
"Maximum Age": "Ancienneté maximum",
|
||||
"Metadata Only": "Métadonnées uniquement",
|
||||
"Move to top of queue": "Déplacer en haut de la file",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Jamais",
|
||||
"New Device": "Nouvelle machine",
|
||||
"New Folder": "Nouveau dossier",
|
||||
"Newest First": "Les plus récents en premier",
|
||||
"No": "Non",
|
||||
"No File Versioning": "Pas de version de fichier",
|
||||
"Notice": "Notification",
|
||||
"OK": "OK",
|
||||
"Off": "Éteint",
|
||||
"Oldest First": "Les plus anciens en premier",
|
||||
"Out Of Sync": "Non synchronisé",
|
||||
"Out of Sync Items": "Objets non synchronisés",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
|
||||
"Override Changes": "Écraser les changements",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin du dossier sur l'ordinateur local. Il sera créé si il n'existe pas. Le caractère tilde (~) peut être utilisé comme raccourci vers",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Veuillez consulter les notes de version avant de réaliser une mise à jour majeure.",
|
||||
"Please wait": "Merci de patienter",
|
||||
"Preview": "Aperçu",
|
||||
"Preview Usage Report": "Aperçu du rapport de statistiques d'utilisation",
|
||||
"Quick guide to supported patterns": "Guide rapide des masques supportés",
|
||||
"RAM Utilization": "Utilisation de la RAM",
|
||||
"Random": "Aléatoire",
|
||||
"Release Notes": "Notes de version",
|
||||
"Rescan": "Rescanner",
|
||||
"Rescan All": "Re-analyser tout",
|
||||
"Rescan Interval": "Intervalle de scan",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Extinction terminée",
|
||||
"Simple File Versioning": "Suivi simple des versions de fichier",
|
||||
"Single level wildcard (matches within a directory only)": "Astérisque à un seul niveau (correspond uniquement à l’intérieur du dossier)",
|
||||
"Smallest First": "Les plus petits en premier",
|
||||
"Source Code": "Code source",
|
||||
"Staggered File Versioning": "Versions échelonnées de fichier",
|
||||
"Start Browser": "Démarrer le navigateur web",
|
||||
@@ -134,7 +150,7 @@
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être éteint, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble avoir un problème pour traiter votre demande. S'il vous plaît, rafraîchissez la page ou redémarrer Syncthing si le problème persiste.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Les statistiques agrégées sont disponibles publiquement à l'adresse {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuration a été sauvée mais pas activée. Syncthing doit redémarrer afin d'activer la nouvelle configuration.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuration a été enregistrée mais pas activée. Syncthing doit redémarrer afin d'activer la nouvelle configuration.",
|
||||
"The device ID cannot be blank.": "L'ID de l'appareil ne peut être vide.",
|
||||
"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 de l'appareil à renseigner peut être trouvé dans le menu \"Éditer > Montrer l'ID\" des autres nœuds. Les espaces et les tirets sont optionnels (ils seront ignorés).",
|
||||
"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.": "Le rapport d'utilisation chiffré est envoyé quotidiennement. Il sert à répertorier les plateformes utilisées, la taille des répertoires et les versions de l'application. Si les données rapportées sont modifiées cette boite de dialogue vous redemandera votre confirmation.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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.": "Les intervalles suivant sont utilisés: la première heure une version est conservée chaque 30 secondes, le premier jour une version est conservée chaque heure, les premiers 30 jours une version est conservée chaque jour, jusqu'à la limite d'âge maximum une version est conservée chaque semaine.",
|
||||
"The maximum age must be a number and cannot be blank.": "L'ancienneté maximum doit être un nombre et ne peut être vide.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Le temps maximum de conservation d'une version (en jours, mettre à 0 pour conserver les versions pour toujours)",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Le nombre d'anciennes versions à garder, par fichier.",
|
||||
"The number of versions must be a number and cannot be blank.": "Le nombre de versions doit être numérique, et ne peut pas être vide.",
|
||||
"The path cannot be blank.": "Le chemin ne peut pas être vide.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "L'intervalle d'analyse ne doit pas être un nombre négatif de secondes.",
|
||||
"This is a major version upgrade.": "Ceci est une mise à jour majeure",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Inconnu",
|
||||
"Unshared": "Non partagé",
|
||||
"Unused": "Non utilisé",
|
||||
"Up to Date": "Synchronisation à jour",
|
||||
"Upgrade": "Mise à jour",
|
||||
"Upgrade To {%version%}": "Mettre à jour vers {{version}}",
|
||||
"Upgrading": "Mise à jour de Syncthing",
|
||||
"Upload Rate": "Débit d'envoi",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,13 +12,15 @@
|
||||
"Addresses": "Címek",
|
||||
"All Data": "Minden adat",
|
||||
"Allow Anonymous Usage Reporting?": "Engedélyezed a névtelen felhasználási adatok küldését?",
|
||||
"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": "ABC rendben",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Egy külső program kezeli a fájl verziózást. El kell távolítsa a fájlt a szinkronizált mappából.",
|
||||
"Anonymous Usage Reporting": "Névtelen felhasználási adatok küldése",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Minden eszköz ami a bevezető eszközön lett beállítva hozzá lesz adva ehhez az eszközhöz is.",
|
||||
"Automatic upgrades": "Automatikus frissítés",
|
||||
"Bugs": "Hibák",
|
||||
"CPU Utilization": "Processzor használat",
|
||||
"Changelog": "Változások",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Bezárás",
|
||||
"Command": "Parancs",
|
||||
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
|
||||
@@ -23,7 +28,7 @@
|
||||
"Connection Error": "Kapcsolódási hiba",
|
||||
"Copied from elsewhere": "Másolva máshonnan",
|
||||
"Copied from original": "Másolva az eredetiről",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 az alábbi Közreműködők",
|
||||
"Delete": "Törlés",
|
||||
"Device ID": "Eszköz azonosító",
|
||||
"Device Identification": "Eszköz azonosító",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Figyelmen kívül hagyáshoz ide írhatod a mintákat, soronként egyet",
|
||||
"Error": "Hiba",
|
||||
"External File Versioning": "Külső fájl verziózás",
|
||||
"File Pull Order": "Fájl küldési sorrend",
|
||||
"File Versioning": "Fájl verziózás",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "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.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszereken használatakor.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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 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.": "A fájlok védve vannak a más eszközökön történt változásokkal szemben, de az ezen az eszközön történt változások érvényesek lesznek a többire.",
|
||||
"Folder ID": "Mappa azonosító",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Globális felfedezés",
|
||||
"Global Discovery Server": "Globális felfedező szerver",
|
||||
"Global State": "Globális állapot",
|
||||
"Help": "Segítség",
|
||||
"Ignore": "Visszautasítás",
|
||||
"Ignore Patterns": "Figyelmen kívül hagyás",
|
||||
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Bevezető",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "A feltétel ellentéte (pl. ki nem hagyás)",
|
||||
"Keep Versions": "Megtartott verziók",
|
||||
"Largest First": "Nagyobb először",
|
||||
"Last File Received": "Utolsó beérkezett fájl",
|
||||
"Last seen": "Utoljára látva",
|
||||
"Later": "Később",
|
||||
"Local Discovery": "Helyi felfedezés",
|
||||
"Local State": "Helyi állapot",
|
||||
"Major Upgrade": "Főverzió frissítés",
|
||||
"Maximum Age": "Maximális kor",
|
||||
"Metadata Only": "Csak metaadatok",
|
||||
"Move to top of queue": "Sor elejére mozgatás",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Soha",
|
||||
"New Device": "Új eszköz",
|
||||
"New Folder": "Új mappa",
|
||||
"Newest First": "Újabb először",
|
||||
"No": "Nem",
|
||||
"No File Versioning": "Nincs fájl verziózás",
|
||||
"Notice": "Megjegyzés",
|
||||
"OK": "Rendben",
|
||||
"Off": "Kikapcsolva",
|
||||
"Oldest First": "Régebbi először",
|
||||
"Out Of Sync": "Nincs szinkronban",
|
||||
"Out of Sync Items": "Nem szinkronizált elemek",
|
||||
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
|
||||
"Override Changes": "Változtatások felülbírálása",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "A mappa elérési útja az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Elérési út ahol a verziók tárolásra kerülnek (szabadon hagyva az alapértelmezett .stversions mappa lesz használva)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Kérlek, várj",
|
||||
"Preview": "Előnézet",
|
||||
"Preview Usage Report": "Felhasználási adatok átnézése",
|
||||
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
|
||||
"RAM Utilization": "Memória használat",
|
||||
"Random": "Véletlenszerű",
|
||||
"Release Notes": "Kiadási megjegyzések",
|
||||
"Rescan": "Átnézés",
|
||||
"Rescan All": "Összes átnézése",
|
||||
"Rescan Interval": "Átnézési intervallum",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Leállítás",
|
||||
"Simple File Versioning": "Egyszerű fájl verziókövetés",
|
||||
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
|
||||
"Smallest First": "Kisebb előbb",
|
||||
"Source Code": "Forráskód",
|
||||
"Staggered File Versioning": "Többszintű fájl verziókövetés",
|
||||
"Start Browser": "Böngésző indítása",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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.": "A következő intervallumokat használjuk: egy régi verziót őrzünk meg az első órában minden 30 másodpercben, az első nap minden órában, az első 30 napban minden nap, egészen addig amíg el nem érjük a maximálisan megtartható verziók számát minden héten.",
|
||||
"The maximum age must be a number and cannot be blank.": "A maximális kornak számnak kell lenni és nem lehet üres",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "A verziók megtartásának maximális ideje (napokban, ha 0-t adsz meg örökre megmaradnak).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "A megtartott régi verziók száma, fájlonként.",
|
||||
"The number of versions must be a number and cannot be blank.": "A megtartott verziók száma nem lehet üres",
|
||||
"The path cannot be blank.": "Elérési út nem lehet üres.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Az átnézési intervallum nullánál nagyobb másodperc érték kell legyen",
|
||||
"This is a major version upgrade.": "Ez egy főverzió frissítés.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Ismeretlen",
|
||||
"Unshared": "Nincs megosztva",
|
||||
"Unused": "Nincs használatban",
|
||||
"Up to Date": "Friss",
|
||||
"Upgrade": "Frissítés",
|
||||
"Upgrade To {%version%}": "Frissítés a {{version}} verzióra",
|
||||
"Upgrading": "Frissítés",
|
||||
"Upload Rate": "Feltöltési sebesség",
|
||||
"Uptime": "Üzemidő",
|
||||
"Use HTTPS for GUI": "HTTPS használata a GUI-hoz",
|
||||
"Version": "Verzió",
|
||||
"Versions Path": "Verziók útvonala",
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
"Add new folder?": "Aggiungere una nuova cartella?",
|
||||
"Address": "Indirizzo",
|
||||
"Addresses": "Indirizzi",
|
||||
"All Data": "Tutti i dati",
|
||||
"All Data": "Tutti i Dati",
|
||||
"Allow Anonymous Usage Reporting?": "Abilitare Statistiche Anonime di Utilizzo?",
|
||||
"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": "Alfabetico",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Il controllo versione è gestito da un comando esterno. Quest'ultimo deve rimuovere il file dalla cartella sincronizzata.",
|
||||
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Qualsiasi dispositivo configurato in un introduttore verrà aggiunto anche a questo dispositivo.",
|
||||
"Automatic upgrades": "Aggiornamenti automatici",
|
||||
"Bugs": "Bug",
|
||||
"CPU Utilization": "Utilizzo CPU",
|
||||
"Changelog": "Changelog",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Chiudi",
|
||||
"Command": "Command",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
|
||||
"Compression": "Compressione",
|
||||
"Connection Error": "Errore di Connessione",
|
||||
@@ -34,7 +39,7 @@
|
||||
"Documentation": "Documentazione",
|
||||
"Download Rate": "Velocità Download",
|
||||
"Downloaded": "Scaricato",
|
||||
"Downloading": "Sto scaricando",
|
||||
"Downloading": "Scaricamento in corso",
|
||||
"Edit": "Modifica",
|
||||
"Edit Device": "Modifica Dispositivo",
|
||||
"Edit Folder": "Modifica Cartella",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Inserisci gli schemi di esclusione, uno per riga.",
|
||||
"Error": "Errore",
|
||||
"External File Versioning": "Controllo Versione Esterno",
|
||||
"File Pull Order": "Ordine di prelievo dei file",
|
||||
"File Versioning": "Controllo Versione dei File",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Il software evita i bit dei permessi dei file durante il controllo delle modifiche. Utilizzato nei filesystem FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "I file sostituiti o eliminati da Syncthing vengono datati e spostati in una cartella .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.": "I file sono protetti dalle modifiche effettuate negli altri dispositivi, ma le modifiche effettuate in questo dispositivo verranno inviate anche al resto del cluster.",
|
||||
"Folder ID": "ID Cartella",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,34 +74,41 @@
|
||||
"Introducer": "Introduttore",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversione della condizione indicata (ad es. non escludere)",
|
||||
"Keep Versions": "Versioni Mantenute",
|
||||
"Largest First": "Prima il più grande",
|
||||
"Last File Received": "Ultimo File Ricevuto",
|
||||
"Last seen": "Ultima connessione",
|
||||
"Later": "Più Tardi",
|
||||
"Local Discovery": "Individuazione Locale",
|
||||
"Local State": "Stato Locale",
|
||||
"Major Upgrade": "Aggiornamento principale",
|
||||
"Maximum Age": "Durata Massima",
|
||||
"Metadata Only": "Solo i metadati",
|
||||
"Metadata Only": "Solo i Metadati",
|
||||
"Move to top of queue": "Posiziona in cima alla coda",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Metacarattere multi-livello (corrisponde alle cartelle e alle sotto-cartelle)",
|
||||
"Never": "Mai",
|
||||
"New Device": "Nuovo Dispositivo",
|
||||
"New Folder": "Nuova Cartella",
|
||||
"Newest First": "Prima il più recente",
|
||||
"No": "No",
|
||||
"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",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite Velocità in Uscita (KiB/s)",
|
||||
"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 wait": "Attendere prego",
|
||||
"Preview": "Anteprima",
|
||||
"Preview Usage Report": "Anteprima Statistiche di Utilizzo",
|
||||
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
|
||||
"RAM Utilization": "Utilizzo RAM",
|
||||
"Random": "Casuale",
|
||||
"Release Notes": "Note di rilascio",
|
||||
"Rescan": "Riscansiona",
|
||||
"Rescan All": "Riscansiona Tutto",
|
||||
"Rescan Interval": "Intervallo Scansione",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Arresto Eseguito",
|
||||
"Simple File Versioning": "Controllo Versione Semplice",
|
||||
"Single level wildcard (matches within a directory only)": "Metacarattere di singolo livello (corrisponde solo all'interno di una cartella)",
|
||||
"Smallest First": "Prima il più piccolo",
|
||||
"Source Code": "Codice Sorgente",
|
||||
"Staggered File Versioning": "Controllo Versione Cadenzato",
|
||||
"Start Browser": "Avvia Browser",
|
||||
@@ -139,7 +155,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).": "Trova l'ID nella finestra di dialogo \"Modifica > Mostra ID\" dell'altro dispositivo, poi inseriscilo qui. Gli spazi e i trattini sono opzionali (ignorati).",
|
||||
"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.": "Quotidianamente il software invia le statistiche di utilizzo in forma criptata. Questi dati riguardano i sistemi operativi utilizzati, le dimensioni delle cartelle e le versioni del software. Se i dati riportati sono cambiati, verrà mostrata di nuovo questa finestra di dialogo.",
|
||||
"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 dispositivo inserito non sembra valido. Dovrebbe essere una stringa di 52 o 56 caratteri costituita da lettere e numeri, con spazi e trattini opzionali.",
|
||||
"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.": "Il primo parametro della riga di comando è il percorso della cartella e il secondo parametro è il percorso relativo nella cartella.",
|
||||
"The folder ID cannot be blank.": "L'ID della cartella non può essere vuoto.",
|
||||
"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 della cartella dev'essere un identificatore breve (64 caratteri o meno) costituito solamente da lettere, numeri, punti (.), trattini (-) e trattini bassi (_).",
|
||||
"The folder ID must be unique.": "L'ID della cartella dev'essere unico.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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.": "Vengono utilizzati i seguenti intervalli temporali: per la prima ora viene mantenuta una versione ogni 30 secondi, per il primo giorno viene mantenuta una versione ogni ora, per i primi 30 giorni viene mantenuta una versione al giorno, successivamente viene mantenuta una versione ogni settimana fino al periodo massimo impostato.",
|
||||
"The maximum age must be a number and cannot be blank.": "La durata massima dev'essere un numero e non può essere vuoto.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La durata massima di una versione (in giorni, imposta a 0 per mantenere le versioni per sempre).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Il numero di vecchie versioni da mantenere, per file.",
|
||||
"The number of versions must be a number and cannot be blank.": "Il numero di versioni dev'essere un numero e non può essere vuoto.",
|
||||
"The path cannot be blank.": "Il percorso non può essere vuoto.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "L'intervallo di scansione deve essere un numero superiore a zero secondi.",
|
||||
"This is a major version upgrade.": "Questo è un aggiornamento di versione principale",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Unshared": "Non Condiviso",
|
||||
"Unused": "Non Utilizzato",
|
||||
"Up to Date": "Sincronizzato",
|
||||
"Upgrade": "Aggiornamento",
|
||||
"Upgrade To {%version%}": "Aggiorna alla {{version}}",
|
||||
"Upgrading": "Aggiornamento",
|
||||
"Upload Rate": "Velocità Upload",
|
||||
"Uptime": "Tempo di funzionamento",
|
||||
"Use HTTPS for GUI": "Utilizza HTTPS per l'interfaccia grafica",
|
||||
"Version": "Versione",
|
||||
"Versions Path": "Percorso Cartella Versioni",
|
||||
|
||||
194
gui/assets/lang/lang-ko-KR.json
Normal file
194
gui/assets/lang/lang-ko-KR.json
Normal file
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "새로운 메이저 버전은 이전 버전과 호환되지 않을 수 있습니다.",
|
||||
"API Key": "API 키",
|
||||
"About": " 정보",
|
||||
"Actions": "동작",
|
||||
"Add": "추가",
|
||||
"Add Device": "기기 추가",
|
||||
"Add Folder": "폴더 추가",
|
||||
"Add new folder?": "새로운 폴더를 추가하시겠습니까?",
|
||||
"Address": "주소",
|
||||
"Addresses": "주소",
|
||||
"All Data": "전체 데이터",
|
||||
"Allow Anonymous Usage Reporting?": "익명 사용 보고서를 보내시겠습니까?",
|
||||
"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": "자동 업데이트",
|
||||
"Bugs": "버그",
|
||||
"CPU Utilization": "CPU 사용률",
|
||||
"Changelog": "바뀐 점",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "닫기",
|
||||
"Command": "커맨드",
|
||||
"Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
|
||||
"Compression": "압축",
|
||||
"Connection Error": "연결 에러",
|
||||
"Copied from elsewhere": "다른 곳에서 복사됨",
|
||||
"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}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
|
||||
"Devices": "기기",
|
||||
"Disconnected": "연결 끊김",
|
||||
"Documentation": "문서",
|
||||
"Download Rate": "다운로드 속도",
|
||||
"Downloaded": "다운로드됨",
|
||||
"Downloading": "다운로드 중",
|
||||
"Edit": "편집",
|
||||
"Edit Device": "기기 편집",
|
||||
"Edit Folder": "폴더 편집",
|
||||
"Editing": "편집",
|
||||
"Enable UPnP": "UPnP 활성화",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "주소 자동 검색을 하기 위해서는 \"ip:port\" 형식의 주소들을 쉽표로 구분해서 입력하거나 \"dynamic\"을 입력하세요.",
|
||||
"Enter ignore patterns, one per line.": "무시할 패턴을 한 줄에 하나씩 입력하세요.",
|
||||
"Error": "오류",
|
||||
"External File Versioning": "외부 파일 버전 관리",
|
||||
"File Pull Order": "파일 동기화 순서",
|
||||
"File Versioning": "파일 버전 관리",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "파일을 동기화할 때 파일 권한이 무시됩니다. FAT 파일 시스템에서 사용하세요.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "파일이 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": "폴더 ID",
|
||||
"Folder Master": "폴더 소유자",
|
||||
"Folder Path": "폴더 경로",
|
||||
"Folders": "폴더",
|
||||
"GUI Authentication Password": "GUI 인증 비밀번호",
|
||||
"GUI Authentication User": "GUI 인증 사용자",
|
||||
"GUI Listen Addresses": "GUI 주소",
|
||||
"Generate": "생성",
|
||||
"Global Discovery": "글로벌 탐색",
|
||||
"Global Discovery Server": "글로벌 탐색 서버",
|
||||
"Global State": "글로벌 서버 상태",
|
||||
"Help": "도움말",
|
||||
"Ignore": "무시",
|
||||
"Ignore Patterns": "패턴 무시",
|
||||
"Ignore Permissions": "권한 무시",
|
||||
"Incoming Rate Limit (KiB/s)": "다운로드 속도 제한 (KiB/S)",
|
||||
"Introducer": "유도",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "주어진 조건의 반대(전혀 배제하지 않음)",
|
||||
"Keep Versions": "버전 보관",
|
||||
"Largest First": "큰 파일 순",
|
||||
"Last File Received": "마지막으로 받은 파일",
|
||||
"Last seen": "마지막 접속",
|
||||
"Later": "나중에",
|
||||
"Local Discovery": "로컬 노드 검색",
|
||||
"Local State": "로컬 상태",
|
||||
"Major Upgrade": "메이저 업데이트",
|
||||
"Maximum Age": "최대 보존 기간",
|
||||
"Metadata Only": "메타데이터만",
|
||||
"Move to top of queue": "대기열 상단으로 이동",
|
||||
"Multi level wildcard (matches multiple directory levels)": "다중 레벨 와일드 카드 (여러 단계의 디렉토리와 일치하는 경우)",
|
||||
"Never": "사용 안 함",
|
||||
"New Device": "새 기기",
|
||||
"New Folder": "새 폴더",
|
||||
"Newest First": "새로운 파일순",
|
||||
"No": "아니오",
|
||||
"No File Versioning": "파일 버전 관리 안 함",
|
||||
"Notice": "공지",
|
||||
"OK": "확인",
|
||||
"Off": "꺼짐",
|
||||
"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 wait": "기다려 주십시오",
|
||||
"Preview": "미리보기",
|
||||
"Preview Usage Report": "사용 보고서 미리보기",
|
||||
"Quick guide to supported patterns": "지원하는 패턴에 대한 빠른 도움말",
|
||||
"RAM Utilization": "RAM 사용량",
|
||||
"Random": "무작위",
|
||||
"Release Notes": "릴리즈 노트",
|
||||
"Rescan": "재탐색",
|
||||
"Rescan All": "전체 재탐색",
|
||||
"Rescan Interval": "재탐색 간격",
|
||||
"Restart": "재시작",
|
||||
"Restart Needed": "재시작 필요함",
|
||||
"Restarting": "재시작 중",
|
||||
"Reused": "재개",
|
||||
"Save": "저장",
|
||||
"Scanning": "탐색중",
|
||||
"Select the devices to share this folder with.": "이 폴더를 공유할 장치를 선택합니다.",
|
||||
"Select the folders to share with this device.": "이 장치와 공유할 폴더를 선택합니다.",
|
||||
"Settings": "설정",
|
||||
"Share": "공유",
|
||||
"Share Folder": "폴더 공유",
|
||||
"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.": "간단한 폴더 식별자입니다. 모든 장치에서 동일해야 합니다.",
|
||||
"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 updated to the name the device advertises if left empty.": "아이디가 비어있는 경우 기본 값으로 다른 장치에 업데이트 됩니다.",
|
||||
"Shutdown": "종료",
|
||||
"Shutdown Complete": "종료 완료",
|
||||
"Simple File Versioning": "간단한 파일 버전 관리",
|
||||
"Single level wildcard (matches within a directory only)": "단일 레벨 와일드카드 (하나의 디렉토리만 일치하는 경우)",
|
||||
"Smallest First": "작은 파일순",
|
||||
"Source Code": "소스 코드",
|
||||
"Staggered File Versioning": "타임스탬프 기준 파일 버전 관리",
|
||||
"Start Browser": "브라우저 열기",
|
||||
"Stopped": "중지됨",
|
||||
"Support": "지원",
|
||||
"Sync Protocol Listen Addresses": "동기화 프로토콜 수신 주소",
|
||||
"Syncing": "동기화 중",
|
||||
"Syncthing has been shut down.": "Syncthing이 종료되었습니다.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing은 다음과 같은 소프트웨어나 그 일부를 포함합니다:",
|
||||
"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을 재시작해 보세요.",
|
||||
"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.": "기기 ID는 비워 둘 수 없습니다.",
|
||||
"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).": "여기에 입력한 기기 ID가 다른 장치의 \"편집 - ID 보기\"에 표시됩니다. 공백과 하이픈은 세지 않습니다. 즉 무시됩니다.",
|
||||
"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.": "입력한 기기 ID가 올바르지 않습니다. 52/56자의 알파벳과 숫자로 구성되어 있으며, 공백과 하이픈은 포함되지 않습니다.",
|
||||
"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.": "폴더 ID는 비워 둘 수 없습니다.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "폴더 ID는 문자, 숫자, 마침표(.), 붙임표(-), 밑줄 문자(_)로만 구성되어 있는 짧은 식별자(64자 이하)여야 합니다.",
|
||||
"The folder ID must be unique.": "폴더 ID는 중복될 수 없습니다.",
|
||||
"The folder path cannot be blank.": "폴더 경로는 비워 둘 수 없습니다.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "다음과 같은 간격이 사용됩니다: 첫 한 시간 동안은 버전이 매 30초마다 유지되며, 첫 하루 동안은 매 시간, 첫 한 달 동안은 매 일마다 유지됩니다. 그리고 최대 날짜까지는 버전이 매 주마다 유지됩니다.",
|
||||
"The maximum age must be a number and cannot be blank.": "최대 보존 기간은 숫자여야 하며 비워 둘 수 없습니다.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "버전을 유지할 최대 시간을 지정합니다. 일단위이며 버전을 계속 유지하려면 0을 입력하세요,",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "각 파일별로 유지할 이전 버전의 개수를 지정합니다.",
|
||||
"The number of versions must be a number and cannot be blank.": "버전 개수는 숫자여야 하며 비워 둘 수 없습니다.",
|
||||
"The path cannot be blank.": "경로는 비워 둘 수 없습니다.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "재검색 간격은 초단위이며 양수로 입력해야 합니다.",
|
||||
"This is a major version upgrade.": "이 업데이트는 메이저 버전입니다.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "알 수 없음",
|
||||
"Unshared": "공유되지 않음",
|
||||
"Unused": "사용되지 않음",
|
||||
"Up to Date": "최신 데이터",
|
||||
"Upgrade": "업데이트",
|
||||
"Upgrade To {%version%}": "{{version}} 으로 업데이트",
|
||||
"Upgrading": "업데이트 중",
|
||||
"Upload Rate": "업로드 속도",
|
||||
"Uptime": "가동 시간",
|
||||
"Use HTTPS for GUI": "GUI에서 HTTPS 프로토콜 사용",
|
||||
"Version": "버전",
|
||||
"Versions Path": "버전 저장 경로",
|
||||
"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 folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
|
||||
"Yes": "예",
|
||||
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
|
||||
"full documentation": "전체 문서",
|
||||
"items": "항목",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다."
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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ą",
|
||||
@@ -9,21 +12,23 @@
|
||||
"Addresses": "Adresai",
|
||||
"All Data": "Visiems duomenims",
|
||||
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
|
||||
"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": "Abėcėlės tvarka",
|
||||
"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",
|
||||
"Bugs": "Klaidos",
|
||||
"CPU Utilization": "Procesoriaus panaudojimas",
|
||||
"Changelog": "Pasikeitimai",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Uždaryti",
|
||||
"Command": "Command",
|
||||
"Command": "Komanda",
|
||||
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
|
||||
"Compression": "Kompresija",
|
||||
"Connection Error": "Susijungimo klaida",
|
||||
"Copied from elsewhere": "Nukopijuota iš betkur",
|
||||
"Copied from original": "Nukopijuota iš originalo",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Visos teisės saugomos © 2015 šių bendraautorių:",
|
||||
"Delete": "Trinti",
|
||||
"Device ID": "Įrenginio ID",
|
||||
"Device Identification": "Įrenginio identifikacija",
|
||||
@@ -43,10 +48,12 @@
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "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",
|
||||
@@ -59,6 +66,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ų",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Supažindintojas",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Apversti sąlygas (pvz.: nenustoti naudoti)",
|
||||
"Keep Versions": "Saugojamų versijų kiekis",
|
||||
"Largest First": "Didžiausi pirmiau",
|
||||
"Last File Received": "Paskutinis priimtas failas",
|
||||
"Last seen": "Paskutinį kartą matytas",
|
||||
"Later": "Vėliau",
|
||||
"Local Discovery": "Vietinis matomumas",
|
||||
"Local State": "Vietinė būsena",
|
||||
"Major Upgrade": "Stambus atnaujinimas",
|
||||
"Maximum Age": "Maksimalus amžius",
|
||||
"Metadata Only": "Metaduomenims",
|
||||
"Move to top of queue": "Perkelti į eilės priekį",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Niekada",
|
||||
"New Device": "Naujas įrenginys",
|
||||
"New Folder": "Naujas aplankas",
|
||||
"Newest First": "Naujausi pirmiau",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Nėra versijų valdymo",
|
||||
"Notice": "Įspėjimas",
|
||||
"OK": "Gerai",
|
||||
"Off": "Netaikoma",
|
||||
"Oldest First": "Seniausi pirmiau",
|
||||
"Out Of Sync": "Nesutikrinta",
|
||||
"Out of Sync Items": "Nesutikrinta",
|
||||
"Outgoing Rate Limit (KiB/s)": "Išeinančio srauto maksimalus greitis (KiB/s)",
|
||||
"Override Changes": "Perrašyti pakeitimus",
|
||||
"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": "Kelias iki aplanko šiame kompiuteryje. Bus sukurtas, jei neegzistuoja. Tildės simbolis (~) gali būti naudojamas kaip trumpinys",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kelias, kur bus saugomos versijos (palikite tuščią numatytam .stversions aplankui).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Peržvelkite laidos informaciją prieš atlikdami stambų atnaujinimą.",
|
||||
"Please wait": "Prašome palaukti",
|
||||
"Preview": "Peržiūra",
|
||||
"Preview Usage Report": "Vartojimo statistikos peržiūra",
|
||||
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
|
||||
"RAM Utilization": "Atminties naudojimas",
|
||||
"Random": "Atsitiktinė",
|
||||
"Release Notes": "Laidos Informacija",
|
||||
"Rescan": "Nuskaityti iš naujo",
|
||||
"Rescan All": "Nuskaityti visus aplankus",
|
||||
"Rescan Interval": "Pertrauka tarp nuskaitymų",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Sėkmingai išjungta",
|
||||
"Simple File Versioning": "Supaprastintas versijų valdymas",
|
||||
"Single level wildcard (matches within a directory only)": "Vieno lygio pakaitos (atitinka tik vieną direktorijos lygį)",
|
||||
"Smallest First": "Mažiausi pirmiau",
|
||||
"Source Code": "Išeities kodas",
|
||||
"Staggered File Versioning": "Pakopinis versijų valdymas",
|
||||
"Start Browser": "Paleisti naršyklę",
|
||||
@@ -132,14 +148,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.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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.": "Šie pertraukų nustatymai naudojami: pirmą valandą versijos laikomos 30 sekundžių, pirmą dieną versijos laikomos valandą, pirmas 30 dienų versijos laikomos parą, kol nebus viršytas nustatytas maksimalus amžius.",
|
||||
"The maximum age must be a number and cannot be blank.": "Maksimalus amžius turi būti skaitmuo ir negali būti tuščias laukelis.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimalus laikas kurį bus saugojama versija (dienomis, nustatykite 0 norėdami saugoti amžinai).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Kiek failo versijų saugoti.",
|
||||
"The number of versions must be a number and cannot be blank.": "Versijų skaičius turi būti skaitmuo ir negali būti tuščias laukelis.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"The path cannot be blank.": "Kelias negali būti tuščias.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
|
||||
"This is a major version upgrade.": "Tai yra stambus atnaujinimas.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Nežinoma",
|
||||
"Unshared": "Nesidalinama",
|
||||
"Unused": "Nenaudojamas",
|
||||
"Up to Date": "Atnaujinta",
|
||||
"Upgrade": "Atnaujinimas",
|
||||
"Upgrade To {%version%}": "Atnaujinti į {{version}}",
|
||||
"Upgrading": "Atnaujinama",
|
||||
"Upload Rate": "Išsiuntimo greitis",
|
||||
"Uptime": "Veiksnumo laikas",
|
||||
"Use HTTPS for GUI": "Valdymo skydeliui naudoti saugų ryšį ",
|
||||
"Version": "Versija",
|
||||
"Versions Path": "Kelias iki versijos",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,13 +12,15 @@
|
||||
"Addresses": "Adresser",
|
||||
"All Data": "Alle data",
|
||||
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
|
||||
"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",
|
||||
"Bugs": "Programfeil",
|
||||
"CPU Utilization": "CPU-utnyttelse",
|
||||
"Changelog": "Endringslog",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Lukk",
|
||||
"Command": "Kommando",
|
||||
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
|
||||
@@ -44,9 +49,11 @@
|
||||
"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": "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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "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",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,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": "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": "Hovedoppgradering",
|
||||
"Maximum Age": "Maksimal Levetid",
|
||||
"Metadata Only": "Kun metadata",
|
||||
"Move to top of queue": "Flytt til topp av kø",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Aldri",
|
||||
"New Device": "Ny Enhet",
|
||||
"New Folder": "Ny Mappe",
|
||||
"Newest First": "Den nyeste først",
|
||||
"No": "Nei",
|
||||
"No File Versioning": "Ingen Versjonskontroll",
|
||||
"Notice": "Merknad",
|
||||
"OK": "OK",
|
||||
"Off": "Av",
|
||||
"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.": "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": "TIlfeldig",
|
||||
"Release Notes": "Utgivelsesnotat",
|
||||
"Rescan": "Skann på nytt",
|
||||
"Rescan All": "Skann alt på nytt",
|
||||
"Rescan Interval": "Skanneintervall",
|
||||
@@ -120,6 +135,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": "Den minste først",
|
||||
"Source Code": "Kildekode",
|
||||
"Staggered File Versioning": "Forskjøvet Versjonskontroll",
|
||||
"Start Browser": "Start Nettleser",
|
||||
@@ -139,7 +155,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.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Følgende intervall blir brukt: den første timen blir en versjon lagret hvert 30. sekund, den første dagen blir en versjon lagret hver time, de første 30 dagene blir en versjon lagret hver dag, og inntil maksimal levetid blir en versjon lagret hver uke.",
|
||||
"The maximum age must be a number and cannot be blank.": "Maksimal levetid må være et tall og kan ikke være tomt.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimal tid å beholde en versjon (i dager, sett til 0 for å beholde versjoner på ubegrenset tid).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Antall gamle versjoner å beholde, per fil.",
|
||||
"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.": "Dette er en hovedoppgradering",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Ukjent",
|
||||
"Unshared": "Ikke delt",
|
||||
"Unused": "Ikke i bruk",
|
||||
"Up to Date": "Oppdatert",
|
||||
"Upgrade": "Oppgradere",
|
||||
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
|
||||
"Upgrading": "Oppgraderer",
|
||||
"Upload Rate": "Opplastingsrate",
|
||||
"Uptime": "Oppetid",
|
||||
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
|
||||
"Version": "Versjon",
|
||||
"Versions Path": "Plassering Av Versjoner",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,13 +12,15 @@
|
||||
"Addresses": "Adressen",
|
||||
"All Data": "Alle Gegevens",
|
||||
"Allow Anonymous Usage Reporting?": "Bijhouden van anonieme gebruikers statistieken toestaan?",
|
||||
"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": "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",
|
||||
"Changelog": "Logboek",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Sluiten",
|
||||
"Command": "Commando",
|
||||
"Comment, when used at the start of a line": "Commentaar, indien gebruikt aan het begin van de lijn",
|
||||
@@ -23,9 +28,9 @@
|
||||
"Connection Error": "Verbindingsfout",
|
||||
"Copied from elsewhere": "Van elders gekopieerd",
|
||||
"Copied from original": "Gekopieerd van het origineel",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"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?",
|
||||
@@ -43,12 +48,14 @@
|
||||
"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": "External File Versioning",
|
||||
"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.": "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.": "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",
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "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",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,34 +74,41 @@
|
||||
"Introducer": "Introductietoestel",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversie van de gegeven voorwaarde (bv. niet uitsluiten)",
|
||||
"Keep Versions": "Versies behouden",
|
||||
"Largest First": "Grootste eerst",
|
||||
"Last File Received": "Laatste ontvangen bestand",
|
||||
"Last seen": "Laatst gezien op",
|
||||
"Later": "Later",
|
||||
"Local Discovery": "Lokaal zoeken",
|
||||
"Local State": "Lokale status",
|
||||
"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",
|
||||
"New Folder": "Nieuwe Folder",
|
||||
"Newest First": "Nieuwste eerst",
|
||||
"No": "Nee",
|
||||
"No File Versioning": "Geen versiebeheer",
|
||||
"Notice": "Notificatie",
|
||||
"OK": "OK",
|
||||
"Off": "Uit",
|
||||
"Oldest First": "Oudste eerst",
|
||||
"Out Of Sync": "Niet gesynchroniseerd",
|
||||
"Out of Sync Items": "Niet gesynchroniseerde objecten",
|
||||
"Outgoing Rate Limit (KiB/s)": "Uitgaande snelheidslimiet (KiB/s)",
|
||||
"Override Changes": "Veranderingen overschrijven",
|
||||
"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": "Locatie van de folder op de lokale computer. Zal aangemaakt worden wanneer deze niet bestaat. De tilde (~) kan gebruikt in plaats van",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Locatie waar de versies opgeslagen moeten worden (leeg laten voor de standaard .stversions subfolder).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Lees eerst de release notes voordat u een grote update uitvoert.",
|
||||
"Please wait": "Even geduld",
|
||||
"Preview": "Voorbeeld",
|
||||
"Preview Usage Report": "Bekijk gebruiksstatistieken",
|
||||
"Quick guide to supported patterns": "Snelgids voor ondersteunde patronen",
|
||||
"RAM Utilization": "RAM gebruik",
|
||||
"Random": "Willekeurig",
|
||||
"Release Notes": "Release notes",
|
||||
"Rescan": "Opnieuw scannen",
|
||||
"Rescan All": "Scan Alles Opnieuw",
|
||||
"Rescan Interval": "Scanfrequentie",
|
||||
@@ -112,14 +127,15 @@
|
||||
"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",
|
||||
"Single level wildcard (matches within a directory only)": "Wildcard op enkel niveau (toepasbaar binnen een enkele folder)",
|
||||
"Smallest First": "Kleinste eerst",
|
||||
"Source Code": "Broncode",
|
||||
"Staggered File Versioning": "Gelaagd versiebeheer",
|
||||
"Start Browser": "Start browser",
|
||||
@@ -132,38 +148,44 @@
|
||||
"Syncthing is restarting.": "Syncthing is aan het herstarten.",
|
||||
"Syncthing is upgrading.": "Syncthing is aan het upgraden.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing lijkt afgesloten te zijn, of er is een verbindingsprobleem met het internet. Nieuwe poging....",
|
||||
"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.": "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 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.": "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 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.": "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.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "De maximale tijdsduur om een versie te bewaren (in dagen, gebruik 0 om versies voor altijd te bewaren).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Het aantal versies dat bewaard moet worden per file.",
|
||||
"The number of versions must be a number and cannot be blank.": "Het aantal nummers moet een getal zijn en mag niet leeg blijven.",
|
||||
"The path cannot be blank.": "U dient een locatie in te voeren.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "De scanfrequentie moet een positief getal in seconden zijn.",
|
||||
"This is a major version upgrade.": "Dit is een grote update.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Onbekend",
|
||||
"Unshared": "Niet gedeeld",
|
||||
"Unused": "Ongebruikt",
|
||||
"Up to Date": "Gesynchroniseerd",
|
||||
"Upgrade": "Update",
|
||||
"Upgrade To {%version%}": "Upgrade naar {{version}}",
|
||||
"Upgrading": "Bezig met upgrade",
|
||||
"Upload Rate": "Upload snelheid",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "Gebruik HTTPS voor de GUI",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Adresser",
|
||||
"All Data": "Alle dataa",
|
||||
"Allow Anonymous Usage Reporting?": "Tillata anonymisert bruksrapportering?",
|
||||
"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": "Anonymisert bruksrapportering",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Einingar konfigurert på ei introduksjonseining vil òg verta lagt til denne eininga.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Programfeil",
|
||||
"CPU Utilization": "CPU-utnytting",
|
||||
"Changelog": "Endringslogg",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Lukk",
|
||||
"Command": "Kommando",
|
||||
"Comment, when used at the start of a line": "Kommentar, når brukt i starten av linja",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelatast, eitt per linje.",
|
||||
"Error": "Feilmelding",
|
||||
"External File Versioning": "Ekstern filutgåvehandtering",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "Filutgåvekontroll",
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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 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.": "Filer er beskytta mot endringar gjort på andre einingar, men endringar gjort på denne eininga vert sende til resten av klyngja.",
|
||||
"Folder ID": "Mappe ID",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Introduktør",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Det motsette av den gitte tilstanden (dvs. ekskluder ikkje)",
|
||||
"Keep Versions": "Behald Versjonar",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Siste mottatte fila",
|
||||
"Last seen": "Sist sett",
|
||||
"Later": "Seinare",
|
||||
"Local Discovery": "Lokal oppdaging",
|
||||
"Local State": "Lokal Tilstand",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maksimal Levetid",
|
||||
"Metadata Only": "Berre metadata",
|
||||
"Move to top of queue": "Flytt øvst i køen",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Aldri",
|
||||
"New Device": "Ny eining",
|
||||
"New Folder": "Ny mappe",
|
||||
"Newest First": "Newest First",
|
||||
"No": "Nei",
|
||||
"No File Versioning": "Ingen versjonskontroll",
|
||||
"Notice": "Merknad",
|
||||
"OK": "OK",
|
||||
"Off": "Av",
|
||||
"Oldest First": "Oldest First",
|
||||
"Out Of Sync": "Ikkje synkronisert",
|
||||
"Out of Sync Items": "Ikkje-synkroniserte element",
|
||||
"Outgoing Rate Limit (KiB/s)": "Utgåande hastigheitsgrense (KiB/s)",
|
||||
"Override Changes": "Overstyr endringar",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Plasseringa av mappa på datamaskinen. Vert oppretta om ho ikkje finst. Krøllstrekteiknet (~) kan brukast som forkorting for",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringa for lagra versjonar (la denne vera tom for å bruka standard .stversions-mappa i mappa).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Gjer vel og vent",
|
||||
"Preview": "Førehandsvisning",
|
||||
"Preview Usage Report": "Førehandsvis bruksrapporten",
|
||||
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
|
||||
"RAM Utilization": "Minnebruk",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Skann På Ny",
|
||||
"Rescan All": "Skann alle på nytt",
|
||||
"Rescan Interval": "Skanneintervall",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Slått av",
|
||||
"Simple File Versioning": "Enkel versjonskontroll",
|
||||
"Single level wildcard (matches within a directory only)": "Enkeltnivå-jokerteikn (søkjer berre i éi mappe)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Kildekode",
|
||||
"Staggered File Versioning": "Forskuva versjonskontroll",
|
||||
"Start Browser": "Start Nettlesar",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Desse intervalla vert nytta: den fyrste timen vert ei utgåve lagra kvart 30. sekund, den fyrste dagen vert ei utgåve lagra kvar time, dei fyrste 30 dagane vert ei utgåve lagra kvar dag, og inntil høgaste alderen vert ei utgåve lagra kvar veke.",
|
||||
"The maximum age must be a number and cannot be blank.": "Maksimal levetid må vera eit tal og kan ikkje vera tomt.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Høgaste tidsrom å behalda ei utgåve (i dagar, set til 0 for å behalda versjonane for alltid).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Tal på gamle versjonar ein skal behalda, per fil.",
|
||||
"The number of versions must be a number and cannot be blank.": "Tal på versjonar må vera eit tal og kan ikkje vera tomt.",
|
||||
"The path cannot be blank.": "Bana kan ikkje vera tom.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Talet på sekund i skanneintervallet kan ikkje vera negativt.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Ukjent",
|
||||
"Unshared": "Ikkje delt",
|
||||
"Unused": "Ubrukt",
|
||||
"Up to Date": "Oppdatert",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
|
||||
"Upgrading": "Oppgraderer",
|
||||
"Upload Rate": "Opplastingsfart",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "Bruk HTTPS ved grafisk grensesnitt",
|
||||
"Version": "Versjon",
|
||||
"Versions Path": "Utgåvebane",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Adresy",
|
||||
"All Data": "Wszystkie dane",
|
||||
"Allow Anonymous Usage Reporting?": "Zezwalaj na anonimowe statystyki użycia",
|
||||
"Alphabetic": "Alfabetycznie",
|
||||
"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": "Anonimowe statystyki użycia",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Wszystkie urządzenia skonfigurowane na urządzeniu wprowadzającym zostaną dodane także do tego urządzenia.",
|
||||
@@ -16,12 +20,13 @@
|
||||
"Bugs": "Błędy",
|
||||
"CPU Utilization": "Użycie CPU",
|
||||
"Changelog": "Historia zmian",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Zamknij",
|
||||
"Command": "Command",
|
||||
"Command": "Polecenie",
|
||||
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
|
||||
"Compression": "Kompresja",
|
||||
"Connection Error": "Błąd połączenia",
|
||||
"Copied from elsewhere": "Copied from elsewhere",
|
||||
"Copied from elsewhere": "Skopiowane z innego miejsca ",
|
||||
"Copied from original": "Skopiowane z oryginału",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Delete": "Usuń",
|
||||
@@ -43,9 +48,11 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Wprowadź adresy oddzielone przecinkiem \"ip:port\", lub wpisz \"dynamic\" w celu wykrycia adresu. ",
|
||||
"Enter ignore patterns, one per line.": "Wprowadz wzorce ignorowania, jeden w każdej linii.",
|
||||
"Error": "Błąd",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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 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",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Wprowadzający",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Odwrócenie podanego wzorca (np. nie wykluczaj)",
|
||||
"Keep Versions": "Zachowuj wersje",
|
||||
"Largest First": "Największe na początku",
|
||||
"Last File Received": "Ostatni otrzymany plik",
|
||||
"Last seen": "Ostatnio widziany",
|
||||
"Later": "Później",
|
||||
"Local Discovery": "Lokalne odnajdywanie",
|
||||
"Local State": "Status lokalny",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maksymalny wiek",
|
||||
"Metadata Only": "Tylko metadane",
|
||||
"Move to top of queue": "Przenieś na początek kolejki",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Nigdy",
|
||||
"New Device": "Nowe urządzenie",
|
||||
"New Folder": "Nowy folder",
|
||||
"Newest First": "Najnowsze na początku",
|
||||
"No": "Nie",
|
||||
"No File Versioning": "Bez wersjonowania pliku",
|
||||
"Notice": "Wskazówka",
|
||||
"OK": "OK",
|
||||
"Off": "Wyłącz",
|
||||
"Oldest First": "Najstarsze na początku",
|
||||
"Out Of Sync": "Niezsynchronizowane",
|
||||
"Out of Sync Items": "Niezsynchronizowane pliki",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ograniczenie prędkości wysyłania (KiB/s)",
|
||||
"Override Changes": "Nadpisz zmiany",
|
||||
"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": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ścieżka gdzie będą przechowywane wersje (pozostaw puste dla domyślnego folderu .stversions)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Proszę czekać",
|
||||
"Preview": "Podgląd",
|
||||
"Preview Usage Report": "Podgląd raportu użycia.",
|
||||
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
|
||||
"RAM Utilization": "Użycie pamięci RAM",
|
||||
"Random": "Losowo",
|
||||
"Release Notes": "Informacje o wydaniu",
|
||||
"Rescan": "Skanuj ponownie",
|
||||
"Rescan All": "Skanuj wszystko ponownie",
|
||||
"Rescan Interval": "Interwał skanowania",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Wyłączanie ukończone",
|
||||
"Simple File Versioning": "Proste wersjonowanie pliku",
|
||||
"Single level wildcard (matches within a directory only)": "Wieloznaczność na poziomie plików (uwzględnia nazwy plików)",
|
||||
"Smallest First": "Najmniejsze na początku",
|
||||
"Source Code": "Kod źródłowy",
|
||||
"Staggered File Versioning": "Rozbudowane wersjonowanie pliku",
|
||||
"Start Browser": "Uruchom przeglądarkę",
|
||||
@@ -132,7 +148,7 @@
|
||||
"Syncthing is restarting.": "Restart Syncthing",
|
||||
"Syncthing is upgrading.": "Aktualizowanie Syncthing",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing wydaje się być wyłączony lub jest problem z twoim połączeniem internetowym. Próbuje ponownie...",
|
||||
"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.": "Syncthing nie może przetworzyć twojego zapytania. Proszę przeładuj stronę lub zrestartuj Syncthing, jeśli problem pozostanie.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Zebrane statystyki są publicznie dostępna pod adresem {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfiguracja została zapisana lecz nie jest aktywna. Syncthing musi zostać zrestartowany aby aktywować nową konfiguracje.",
|
||||
"The device ID cannot be blank.": "ID urządzenia nie może być puste.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"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.": "Następujący interwał jest używany: dla pierwszej godziny wersja jest zachowywana co 30 sekund, dla pierwszego dnia wersja jest zachowywana co godzinę, dla pierwszego miesiąca wersja jest zachowywana codziennie, aż do maksymalnego odstępu zapisywania wersji co tydzień.",
|
||||
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być liczbą i nie może być pusty.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Liczba wersji pliku do zachowania.",
|
||||
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być liczbą i nie może być pusta.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"The path cannot be blank.": "Ścieżka nie może być pusta.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Nieznany",
|
||||
"Unshared": "Nieudostępnione",
|
||||
"Unused": "Nieużywane",
|
||||
"Up to Date": "Aktualny",
|
||||
"Upgrade": "Aktualizacja",
|
||||
"Upgrade To {%version%}": "Aktualizuj do {{version}}",
|
||||
"Upgrading": "Aktualizowanie",
|
||||
"Upload Rate": "Prędkość wysyłania",
|
||||
"Uptime": "Czas działania",
|
||||
"Use HTTPS for GUI": "Używaj HTTPS",
|
||||
"Version": "Wersja",
|
||||
"Versions Path": "Ścieżka wersji",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Endereços",
|
||||
"All Data": "Todos os dados",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anônimos de uso?",
|
||||
"Alphabetic": "Alfabética",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Um comando externo controla o versionamento. Ele tem que remover o arquivo da pasta sincronizada.",
|
||||
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados em um apresentador também serão adicionados a este dispositivo.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Erros",
|
||||
"CPU Utilization": "Uso de CPU",
|
||||
"Changelog": "Registro de alterações",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Fechar",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentário, se usado no início de uma linha",
|
||||
@@ -43,9 +48,11 @@
|
||||
"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.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Os arquivos são renomeados com suas datas na pasta .stversions quando são substituídos ou removidos pelo Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
|
||||
"Folder ID": "ID da pasta",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Apresentador",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversão de uma condição (ou seja, não excluir)",
|
||||
"Keep Versions": "Manter versões",
|
||||
"Largest First": "Maior primeiro",
|
||||
"Last File Received": "Último arquivo recebido",
|
||||
"Last seen": "Visto por último em",
|
||||
"Later": "Depois",
|
||||
"Local Discovery": "Descoberta local",
|
||||
"Local State": "Estado local",
|
||||
"Major Upgrade": "Atualização \"major\"",
|
||||
"Maximum Age": "Idade máxima",
|
||||
"Metadata Only": "Somente metadados",
|
||||
"Move to top of queue": "Mover para o topo da lista",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Nunca",
|
||||
"New Device": "Novo dispositivo",
|
||||
"New Folder": "Nova Pasta",
|
||||
"Newest First": "Mais novo primeiro",
|
||||
"No": "Não",
|
||||
"No File Versioning": "Sem versionamento de arquivos",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Off": "Desligada",
|
||||
"Oldest First": "Mais antigo primeiro",
|
||||
"Out Of Sync": "Não sincronizado",
|
||||
"Out of Sync Items": "Itens não sincronizados",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite de velocidade de envio (KiB/s)",
|
||||
"Override Changes": "Sobrescrever mudanças",
|
||||
"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 na máquina local. Será criado caso não exista. O caractere til (~) pode ser usado como um atalho para",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "O caminho onde as versões serão salvas (deixe vazio para usar a pasta padrão .stversions dentro desta pasta).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Por favor, consulte as notas de lançamento antes de atualizar para uma versão \"major\".",
|
||||
"Please wait": "Aguarde",
|
||||
"Preview": "Visualizar",
|
||||
"Preview Usage Report": "Visualizar relatório de uso",
|
||||
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
|
||||
"RAM Utilization": "Uso de RAM",
|
||||
"Random": "Aleatória",
|
||||
"Release Notes": "Notas de lançamento",
|
||||
"Rescan": "Verificar agora",
|
||||
"Rescan All": "Verificar todas",
|
||||
"Rescan Interval": "Intervalo entre verificações",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Desligamento completado",
|
||||
"Simple File Versioning": "Versionamento simples de arquivos",
|
||||
"Single level wildcard (matches within a directory only)": "Coringa de único nível (faz corresponder apenas dentro de uma pasta)",
|
||||
"Smallest First": "Menor primeiro",
|
||||
"Source Code": "Código-fonte",
|
||||
"Staggered File Versioning": "Versionamento escalonado de arquivos",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
@@ -137,7 +153,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.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
|
||||
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico e não pode ficar vazio.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "O número máximo de dias em que uma versão é guardada. (Use 0 para manter para sempre).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "O número de versões antigas a serem mantidas, por arquivo.",
|
||||
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numério e não pode ficar vazio.",
|
||||
"The path cannot be blank.": "O caminho não pode ficar vazio.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações deve ser um número positivo de segundos.",
|
||||
"This is a major version upgrade.": "Esta é uma atualização para uma versão \"major\".",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Desconhecida",
|
||||
"Unshared": "Não compartilhada",
|
||||
"Unused": "Não utilizado",
|
||||
"Up to Date": "Sincronizada",
|
||||
"Upgrade": "Atualização",
|
||||
"Upgrade To {%version%}": "Atualizar para {{version}}",
|
||||
"Upgrading": "Atualizando",
|
||||
"Upload Rate": "Velocidade de envio",
|
||||
"Uptime": "Tempo ligado",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
|
||||
"Version": "Versão",
|
||||
"Versions Path": "Caminho das versões",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "Um número negativo de dias não faz sentido.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Endereços",
|
||||
"All Data": "Todos os dados",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anónimos de utilização?",
|
||||
"Alphabetic": "Alfabética",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Um comando externo trata do controle de versões. Esse comando tem que remover o ficheiro da pasta sincronizada.",
|
||||
"Anonymous Usage Reporting": "Enviar relatórios anónimos de utilização",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados num dispositivo apresentador serão também adicionados a este dispositivo.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Erros",
|
||||
"CPU Utilization": "Utilização da CPU",
|
||||
"Changelog": "Registo de alterações",
|
||||
"Clean out after": "Limpar e arrumar após a acção",
|
||||
"Close": "Fechar",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
|
||||
@@ -43,9 +48,11 @@
|
||||
"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",
|
||||
"File Versioning": "Gestão de versões",
|
||||
"External File Versioning": "Externa",
|
||||
"File Pull Order": "Ordem de obtenção de ficheiros",
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Os ficheiros são movidos para a pasta .stversions quando substituídos ou eliminados pelo Syncthing.",
|
||||
"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.",
|
||||
"Folder ID": "ID da pasta",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,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": "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 importante",
|
||||
"Maximum Age": "Idade máxima",
|
||||
"Metadata Only": "Metadados apenas",
|
||||
"Move to top of queue": "Mover para o topo da fila",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Nunca",
|
||||
"New Device": "Novo dispositivo",
|
||||
"New Folder": "Nova pasta",
|
||||
"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": "Primeiro os mais antigos",
|
||||
"Out Of Sync": "Não sincronizado",
|
||||
"Out of Sync Items": "Itens não sincronizados",
|
||||
"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).",
|
||||
"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ória",
|
||||
"Release Notes": "Notas de lançamento",
|
||||
"Rescan": "Verificar agora",
|
||||
"Rescan All": "Verificar todas agora",
|
||||
"Rescan Interval": "Intervalo entre verificações",
|
||||
@@ -118,10 +133,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": "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",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
|
||||
"The maximum age must be a number and cannot be blank.": "A idade máxima tem que ser um número e não pode estar vazia.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Tempo máximo para manter uma versão (em dias, use 0 para manter a versão para sempre).",
|
||||
"The number of days must be a number and cannot be blank.": "O número de dias tem que ser um número e não pode estar em branco.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "O número de dias a manter os ficheiros na reciclagem. Zero significa para sempre.",
|
||||
"The number of old versions to keep, per file.": "O número de versões antigas a manter, por ficheiro.",
|
||||
"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 para uma versão importante.",
|
||||
"Trash Can File Versioning": "Reciclagem",
|
||||
"Unknown": "Desconhecido",
|
||||
"Unshared": "Não partilhada",
|
||||
"Unused": "Não utilizado",
|
||||
"Up to Date": "Sincronizado",
|
||||
"Upgrade": "Actualizar",
|
||||
"Upgrade To {%version%}": "Actualizar para {{version}}",
|
||||
"Upgrading": "Actualizando",
|
||||
"Upload Rate": "Velocidade de envio",
|
||||
"Uptime": "Tempo em actividade",
|
||||
"Use HTTPS for GUI": "Utilizar HTTPS na interface gráfica",
|
||||
"Version": "Versão",
|
||||
"Versions Path": "Caminho das versões",
|
||||
|
||||
194
gui/assets/lang/lang-ro-RO.json
Normal file
194
gui/assets/lang/lang-ro-RO.json
Normal file
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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ă",
|
||||
"Add new folder?": "Adauga o mapă nouă?",
|
||||
"Address": "Adresă",
|
||||
"Addresses": "Adrese",
|
||||
"All Data": "Toate Datele",
|
||||
"Allow Anonymous Usage Reporting?": "Permiteţi raportarea anonimă de folosire a aplicaţiei?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "O comandă externă administrează versiunile. Trebuie să şteargă documentul din fişierul sincronizat. ",
|
||||
"Anonymous Usage Reporting": "Raport Anonim despre Folosirea Aplicației",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Toate dispozitivele configurate pe un dispozitiv iniţiator vor fi adăugate şi pe acest dispozitiv. ",
|
||||
"Automatic upgrades": "Actualizare automată",
|
||||
"Bugs": "Bug-uri",
|
||||
"CPU Utilization": "CPU ",
|
||||
"Changelog": "Noutăți",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Închide",
|
||||
"Command": "Comandă",
|
||||
"Comment, when used at the start of a line": "Comentariu, când este folosit la începutul unei linii",
|
||||
"Compression": "Compresie",
|
||||
"Connection Error": "Eroare de conexiune",
|
||||
"Copied from elsewhere": "Copiat din altă parte",
|
||||
"Copied from original": "Copiat din original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright ©2015 Următorii Contribuitori:",
|
||||
"Delete": "Şterge",
|
||||
"Device ID": "ID Dispozitiv",
|
||||
"Device Identification": "Identificare Dispozitiv",
|
||||
"Device Name": "Nume Dispozitiv",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Dispozitivul{{dispoztiv}}({{adresă}})vrea sa se conecteze.Adaug un dispozitiv nou?",
|
||||
"Devices": "Dispozitiv",
|
||||
"Disconnected": "Deconectat",
|
||||
"Documentation": "Documentaţie",
|
||||
"Download Rate": "Viteză de Descărcare",
|
||||
"Downloaded": "Descărcat",
|
||||
"Downloading": "Se descarcă",
|
||||
"Edit": "Modifică",
|
||||
"Edit Device": "Modifică Dispozitiv",
|
||||
"Edit Folder": "Modifică Mapa",
|
||||
"Editing": "Modificare",
|
||||
"Enable UPnP": "Activează UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Adaugă, separate prin virgulă, adresele IP \"ip:port\" sau \"dynamic\" (dinamic) pentru ca adresele să fie descoperite automat.",
|
||||
"Enter ignore patterns, one per line.": "Adaugă șabloanele de ignorare, câte una pe linie.",
|
||||
"Error": "Eroare",
|
||||
"External File Versioning": "Administrare externă a versiunilor documentului",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "Versiune Fișier",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Biții de autorizare sînt excluși cînd se analizează modificările. A se utiliza pe sisteme FAT. ",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Documentele sînt mutate într-un fișier .stversions conținînd versiuni datate atunci cînd sînt șterse sau înlocuite de 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.": "Fișierele sunt protejate de schimbările făcute pe alte dispozitive dar schimbările efectuate pe acest dispozitiv vor fi trimise catre restul grupului.",
|
||||
"Folder ID": "ID Mapă",
|
||||
"Folder Master": "Master Măpi",
|
||||
"Folder Path": "Locaţie Mapei",
|
||||
"Folders": "Mapă",
|
||||
"GUI Authentication Password": "Parolă Interfaţă",
|
||||
"GUI Authentication User": "User Interfaţă",
|
||||
"GUI Listen Addresses": "Adresă Interfaţă",
|
||||
"Generate": "Generează",
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Limită Viteză de Download (KB/s)",
|
||||
"Introducer": "Dispozitiv Inițiator",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversarea condiției (de ex., nu exclude)",
|
||||
"Keep Versions": "Păstrează Versiuni",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Ultimul Fișier Primit",
|
||||
"Last seen": "Ultima vizionare",
|
||||
"Later": "Mai tîrziu",
|
||||
"Local Discovery": "Găsire Locală",
|
||||
"Local State": "Status Local",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Vârsta Maximă",
|
||||
"Metadata Only": "Doar Metadate",
|
||||
"Move to top of queue": "Mută la începutul listei",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Asterisc de nivel multiplu (corespunde fișierelor și sub-fișierelor)",
|
||||
"Never": "Niciodată",
|
||||
"New Device": "Dispozitiv Nou",
|
||||
"New Folder": "Mapă Nouă",
|
||||
"Newest First": "Newest First",
|
||||
"No": "Nu",
|
||||
"No File Versioning": "Fără versiuni ale documentelor",
|
||||
"Notice": "Mențiuni",
|
||||
"OK": "OK",
|
||||
"Off": "Închis",
|
||||
"Oldest First": "Oldest First",
|
||||
"Out Of Sync": "Nesincronizat",
|
||||
"Out of Sync Items": "Elemente Nesincronizate",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limită Viteză de Upload (KB/s)",
|
||||
"Override Changes": "Suprascrie Schimbări",
|
||||
"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": "Localizarea fișierului în acest computer. Dacă nu există, va fi creat. Tilda (~) înlocuiește ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Locul unde vor fi stocate versiunile (a se lăsa neschimbat pentru fișierul .stversions din fișier). ",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Aşteaptă",
|
||||
"Preview": "Previzualizează",
|
||||
"Preview Usage Report": "Vezi raportul de utilizare",
|
||||
"Quick guide to supported patterns": "Ghid rapid pentru regulile suportate",
|
||||
"RAM Utilization": "RAM",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Rescanează",
|
||||
"Rescan All": "Rescaneaza Tot",
|
||||
"Rescan Interval": "Interval Scanare",
|
||||
"Restart": "Restart",
|
||||
"Restart Needed": "Restart Necesar",
|
||||
"Restarting": "Se restartează",
|
||||
"Reused": "Refolosit",
|
||||
"Save": "Salvează",
|
||||
"Scanning": "Scanează",
|
||||
"Select the devices to share this folder with.": "Selectează dispozitivele pentru care să fie disponibil această mapă.",
|
||||
"Select the folders to share with this device.": "Alege mapele pe care vrei sa le imparți cu acest dispozitiv.",
|
||||
"Settings": "Setări",
|
||||
"Share": "Împarte",
|
||||
"Share Folder": "Împarte Mapa",
|
||||
"Share Folders With Device": "Împarte Mapa Cu Dispozitivul",
|
||||
"Share With Devices": "Împarte Cu Dispozitivul",
|
||||
"Share this folder?": "Împarte această mapă?",
|
||||
"Shared With": "Împarte Cu",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificator scurt al fișierului. Trebuie să fie același pe toate mașinile. ",
|
||||
"Show ID": "Arată ID",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vizibil în locul ID-ului dispozitivului într-un grup. Va fi sugerat celorlalte dispozitive ca nume opţional. ",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vizibil în locul ID-ului dispozitivului într-un grup. Va fi înlocuit de numele sugerat de dispozitiv daca nu este completat. ",
|
||||
"Shutdown": "Opreşte",
|
||||
"Shutdown Complete": "Oprește Complet",
|
||||
"Simple File Versioning": "Versiuni simple ale documentelor",
|
||||
"Single level wildcard (matches within a directory only)": "Asterisc de nivel simplu (corespunde doar unui fişier)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Cod Sursă",
|
||||
"Staggered File Versioning": "Versiuni eşalonate ale documentelor",
|
||||
"Start Browser": "Lansează Browser",
|
||||
"Stopped": "Oprit",
|
||||
"Support": "Suport Tehnic",
|
||||
"Sync Protocol Listen Addresses": "Adresa protocolului de sincronizare",
|
||||
"Syncing": "Se sincronizează",
|
||||
"Syncthing has been shut down.": "Sincronizarea a fost oprită.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing include următoarele soft-uri sau părţi din ele:",
|
||||
"Syncthing is restarting.": "Syncthing se restartează.",
|
||||
"Syncthing is upgrading.": "Syncthing se actualizează.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing pare a fi oprit sau aveţi probleme cu conexiunea la internet. Reluare... ",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing pare a avea probleme prelucrînd solicitarea dumneavoastră. Reîncărcaţi pagina sau porniţi Syncthing din nou dacă problema continuă. ",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Statisticile în ansamblu sînt accesibile public la {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Configuraţia a fost salvată dar nu şi activată. Syncthing trebuie să repornească pentru a activa noua configuraţie.",
|
||||
"The device ID cannot be blank.": "ID-ul dispozitivului nu poate fi gol.",
|
||||
"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).": "ID-ul dispozitivului ce trebuie introdus aici poate fi aflat la \"Editează > Arată ID-ul\" pe celălalt dispozitiv. Spaţiile şi liniile oblice sînt opţionale (vor fi ignorate). ",
|
||||
"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.": "Raportul codat de utilizare este trimit zilnic. Este folosit pentru studierea platformelor comune, dimensiunilor fişierelor şi versiunea aplicaţiilor. În cazul în care setul de date trimis este modificat, acest dialog va aparea din nou. ",
|
||||
"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.": "ID-ul dispozitivului nu pare a fi valid.El trebuie sa fie format dintrun șir din 52 ori 56 de caractere formate din litere și cifre, cu spatii și linii opțional.",
|
||||
"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.": "ID-ul mapei nu poate fi gol.",
|
||||
"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.": "ID-ul mapei trebuie să fie unic.",
|
||||
"The folder path cannot be blank.": "Locaţia mapei nu poate fi goală.",
|
||||
"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.": "Vârsta maximă trebuie să fie un număr şi nu poate fi goală.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Câte zile să se păstreze o versiune (setează 0 pentru nelimitat)",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Numărul de versiuni vechi de salvat per fişier.",
|
||||
"The number of versions must be a number and cannot be blank.": "Numărul de versiuni trebuie să fie un număr şi nu poate fi gol.",
|
||||
"The path cannot be blank.": "Locația nu poate fi goală.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Intervalul de rescanare trebuie să nu fie un număr negativ de secunde. ",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Necunoscut",
|
||||
"Unshared": "Neîmpărțit",
|
||||
"Unused": "Nefolosit",
|
||||
"Up to Date": "La Zi",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Actualizează La Versiunea {{version}}",
|
||||
"Upgrading": "Se Actualizează",
|
||||
"Upload Rate": "Viteză Upload",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "Foloseşte HTTPS pentru interfaţă",
|
||||
"Version": "Versiune",
|
||||
"Versions Path": "Locaţie Versiuni",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versiunile sînt şterse în mod automat dacă sînt mai vechi decît vîrsta maximă sau depăşesc numărul de documente permise într-un anume interval. ",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Când adaugi un dispozitiv nou, trebuie să adaugi şi dispozitivul curent în dispozitivul nou.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cînd adăugaţi un fişier nou, nu uitaţi că ID-ul fişierului va rămîne acelaşi pe toate dispozitivele. Iar literele mari sînt diferite de literele mici. ",
|
||||
"Yes": "Da",
|
||||
"You must keep at least one version.": "Trebuie să păstrezi cel puţin o versiune.",
|
||||
"full documentation": "toată documentaţia",
|
||||
"items": "obiecte",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{Dispozitivul}} vrea să transmită mapa {{Mapa}}"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "Новое обновление основной версии может быть несовместимо с предыдущими версиями.",
|
||||
"API Key": "Ключ API",
|
||||
"About": "О программе",
|
||||
"Actions": "Действия",
|
||||
"Add": "Добавить",
|
||||
"Add Device": "Добавить устройство",
|
||||
"Add Folder": "Добавить папку",
|
||||
@@ -9,21 +12,23 @@
|
||||
"Addresses": "Адреса",
|
||||
"All Data": "Все данные",
|
||||
"Allow Anonymous Usage Reporting?": "Разрешить сбор анонимной статистики использования?",
|
||||
"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": "По алфавиту",
|
||||
"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": "Автообновление",
|
||||
"Bugs": "Ошибки",
|
||||
"CPU Utilization": "Загрузка ЦПУ",
|
||||
"Changelog": "Журнал изменений",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Закрыть",
|
||||
"Command": "Command",
|
||||
"Command": "Команда",
|
||||
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
|
||||
"Compression": "Сжатие",
|
||||
"Connection Error": "Ошибка подключения",
|
||||
"Copied from elsewhere": "Скопировано из другого места",
|
||||
"Copied from original": "Скопировано с оригинала",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Все права защищены ©, 2015 участники:",
|
||||
"Delete": "Удалить",
|
||||
"Device ID": "ID устройства",
|
||||
"Device Identification": "Идентификация устройства",
|
||||
@@ -37,16 +42,18 @@
|
||||
"Downloading": "Загрузка",
|
||||
"Edit": "Изменить",
|
||||
"Edit Device": "Изменить устройство",
|
||||
"Edit Folder": "Изменение папки",
|
||||
"Edit Folder": "Изменить папку",
|
||||
"Editing": "Редактирование",
|
||||
"Enable UPnP": "Включить UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": " Введите пары \"IP:PORT\" разделённые запятыми, или слово \"dynamic\" для автоматического обнаружения адреса.",
|
||||
"Enter ignore patterns, one per line.": "Введите шаблоны игнорирования, по-одному на строку.",
|
||||
"Enter ignore patterns, one per line.": "Введите шаблоны игнорирования, по одному на строку.",
|
||||
"Error": "Ошибка",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"External File Versioning": "Внешний контроль версий файлов",
|
||||
"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.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Права на файлы игнорируются при поиске изменений. Используется на файловой системе FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Файлы с временнОй меткой версии помещаются в папку .stversions при их замене или удалении 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.": "Файлы защищены от изменений сделанных на других устройствах, но изменения сделанные на этом устройстве будут отправлены всему кластеру.",
|
||||
"Folder ID": "ID папки",
|
||||
"Folder Master": "Папка-оригинал",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Глобальное обнаружение",
|
||||
"Global Discovery Server": "Сервер глобального обнаружения",
|
||||
"Global State": "Глобальное состояние",
|
||||
"Help": "Помощь",
|
||||
"Ignore": "Игнорировать",
|
||||
"Ignore Patterns": "Шаблоны игнорирования",
|
||||
"Ignore Permissions": "Игнорировать файловые права доступа",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Рекомендатель",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Инвертировать текущее условие (например, исключить)",
|
||||
"Keep Versions": "Количество хранимых версий",
|
||||
"Largest First": "Сначала большие",
|
||||
"Last File Received": "Последний полученный файл",
|
||||
"Last seen": "Был доступен",
|
||||
"Later": "Потом",
|
||||
"Local Discovery": "Локальное обнаружение",
|
||||
"Local State": "Локальное состояние",
|
||||
"Major Upgrade": "Обновление основной версии",
|
||||
"Maximum Age": "Максимальный срок",
|
||||
"Metadata Only": "Только метаданные",
|
||||
"Move to top of queue": "Поместить в начало очереди",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Никогда",
|
||||
"New Device": "Новое устройство",
|
||||
"New Folder": "Новая папка",
|
||||
"Newest First": "Сначала новые",
|
||||
"No": "Нет",
|
||||
"No File Versioning": "Без управления версиями файлов",
|
||||
"Notice": "Внимание",
|
||||
"OK": "ОК",
|
||||
"Off": "Отключить",
|
||||
"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 в папке).",
|
||||
"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 wait": "Пожалуйста, подождите",
|
||||
"Preview": "Предварительный просмотр",
|
||||
"Preview Usage Report": "Посмотреть отчёт об использовании",
|
||||
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
|
||||
"RAM Utilization": "Использование ОЗУ",
|
||||
"Random": "Случайно",
|
||||
"Release Notes": "Замечания к версии",
|
||||
"Rescan": "Пересканирование",
|
||||
"Rescan All": "Пересканировать все",
|
||||
"Rescan Interval": "Интервал пересканирования",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Выключено",
|
||||
"Simple File Versioning": "Простое управление версиями файлов",
|
||||
"Single level wildcard (matches within a directory only)": "Одноуровневая маска (поиск совпадений только внутри папки)",
|
||||
"Smallest First": "Сначала маленькие",
|
||||
"Source Code": "Исходный код",
|
||||
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
|
||||
"Start Browser": "Открыть браузер",
|
||||
@@ -132,14 +148,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 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 столкнулся с проблемой при обработке Вашего запроса. Пожалуйста, обновите страницу или перезапустите 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.": "ID устройства не может быть пустым.",
|
||||
"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).": "Идентификатор устройства для ввода здесь, может быть найден в диалоге \"Редактирование > Показать ID\" на другом устройстве. Пробелы и тире не обязательны (игнорируются).",
|
||||
"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).": "Идентификатор устройства, который следует тут ввести, может быть найден в диалоге \"Редактирование > Показать ID\" на другом устройстве. Пробелы и тире не обязательны (игнорируются).",
|
||||
"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.": "Введённое ID устройства не валидное. Оно должно состоять из букв и цифр, может включать пробелы и дефисы, его длина должна быть от 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 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.": "Введён недопустимый ID устройства. Он должен состоять из букв и цифр, может включать пробелы и дефисы, длина должна быть от 52 до 56 символов, ",
|
||||
"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.": "ID папки не может быть пустым.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID папки должен быть коротким (не более 64 символов), должен состоять только из букв, цифр, точек (.), дефисов (-) или подчёркиваний (_).",
|
||||
"The folder ID must be unique.": "ID папки должен быть уникальным.",
|
||||
@@ -147,23 +163,29 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Используются следующие интервалы: в первый час версия меняется каждые 30 секунд, в первый день - каждый час, первые 30 дней - каждый день, после, до максимального срока - каждую неделю.",
|
||||
"The maximum age must be a number and cannot be blank.": "Максимальный срок должен быть числом и не может быть пустым.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальный срок хранения версии (в днях, 0 значит вечное хранение).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Количество хранимых версий файла.",
|
||||
"The number of versions must be a number and cannot be blank.": "Количество версий должно быть числом и не может быть пустым.",
|
||||
"The path cannot be blank.": "The path 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.": "Это обновление основной версии продукта.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Неизвестно",
|
||||
"Unshared": "Необщедоступно",
|
||||
"Unused": "Не используется",
|
||||
"Up to Date": "Обновлено",
|
||||
"Upgrade": "Обновить",
|
||||
"Upgrade To {%version%}": "Обновить до {{version}}",
|
||||
"Upgrading": "Обновление",
|
||||
"Upload Rate": "Скорость отдачи",
|
||||
"Uptime": "Аптайм",
|
||||
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
|
||||
"Version": "Версия",
|
||||
"Versions Path": "Путь к версиям",
|
||||
"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 folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папки используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
|
||||
"Yes": "Да",
|
||||
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
|
||||
"full documentation": "полная документация",
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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": "Funktioner",
|
||||
"Add": "Lägg till",
|
||||
"Add Device": "Lägg till enhet",
|
||||
"Add Folder": "Lägg till katalog",
|
||||
"Add new folder?": "Lägg till katalog?",
|
||||
"Address": "Adress",
|
||||
"Addresses": "Adresser",
|
||||
"All Data": "All Data",
|
||||
"All Data": "All data",
|
||||
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
|
||||
"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.": "Ett externt kommando sköter versionshanteringen. Det måste ta bort filen från den synkroniserade mappen.",
|
||||
"Anonymous Usage Reporting": "Anonym användarstatistik",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurerade på en introduktörsenhet kommer också att läggas till den här enheten.",
|
||||
"Automatic upgrades": "Automatisk uppgradering",
|
||||
"Bugs": "Buggar",
|
||||
"CPU Utilization": "CPU-användning",
|
||||
"Changelog": "Changelog",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Stäng",
|
||||
"Command": "Command",
|
||||
"Command": "Kommando",
|
||||
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
|
||||
"Compression": "Compression",
|
||||
"Compression": "Komprimering",
|
||||
"Connection Error": "Anslutningsproblem",
|
||||
"Copied from elsewhere": "Kopierat utifrån",
|
||||
"Copied from original": "Oförändrat",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 följande medverkande:",
|
||||
"Delete": "Radera",
|
||||
"Device ID": "Enhets-ID",
|
||||
"Device Identification": "Enhetsidentifikation",
|
||||
@@ -43,10 +48,12 @@
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade \"ip:port\"-adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
|
||||
"Enter ignore patterns, one per line.": "Ange filmönster, ett per rad.",
|
||||
"Error": "Fel",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"External File Versioning": "Extern versionshantering",
|
||||
"File Pull Order": "Hämtningsprioritering av filer",
|
||||
"File Versioning": "Versionshantering",
|
||||
"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.": "Filrättigheter ignoreras vid sökning efter förändringar. Används på FAT-filsystem.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersatts eller raderats av Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
|
||||
"Folder ID": "Katalog-ID",
|
||||
"Folder Master": "Huvudlagring",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Global uppslagning",
|
||||
"Global Discovery Server": "Global uppslagningsserver",
|
||||
"Global State": "Global status",
|
||||
"Help": "Hjälp",
|
||||
"Ignore": "Ignorera",
|
||||
"Ignore Patterns": "Ignorerade filmönster",
|
||||
"Ignore Permissions": "Ignorera filrättigheter",
|
||||
@@ -66,36 +74,43 @@
|
||||
"Introducer": "introduktör",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Vänder på villkoret, d.v.s. exkluderar inte.",
|
||||
"Keep Versions": "Behåll versioner",
|
||||
"Largest First": "Störst först",
|
||||
"Last File Received": "Senast Mottagna Fil",
|
||||
"Last seen": "Senast online",
|
||||
"Later": "Senare",
|
||||
"Local Discovery": "Lokal uppslagning",
|
||||
"Local State": "Lokal status",
|
||||
"Major Upgrade": "Stor uppgradering",
|
||||
"Maximum Age": "Högsta åldersgräns",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
"Metadata Only": "Endast metadata",
|
||||
"Move to top of queue": "Flytta till överst i kön",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
|
||||
"Never": "Aldrig",
|
||||
"New Device": "Ny enhet",
|
||||
"New Folder": "Ny katalog",
|
||||
"Newest First": "Nyast först",
|
||||
"No": "Nej",
|
||||
"No File Versioning": "Ingen versionshantering",
|
||||
"Notice": "Observera",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
"Off": "Av",
|
||||
"Oldest First": "Äldst först",
|
||||
"Out Of Sync": "Osynkad",
|
||||
"Out of Sync Items": "Osynkade poster",
|
||||
"Outgoing Rate Limit (KiB/s)": "Max uppladdningshastighet (KiB/s)",
|
||||
"Override Changes": "Skriv över ändringar",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sökväg till katalogen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna tomt för att använda .stversions i den ordinarie katalogen).",
|
||||
"Please consult the release notes before performing a major upgrade.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
|
||||
"Please wait": "Var god vänta",
|
||||
"Preview": "Förhandsgranska",
|
||||
"Preview Usage Report": "Förhandsgranska statistik",
|
||||
"Quick guide to supported patterns": "Snabb guide till filmönster som stöds",
|
||||
"RAM Utilization": "Minnesanvändning",
|
||||
"Random": "Slumpmässig",
|
||||
"Release Notes": "versionsnyheter",
|
||||
"Rescan": "Uppdatera",
|
||||
"Rescan All": "Rescan All",
|
||||
"Rescan All": "Uppdatera alla",
|
||||
"Rescan Interval": "Uppdateringsintervall",
|
||||
"Restart": "Starta om",
|
||||
"Restart Needed": "Omstart behövs",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Avstängning klar",
|
||||
"Simple File Versioning": "Enkel versionshantering",
|
||||
"Single level wildcard (matches within a directory only)": "Jokertecken som representerar noll eller fler godtyckliga tecken i ett filnamn.",
|
||||
"Smallest First": "Minst först",
|
||||
"Source Code": "Källkod",
|
||||
"Staggered File Versioning": "Versionshantering i intervall",
|
||||
"Start Browser": "Starta browser",
|
||||
@@ -132,14 +148,14 @@
|
||||
"Syncthing is restarting.": "Syncthing startar om.",
|
||||
"Syncthing is upgrading.": "Syncthing uppgraderas.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller finns det problem med din Internetanslutning. Försöker igen...",
|
||||
"Syncthing seems to be 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.": "Syncthing verkar ha drabbats av ett problem. Uppdatera sidan eller starta om Syncthing om problemet kvarstår.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Sammanställd statistik finns publikt tillgänglig på {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
|
||||
"The device ID cannot be blank.": "Enhets-ID kan inte vara tomt.",
|
||||
"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).": "Enhets-ID som behövs här kan du hitta i \"Redigera > Visa ID\"-dialogen på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets-ID:t verkar inte korrekt. Det ska vara en 52 eller 56 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "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örsta kommandoparametern är sökvägen till mappen och den andra parametern är den relativa sökvägen i mappen.",
|
||||
"The folder ID cannot be blank.": "Ange ett enhets-ID.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Katalog-ID:t måste vara en kort sträng (64 tecken eller mindre), bestående av endast bokstäver, siffror, punkt (.), bindestreck (-) och understreck (_).",
|
||||
"The folder ID must be unique.": "Katalog-ID:t måste vara unikt.",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De följande intervallen används: varje 30 sekunder under den första timmen; varje timme under den första dagen; varje dag för de första 30 dagarna; varje vecka tills den maximala åldersgränsen uppnås.",
|
||||
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas tomt.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas, per fil.",
|
||||
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas tomt.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"The path cannot be blank.": "Ange en sökväg",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
|
||||
"This is a major version upgrade.": "Det här är en stor uppgradering.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Okänt",
|
||||
"Unshared": "Inte delad",
|
||||
"Unused": "Oanvänd",
|
||||
"Up to Date": "Helt uppdaterad",
|
||||
"Upgrade": "Uppgradering",
|
||||
"Upgrade To {%version%}": "Uppgradera till {{version}}",
|
||||
"Upgrading": "Uppgraderar",
|
||||
"Upload Rate": "Uppladdningshastighet",
|
||||
"Uptime": "Tid sedan start",
|
||||
"Use HTTPS for GUI": "Använd HTTPS för GUI",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Katalog för versioner",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"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",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Adresler",
|
||||
"All Data": "All Data",
|
||||
"Allow Anonymous Usage Reporting?": "Anonim kullanım raporlarına izin ver ?",
|
||||
"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": "Anonim Kullanım Raporlama",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Tanıtıcı bir cihazda yapılandırılan cihazlar bu cihaza da eklenecektir.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Hatalar",
|
||||
"CPU Utilization": "İşlemci Kullanımı",
|
||||
"Changelog": "Değişim Günlüğü",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Kapat",
|
||||
"Command": "Command",
|
||||
"Comment, when used at the start of a line": "Satır başında kullanıldığında açıklama özelliği taşır",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Yoksayılacak kalıp dizilerini her satıra bir tane olacak şekilde girin.",
|
||||
"Error": "Hata",
|
||||
"External File Versioning": "Harici Dosya Sürümlendirme",
|
||||
"File Pull Order": "File Pull Order",
|
||||
"File Versioning": "Dosya Sürümlendirme",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Değişimleri yoklarken dosya izin bilgilerini ihmal et. FAT dosya sistemlerinde kullanın.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Dosyalar Syncthing tarafından değiştirildiğinde ya da silindiğinde, tarih damgalı sürümleri .stversions dizinine taşınır.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosyalar diğer cihazlarda yapılan değişikliklerden korunur, ancak bu cihazdaki değişiklikler kümedeki diğer cihazlara gönderilir.",
|
||||
"Folder ID": "Klasör ID",
|
||||
@@ -59,6 +66,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",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Tanıtıcı",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Verilen koşulun ters çevirilmesi (örneğin: yok sayma)",
|
||||
"Keep Versions": "Sürüm tut",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Alınan Son Dosya",
|
||||
"Last seen": "Son Görülen",
|
||||
"Later": "Sonra",
|
||||
"Local Discovery": "Yerel bulma",
|
||||
"Local State": "Yerel Durum",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Azami Süre",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Asla",
|
||||
"New Device": "Yeni Cihaz",
|
||||
"New Folder": "Yeni Klasör",
|
||||
"Newest First": "Newest First",
|
||||
"No": "Hayır",
|
||||
"No File Versioning": "Sürümlendirme Yok",
|
||||
"Notice": "Uyarı",
|
||||
"OK": "Tamam",
|
||||
"Off": "Off",
|
||||
"Oldest First": "Oldest First",
|
||||
"Out Of Sync": "Senkronize edilmemiş",
|
||||
"Out of Sync Items": "Eşzamanlanmayan Öğeler",
|
||||
"Outgoing Rate Limit (KiB/s)": "Yükleme hız sınırı (KB/sn)",
|
||||
"Override Changes": "Değişiklikleri Geçersiz kıl",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Yerel bilgisayardaki klasöre ulaşım yolu. Dizin yoksa yaratılacak. (~) karakterinin kısayol olarak kullanılabileceği yol",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sürümlerin saklanması gereken yol (klasördeki öntanımlı .stversions klasörü için boş bırakın)",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please wait": "Lütfen Bekleyin",
|
||||
"Preview": "Önizleme",
|
||||
"Preview Usage Report": "Kullanım raporunu gözden geçir",
|
||||
"Quick guide to supported patterns": "Desteklenen kalıplar için hızlı rehber",
|
||||
"RAM Utilization": "RAM Kullanımı",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Tekrar Tara",
|
||||
"Rescan All": "Tümünü Tekrar Tara",
|
||||
"Rescan Interval": "Tarama Aralığı",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Kapatma İşlemi Tamamlandı",
|
||||
"Simple File Versioning": "Basit Dosya Sürümlendirme",
|
||||
"Single level wildcard (matches within a directory only)": "Tekli düzey wildcard (sadece bir dizin içinde eşleşme)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Kaynak Kodu",
|
||||
"Staggered File Versioning": "Aşamalı Dosya Sürümlendirme",
|
||||
"Start Browser": "Tarayıcıyı Başlat",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Kullanılan zaman aralıkları : ilk bir saat içinde her 30 saniyede, ilk günde her saatte, ilk 30 günde her gün, azami süreye kadar geçen zamanda ise her hafta yeni sürüm değeri oluşturulur.",
|
||||
"The maximum age must be a number and cannot be blank.": "Azami süre tanımı boş bırakılmamalı ve bir sayı olarak tanımlanmalıdır.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Bir sürümün tutulması için belirlenen azami süre (sürümleri daimi olarak tutabilmek için 0 değeri atayın)",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Dosya başına saklanacak eski sürüm.",
|
||||
"The number of versions must be a number and cannot be blank.": "Sürümlerin sayısı sayı olmalı ve boş bırakılamaz.",
|
||||
"The path cannot be blank.": "Dizin yolu boş bırakılamaz.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Tarama zaman aralığı, saniye cinsinden negatif olmayan bir sayı olmalıdır.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Bilinmiyor",
|
||||
"Unshared": "Paylaşılmayan",
|
||||
"Unused": "Kullanılmayan",
|
||||
"Up to Date": "Güncel",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "{{version}} sürümüne yükselt",
|
||||
"Upgrading": "Yükseltiliyor",
|
||||
"Upload Rate": "Yükleme hızı",
|
||||
"Uptime": "Uptime",
|
||||
"Use HTTPS for GUI": "GUI için HTTPS kullan",
|
||||
"Version": "Sürüm",
|
||||
"Versions Path": "Sürüm Dizini",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "Нова мажорна версія може бути несумісною із попередніми версіями.",
|
||||
"API Key": "API ключ",
|
||||
"About": "Про програму",
|
||||
"Actions": "Дії",
|
||||
"Add": "Додати",
|
||||
"Add Device": "Додати пристрій",
|
||||
"Add Folder": "Додати директорію",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "Адреси",
|
||||
"All Data": "Усі дані",
|
||||
"Allow Anonymous Usage Reporting?": "Дозволити програмі збирати анонімну статистику використання?",
|
||||
"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.": "Усі пристрої, налаштовані на пристрої-рекомендувачі, будуть додані до поточного пристрою.",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "Помилки",
|
||||
"CPU Utilization": "Навантаження CPU",
|
||||
"Changelog": "Перелік змін",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "Закрити",
|
||||
"Command": "Команда",
|
||||
"Comment, when used at the start of a line": "Коментар, якщо використовується на початку рядка",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
|
||||
"Error": "Помилка",
|
||||
"External File Versioning": "Зовнішне керування версіями",
|
||||
"File Pull Order": "Порядок витягнення файлів",
|
||||
"File Versioning": "Керування версіями",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Біти прав доступу до файлів будуть проігноровані під час пошуку змін. Використовуйте на файлових системах FAT.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "Файли будуть поміщатися у директорію .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": "ID директорії",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "Глобальне виявлення",
|
||||
"Global Discovery Server": "Сервер для глобального виявлення",
|
||||
"Global State": "Глобальний статус",
|
||||
"Help": "Допомога",
|
||||
"Ignore": "Ігнорувати",
|
||||
"Ignore Patterns": "Ігнорувати шаблони",
|
||||
"Ignore Permissions": "Ігнорувати права доступу до файлів",
|
||||
@@ -66,11 +74,13 @@
|
||||
"Introducer": "Рекомендувач",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Інверсія поточної умови (тобто не виключає)",
|
||||
"Keep Versions": "Зберігати версії",
|
||||
"Largest First": "Спершу найбільші",
|
||||
"Last File Received": "Останній завантажений файл",
|
||||
"Last seen": "З’являвся останній раз",
|
||||
"Later": "Пізніше",
|
||||
"Local Discovery": "Локальне виявлення",
|
||||
"Local State": "Локальний статус",
|
||||
"Major Upgrade": "Мажорне оновлення",
|
||||
"Maximum Age": "Максимальний вік",
|
||||
"Metadata Only": "Тільки метадані",
|
||||
"Move to top of queue": "Пересунути у початок черги",
|
||||
@@ -78,22 +88,27 @@
|
||||
"Never": "Ніколи",
|
||||
"New Device": "Новий пристрій",
|
||||
"New Folder": "Нова директорія",
|
||||
"Newest First": "Спершу новіші",
|
||||
"No": "Ні",
|
||||
"No File Versioning": "Версіонування вимкнено",
|
||||
"Notice": "Повідомлення",
|
||||
"OK": "Гаразд",
|
||||
"Off": "Вимкнути",
|
||||
"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 wait": "Будь ласка, зачекайте",
|
||||
"Preview": "Попередній перегляд",
|
||||
"Preview Usage Report": "Попередній перегляд статистичного звіту",
|
||||
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
|
||||
"RAM Utilization": "Використання RAM",
|
||||
"Random": "Випадково",
|
||||
"Release Notes": "Примітки до випуску",
|
||||
"Rescan": "Пересканувати",
|
||||
"Rescan All": "Пересканувати усе",
|
||||
"Rescan Interval": "Інтервал для повторного сканування",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "Вимикання завершене",
|
||||
"Simple File Versioning": "Просте версіонування",
|
||||
"Single level wildcard (matches within a directory only)": "Однорівнева маска (пошук збігів лише в середині директорії) ",
|
||||
"Smallest First": "Спершу найменші",
|
||||
"Source Code": "Сирцевий код",
|
||||
"Staggered File Versioning": "Поступове версіонування",
|
||||
"Start Browser": "Запустити браузер",
|
||||
@@ -147,17 +163,23 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Використовуються наступні інтервали: для першої години версія зберігається кожні 30 секунд, для першого дня версія зберігається щогодини, для перших 30 днів версія зберігається кожен день, опісля, до максимального строку, версія зберігається щотижня.",
|
||||
"The maximum age must be a number and cannot be blank.": "Максимальний термін повинен бути числом та не може бути пустим.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальний термін, щоб зберігати версію (у днях, вствновіть в 0, щоби зберігати версії назавжди).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "Кількість старих версій, яку необхідно зберігати для кожного файлу.",
|
||||
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
|
||||
"The path cannot be blank.": "Шлях не може бути порожнім.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
|
||||
"This is a major version upgrade.": "Це оновлення мажорної версії",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "Невідомо",
|
||||
"Unshared": "Не розповсюджується",
|
||||
"Unused": "Не використовується",
|
||||
"Up to Date": "Актуальна версія",
|
||||
"Upgrade": "Оновлення",
|
||||
"Upgrade To {%version%}": "Оновити до {{version}}",
|
||||
"Upgrading": "Оновлення",
|
||||
"Upload Rate": "Швидкість віддачі",
|
||||
"Uptime": "Тривалість роботи",
|
||||
"Use HTTPS for GUI": "Використовувати HTTPS для доступу до панелі управління",
|
||||
"Version": "Версія",
|
||||
"Versions Path": "Шлях до версій",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A new major version may not be compatible with previous versions.": "重大更新可能与之前的版本之间无法兼容",
|
||||
"API Key": "API Key",
|
||||
"About": "关于",
|
||||
"Actions": "动作",
|
||||
"Add": "添加",
|
||||
"Add Device": "添加设备",
|
||||
"Add Folder": "添加文件夹",
|
||||
@@ -9,21 +12,23 @@
|
||||
"Addresses": "地址列表",
|
||||
"All Data": "所有数据",
|
||||
"Allow Anonymous Usage Reporting?": "允许匿名使用报告?",
|
||||
"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": "字母顺序",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "使用外部命令接管版本控制。当文件在其他设备被删除时,本机的 Syncthing 会调用该外部命令,并将文件路径以参数形式传递给该命令。该命令必须自行从同步文件夹中删除该文件。",
|
||||
"Anonymous Usage Reporting": "匿名使用报告",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "在介绍人设备上被添加的其它设备,也将会被添加到本机。",
|
||||
"Automatic upgrades": "自动升级",
|
||||
"Bugs": "Bug汇报",
|
||||
"CPU Utilization": "CPU使用率",
|
||||
"Changelog": "更新日志",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "关闭",
|
||||
"Command": "命令",
|
||||
"Comment, when used at the start of a line": "注释,在行首使用",
|
||||
"Compression": "压缩",
|
||||
"Connection Error": "连接出错",
|
||||
"Copied from elsewhere": "从其他地点复制",
|
||||
"Copied from elsewhere": "从其他设备复制",
|
||||
"Copied from original": "从源复制",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "版权 ©2015 由下列贡献者所有:",
|
||||
"Delete": "删除",
|
||||
"Device ID": "设备标识",
|
||||
"Device Identification": "设备标识",
|
||||
@@ -44,9 +49,11 @@
|
||||
"Enter ignore patterns, one per line.": "请输入忽略表达式,每行一条",
|
||||
"Error": "错误",
|
||||
"External File Versioning": "外部版本控制",
|
||||
"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.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "当寻找文件变更时,忽略文件权限。用于FAT文件系统。",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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.": "当某个文件在其他设备被替换或删除时,本设备将会在 .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": "主文件夹",
|
||||
@@ -56,21 +63,24 @@
|
||||
"GUI Authentication User": "登陆web管理页面的用户名",
|
||||
"GUI Listen Addresses": "web管理页面监听地址",
|
||||
"Generate": "生成",
|
||||
"Global Discovery": "在互联网上寻找节点",
|
||||
"Global Discovery Server": "用以在互联网上寻找节点的Announce服务器地址",
|
||||
"Global Discovery": "在互联网上寻找设备\n",
|
||||
"Global Discovery Server": "用以在互联网上寻找设备的Announce服务器地址",
|
||||
"Global State": "全局状态",
|
||||
"Help": "帮助",
|
||||
"Ignore": "忽略",
|
||||
"Ignore Patterns": "忽略列表",
|
||||
"Ignore Permissions": "忽略文件权限",
|
||||
"Incoming Rate Limit (KiB/s)": "下载速率限制(KiB/s)",
|
||||
"Introducer": "介绍人节点",
|
||||
"Introducer": "介绍人设备",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "对本条件取反(例如:不要排除某项)",
|
||||
"Keep Versions": "保留历史版本数量",
|
||||
"Largest First": "大文件优先",
|
||||
"Last File Received": "最后接收的文件",
|
||||
"Last seen": "最后可见",
|
||||
"Later": "稍后",
|
||||
"Local Discovery": "在局域网上寻找节点",
|
||||
"Local Discovery": "在局域网上寻找设备",
|
||||
"Local State": "本地状态",
|
||||
"Major Upgrade": "重大更新",
|
||||
"Maximum Age": "历史版本最长保留时间",
|
||||
"Metadata Only": "仅元数据",
|
||||
"Move to top of queue": "移动到队列顶端",
|
||||
@@ -78,27 +88,32 @@
|
||||
"Never": "从未",
|
||||
"New Device": "新设备",
|
||||
"New Folder": "新文件夹",
|
||||
"Newest First": "新文件优先",
|
||||
"No": "否",
|
||||
"No File Versioning": "不启用版本控制",
|
||||
"Notice": "提示",
|
||||
"OK": "确定",
|
||||
"Off": "关闭",
|
||||
"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 wait": "请稍候",
|
||||
"Preview": "预览",
|
||||
"Preview Usage Report": "预览使用报告",
|
||||
"Quick guide to supported patterns": "支持的通配符的简单教程:",
|
||||
"RAM Utilization": "内存使用量",
|
||||
"Random": "随机顺序",
|
||||
"Release Notes": "发布说明",
|
||||
"Rescan": "重新扫描",
|
||||
"Rescan All": "全部重新扫描",
|
||||
"Rescan Interval": "扫描间隔",
|
||||
"Restart": "重启syncthing",
|
||||
"Restart Needed": "需要重启Syncthing",
|
||||
"Restart": "重启 Syncthing",
|
||||
"Restart Needed": "需要重启 Syncthing",
|
||||
"Restarting": "重启中",
|
||||
"Reused": "复用",
|
||||
"Save": "保存",
|
||||
@@ -113,13 +128,14 @@
|
||||
"Share this folder?": "是否共享该文件夹?",
|
||||
"Shared With": "共享给",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "文件夹的别名。必须在所有设备上保持一致。",
|
||||
"Show ID": "显示设备ID",
|
||||
"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.": "在设备丛中,将会显示本名称,而不是设备标识。如果设置为空,则会使用目标设备提供的默认名称。",
|
||||
"Shutdown": "关闭Syncthing",
|
||||
"Shutdown": "关闭 Syncthing",
|
||||
"Shutdown Complete": "关闭完成",
|
||||
"Simple File Versioning": "简易版本控制",
|
||||
"Single level wildcard (matches within a directory only)": "单级通配符(仅匹配单层文件夹)",
|
||||
"Smallest First": "小文件优先",
|
||||
"Source Code": "源代码",
|
||||
"Staggered File Versioning": "阶段版本控制",
|
||||
"Start Browser": "启动浏览器",
|
||||
@@ -127,37 +143,43 @@
|
||||
"Support": "支持",
|
||||
"Sync Protocol Listen Addresses": "协议监听地址",
|
||||
"Syncing": "同步中",
|
||||
"Syncthing has been shut down.": "Syncthing已关闭",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing使用了下列软件或其中的一部分",
|
||||
"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 seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
|
||||
"Syncthing has been shut down.": "Syncthing 已关闭",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing 使用了下列软件或其中的一部分",
|
||||
"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。",
|
||||
"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 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 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 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.": "文件夹标识不得重复",
|
||||
"The folder path cannot be blank.": "文件夹路径不能为空",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "保留的历史版本会满足以下条件:最近一小时内的历史版本,更新间隔小于30秒的仅保留一份。最近一天内的历史版本,更新间隔小于1小时的仅保留一份。最近一个月内的历史版本,更新间隔小于1天的仅保留一份。距离现在超过一个月且小于最长保留时间的,更新间隔小于1周的仅保留一份。",
|
||||
"The maximum age must be a number and cannot be blank.": "最长保留时间必须为数字,且不能为空。",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "历史版本保留的最长天数,0为永久保存",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "每个文件保留的版本数量上限。",
|
||||
"The number of versions must be a number and cannot be blank.": "保留版本数量必须为数字,且不能为空。",
|
||||
"The path cannot be blank.": "路径不能为空",
|
||||
"The rescan interval must be a non-negative number of seconds.": "扫描间隔单位为秒,且不能为负数。",
|
||||
"This is a major version upgrade.": "这是一个重大版本更新。",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "未知",
|
||||
"Unshared": "未共享",
|
||||
"Unused": "已共享",
|
||||
"Unused": "未使用",
|
||||
"Up to Date": "同步完成",
|
||||
"Upgrade": "更新",
|
||||
"Upgrade To {%version%}": "升级至版本{{version}}",
|
||||
"Upgrading": "升级中",
|
||||
"Upload Rate": "上传速度",
|
||||
"Uptime": "已启动",
|
||||
"Use HTTPS for GUI": "使用HTTPS连接web管理页面",
|
||||
"Version": "版本",
|
||||
"Versions Path": "历史版本路径",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "一個負的天數並不合理。",
|
||||
"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": "操作",
|
||||
"Add": "增加",
|
||||
"Add Device": "增加裝置",
|
||||
"Add Folder": "增加資料夾",
|
||||
@@ -9,6 +12,7 @@
|
||||
"Addresses": "位址",
|
||||
"All Data": "全部資料",
|
||||
"Allow Anonymous Usage Reporting?": "允許匿名的使用資訊回報?",
|
||||
"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": "匿名的使用資訊回報",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "任何在引入者裝置所設置的裝置將會一併新增至此裝置",
|
||||
@@ -16,6 +20,7 @@
|
||||
"Bugs": "程式錯誤",
|
||||
"CPU Utilization": "CPU 使用",
|
||||
"Changelog": "更新日誌",
|
||||
"Clean out after": "Clean out after",
|
||||
"Close": "關閉",
|
||||
"Command": "指令",
|
||||
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
|
||||
@@ -23,7 +28,7 @@
|
||||
"Connection Error": "連線錯誤",
|
||||
"Copied from elsewhere": "從別處複製",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 下列貢獻者:",
|
||||
"Delete": "刪除",
|
||||
"Device ID": "裝置識別碼",
|
||||
"Device Identification": "裝置識別",
|
||||
@@ -44,8 +49,10 @@
|
||||
"Enter ignore patterns, one per line.": "輸入忽略樣式,每行一種。",
|
||||
"Error": "錯誤",
|
||||
"External File Versioning": "外部檔案版本控制",
|
||||
"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 .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .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 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.": "其他裝置做的改變不會影響到此裝置的檔案,但在此裝置上的變化將被發送到叢集中的其他部分。",
|
||||
"Folder ID": "資料夾識別碼",
|
||||
@@ -59,6 +66,7 @@
|
||||
"Global Discovery": "全域探索",
|
||||
"Global Discovery Server": "全域探索伺服器",
|
||||
"Global State": "全域狀態",
|
||||
"Help": "說明",
|
||||
"Ignore": "忽略",
|
||||
"Ignore Patterns": "忽略樣式",
|
||||
"Ignore Permissions": "忽略權限",
|
||||
@@ -66,34 +74,41 @@
|
||||
"Introducer": "引入者",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "反轉給定條件 (即:不要排除)",
|
||||
"Keep Versions": "保留歷史版本數",
|
||||
"Largest First": "最大的優先",
|
||||
"Last File Received": "最後接收的檔案",
|
||||
"Last seen": "最後發現時間",
|
||||
"Later": "稍後",
|
||||
"Local Discovery": "本地探索",
|
||||
"Local State": "本地狀態",
|
||||
"Major Upgrade": "重大更新",
|
||||
"Maximum Age": "最長保留時間",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Metadata Only": "僅中繼資料",
|
||||
"Move to top of queue": "移到隊列頂端",
|
||||
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
|
||||
"Never": "從未",
|
||||
"New Device": "新裝置",
|
||||
"New Folder": "新資料夾",
|
||||
"Newest First": "最新的優先",
|
||||
"No": "否",
|
||||
"No File Versioning": "無檔案版本控制",
|
||||
"Notice": "注意",
|
||||
"OK": "確定",
|
||||
"Off": "關閉",
|
||||
"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 wait": "請稍後",
|
||||
"Preview": "預覽",
|
||||
"Preview Usage Report": "預覽使用資訊報告",
|
||||
"Quick guide to supported patterns": "可支援樣式的快速指南",
|
||||
"RAM Utilization": "記憶體使用",
|
||||
"Random": "隨機",
|
||||
"Release Notes": "版本資訊",
|
||||
"Rescan": "重新掃描",
|
||||
"Rescan All": "全部重新掃描",
|
||||
"Rescan Interval": "重新掃描間隔",
|
||||
@@ -120,6 +135,7 @@
|
||||
"Shutdown Complete": "關閉完成",
|
||||
"Simple File Versioning": "簡單檔案版本控制",
|
||||
"Single level wildcard (matches within a directory only)": "單階層萬用字元 (只在單個資料夾階層內比對)",
|
||||
"Smallest First": "最小的優先",
|
||||
"Source Code": "原始碼",
|
||||
"Staggered File Versioning": "變動式檔案版本控制",
|
||||
"Start Browser": "啟動瀏覽器",
|
||||
@@ -145,19 +161,25 @@
|
||||
"The folder ID must be unique.": "資料夾識別碼必須為獨一無二的。",
|
||||
"The folder path cannot be blank.": "資料夾路徑不能空白。",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "使用下列的間隔:在第一個小時內每 30 秒保留一個版本,在第一天內每小時保留一個版本,在第 30 天內每一天保留一個版本,在達到最長保留時間前每一星期保留一個版本。",
|
||||
"The 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).": "一個版本被保留的最長時間 (單位為天,若設定為 0 則表示永遠保留)。",
|
||||
"The number of days must be a number and cannot be blank.": "天數必須必須為一個數字且不得為空。",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "每個檔案要保留的舊版本數量。",
|
||||
"The number of versions must be a number and cannot be blank.": "每個檔案要保留的舊版本數量必須是數字且不能為空白。",
|
||||
"The path cannot be blank.": "路徑不能空白。",
|
||||
"The rescan interval must be a non-negative number of seconds.": "重新掃描間隔必須為一個非負數的秒數。",
|
||||
"This is a major version upgrade.": "這是一個主要版本更新。",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Unknown": "未知",
|
||||
"Unshared": "未共享",
|
||||
"Unused": "未使用",
|
||||
"Up to Date": "最新",
|
||||
"Upgrade": "升級",
|
||||
"Upgrade To {%version%}": "升級至 {{version}}",
|
||||
"Upgrading": "正在升級",
|
||||
"Upload Rate": "上載速率",
|
||||
"Uptime": "上線時間",
|
||||
"Use HTTPS for GUI": "為 GUI 使用 HTTPS",
|
||||
"Version": "版本",
|
||||
"Versions Path": "歷史版本路徑",
|
||||
|
||||
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","fr","hu","it","lt","nb","nl","nn","pl","pt-BR","pt-PT","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"]
|
||||
|
||||
220
gui/index.html
220
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,21 +261,22 @@
|
||||
</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="trashcan" translate>Trash Can File Versioning</span>
|
||||
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
|
||||
<span ng-switch-when="simple" translate>Simple File Versioning</span>
|
||||
<span ng-switch-when="external" translate>External File Versioning</span>
|
||||
</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 +285,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="['idle', 'stopped'].indexOf(folderStatus(folder)) > -1" 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 +296,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 +320,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 +349,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 +385,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,11 +444,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="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>
|
||||
@@ -501,12 +513,12 @@
|
||||
<span translate>Please consult the release notes before performing a major upgrade.</span>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/syncthing/syncthing/releases/latest" target="_blank" translate>Release Notes</a>
|
||||
<a href="https://github.com/syncthing/syncthing/releases/tag/{{upgradeInfo.latest}}" target="_blank" translate>Release Notes</a>
|
||||
</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>
|
||||
@@ -584,9 +596,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>
|
||||
@@ -670,27 +682,27 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label translate>File Versioning</label>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="none"> <span translate>No File Versioning</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="simple"> <span translate>Simple File Versioning</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="external"> <span translate>External File Versioning</span>
|
||||
</label>
|
||||
<label translate>File Versioning</label> <a href="http://docs.syncthing.net/users/versioning.html" target="_blank"><span class="glyphicon glyphicon-book"></span> <span translate>Help</span></a>
|
||||
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
|
||||
<option value="none" translate>No File Versioning</option>
|
||||
<option value="trashcan" translate>Trash Can File Versioning</option>
|
||||
<option value="simple" translate>Simple File Versioning</option>
|
||||
<option value="staggered" translate>Staggered File Versioning</option>
|
||||
<option value="external" translate>External File Versioning</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
|
||||
<p translate class="help-block">Files are moved to .stversions folder when replaced or deleted by Syncthing.</p>
|
||||
<label translate for="trashcanClean">Clean out after</label>
|
||||
<div class="input-group">
|
||||
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required min="0"></input>
|
||||
<div class="input-group-addon">days</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
|
||||
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
|
||||
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</p>
|
||||
@@ -749,10 +761,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>
|
||||
@@ -772,7 +784,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>
|
||||
@@ -782,8 +794,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>
|
||||
@@ -904,8 +916,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>
|
||||
@@ -926,8 +938,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>
|
||||
@@ -947,7 +959,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>
|
||||
@@ -978,7 +990,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 -->
|
||||
@@ -1007,7 +1019,7 @@
|
||||
<a href="#" ng-click="neededChangePageSize(option)">{{option}}</a>
|
||||
<li>
|
||||
</ul>
|
||||
<div class="clearfix">
|
||||
<div class="clearfix"></div>
|
||||
</modal>
|
||||
|
||||
<!-- About modal -->
|
||||
@@ -1025,6 +1037,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>
|
||||
@@ -1033,6 +1046,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>
|
||||
@@ -1054,6 +1068,7 @@
|
||||
<li class="auto-generated">Karol Różycki</li>
|
||||
<li class="auto-generated">Ken'ichi Kamada</li>
|
||||
<li class="auto-generated">Lode Hoste</li>
|
||||
<li class="auto-generated">Lord Landon Agahnim</li>
|
||||
<li class="auto-generated">Marc Laporte</li>
|
||||
<li class="auto-generated">Marc Pujol</li>
|
||||
<li class="auto-generated">Marcin Dziadus</li>
|
||||
@@ -1119,6 +1134,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 -->
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
|
||||
|
||||
});
|
||||
|
||||
// @TODO: extract global level functions into seperate service(s)
|
||||
// @TODO: extract global level functions into separate service(s)
|
||||
|
||||
function deviceCompare(a, b) {
|
||||
if (typeof a.name !== 'undefined' && typeof b.name !== 'undefined') {
|
||||
|
||||
@@ -12,7 +12,7 @@ angular.module('syncthing.core')
|
||||
// progress the browser on some platforms returns a 200 (since the
|
||||
// headers has been flushed with the return code 200), with no data.
|
||||
// This basically means that the connection has been reset, and the call
|
||||
// was not actually sucessful.
|
||||
// was not actually successful.
|
||||
if (!data) {
|
||||
errorFn(data);
|
||||
return;
|
||||
|
||||
@@ -140,6 +140,7 @@ angular.module('syncthing.core')
|
||||
var data = arg.data;
|
||||
if ($scope.model[data.folder]) {
|
||||
$scope.model[data.folder].state = data.to;
|
||||
$scope.model[data.folder].error = data.error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -220,7 +221,7 @@ angular.module('syncthing.core')
|
||||
var copiedFromElsewhere = 100 * s.copiedFromElsewhere / s.total;
|
||||
var pulled = 100 * s.pulled / s.total;
|
||||
var pulling = 100 * s.pulling / s.total;
|
||||
// We try to round up pulling to atleast a percent so that it would be atleast a bit visible.
|
||||
// We try to round up pulling to at least a percent so that it would be at least a bit visible.
|
||||
if (pulling < 1 && pulled + copiedFromElsewhere + copiedFromOrigin + reused <= 99) {
|
||||
pulling = 1;
|
||||
}
|
||||
@@ -522,6 +523,11 @@ angular.module('syncthing.core')
|
||||
return 'stopped'; // legacy, the state is called "stopped" in the GUI
|
||||
}
|
||||
|
||||
// after restart syncthing process state may be empty
|
||||
if (!$scope.model[folderCfg.id].state) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
return '' + $scope.model[folderCfg.id].state;
|
||||
};
|
||||
|
||||
@@ -985,7 +991,11 @@ angular.module('syncthing.core')
|
||||
$scope.currentFolder.devices.forEach(function (n) {
|
||||
$scope.currentFolder.selectedDevices[n.deviceID] = true;
|
||||
});
|
||||
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
|
||||
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
|
||||
$scope.currentFolder.trashcanFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "trashcan";
|
||||
$scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
|
||||
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
|
||||
$scope.currentFolder.simpleFileVersioning = true;
|
||||
$scope.currentFolder.fileVersioningSelector = "simple";
|
||||
$scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
|
||||
@@ -1002,6 +1012,7 @@ angular.module('syncthing.core')
|
||||
} else {
|
||||
$scope.currentFolder.fileVersioningSelector = "none";
|
||||
}
|
||||
$scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
|
||||
$scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
|
||||
$scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
|
||||
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
|
||||
@@ -1025,6 +1036,7 @@ angular.module('syncthing.core')
|
||||
};
|
||||
$scope.currentFolder.rescanIntervalS = 60;
|
||||
$scope.currentFolder.fileVersioningSelector = "none";
|
||||
$scope.currentFolder.trashcanClean = 0;
|
||||
$scope.currentFolder.simpleKeep = 5;
|
||||
$scope.currentFolder.staggeredMaxAge = 365;
|
||||
$scope.currentFolder.staggeredCleanInterval = 3600;
|
||||
@@ -1046,6 +1058,7 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.currentFolder.rescanIntervalS = 60;
|
||||
$scope.currentFolder.fileVersioningSelector = "none";
|
||||
$scope.currentFolder.trashcanClean = 0;
|
||||
$scope.currentFolder.simpleKeep = 5;
|
||||
$scope.currentFolder.staggeredMaxAge = 365;
|
||||
$scope.currentFolder.staggeredCleanInterval = 3600;
|
||||
@@ -1082,7 +1095,16 @@ angular.module('syncthing.core')
|
||||
}
|
||||
delete folderCfg.selectedDevices;
|
||||
|
||||
if (folderCfg.fileVersioningSelector === "simple") {
|
||||
if (folderCfg.fileVersioningSelector === "trashcan") {
|
||||
folderCfg.versioning = {
|
||||
'Type': 'trashcan',
|
||||
'Params': {
|
||||
'cleanoutDays': '' + folderCfg.trashcanClean
|
||||
}
|
||||
};
|
||||
delete folderCfg.trashcanFileVersioning;
|
||||
delete folderCfg.trashcanClean;
|
||||
} else if (folderCfg.fileVersioningSelector === "simple") {
|
||||
folderCfg.versioning = {
|
||||
'Type': 'simple',
|
||||
'Params': {
|
||||
|
||||
@@ -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,13 +21,16 @@ 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 recive async query accpeted-languages
|
||||
|
||||
// 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) {
|
||||
if (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;
|
||||
};
|
||||
@@ -42,7 +39,7 @@ angular.module('syncthing.core')
|
||||
* @returns promise which on success resolves with a locales array
|
||||
*/
|
||||
function readBrowserLocales() {
|
||||
// @TODO: check if there is nice way to utilize window.navigator.languages or similiar api.
|
||||
// @TODO: check if there is nice way to utilize window.navigator.languages or similar api.
|
||||
|
||||
return $http.get(urlbase + "/svc/lang");
|
||||
}
|
||||
@@ -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
@@ -6,31 +6,51 @@
|
||||
|
||||
package beacon
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
type Broadcast struct {
|
||||
conn *net.UDPConn
|
||||
*suture.Supervisor
|
||||
port int
|
||||
inbox chan []byte
|
||||
outbox chan recv
|
||||
}
|
||||
|
||||
func NewBroadcast(port int) (*Broadcast, error) {
|
||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewBroadcast(port int) *Broadcast {
|
||||
b := &Broadcast{
|
||||
conn: conn,
|
||||
Supervisor: suture.New("broadcastBeacon", suture.Spec{
|
||||
// Don't retry too frenetically: an error to open a socket or
|
||||
// whatever is usually something that is either permanent or takes
|
||||
// a while to get solved...
|
||||
FailureThreshold: 2,
|
||||
FailureBackoff: 60 * time.Second,
|
||||
// Only log restarts in debug mode.
|
||||
Log: func(line string) {
|
||||
if debug {
|
||||
l.Debugln(line)
|
||||
}
|
||||
},
|
||||
}),
|
||||
port: port,
|
||||
inbox: make(chan []byte),
|
||||
outbox: make(chan recv, 16),
|
||||
}
|
||||
|
||||
go genericReader(b.conn, b.outbox)
|
||||
go b.writer()
|
||||
b.Add(&broadcastReader{
|
||||
port: port,
|
||||
outbox: b.outbox,
|
||||
})
|
||||
b.Add(&broadcastWriter{
|
||||
port: port,
|
||||
inbox: b.inbox,
|
||||
})
|
||||
|
||||
return b, nil
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Broadcast) Send(data []byte) {
|
||||
@@ -42,12 +62,38 @@ func (b *Broadcast) Recv() ([]byte, net.Addr) {
|
||||
return recv.data, recv.src
|
||||
}
|
||||
|
||||
func (b *Broadcast) writer() {
|
||||
for bs := range b.inbox {
|
||||
type broadcastWriter struct {
|
||||
port int
|
||||
inbox chan []byte
|
||||
conn *net.UDPConn
|
||||
failed bool // Have we already logged a failure reason?
|
||||
}
|
||||
|
||||
func (w *broadcastWriter) Serve() {
|
||||
if debug {
|
||||
l.Debugln(w, "starting")
|
||||
defer l.Debugln(w, "stopping")
|
||||
}
|
||||
|
||||
var err error
|
||||
w.conn, err = net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
if !w.failed {
|
||||
l.Warnln("Local discovery over IPv4 unavailable:", err)
|
||||
w.failed = true
|
||||
}
|
||||
return
|
||||
}
|
||||
defer w.conn.Close()
|
||||
|
||||
w.failed = false
|
||||
|
||||
for bs := range w.inbox {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
l.Warnln("Broadcast: interface addresses:", err)
|
||||
if debug {
|
||||
l.Debugln("Local discovery (broadcast writer):", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -69,13 +115,27 @@ func (b *Broadcast) writer() {
|
||||
}
|
||||
|
||||
for _, ip := range dsts {
|
||||
dst := &net.UDPAddr{IP: ip, Port: b.port}
|
||||
dst := &net.UDPAddr{IP: ip, Port: w.port}
|
||||
|
||||
_, err := b.conn.WriteTo(bs, dst)
|
||||
if err != nil {
|
||||
w.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
_, err := w.conn.WriteTo(bs, dst)
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
// Write timeouts should not happen. We treat it as a fatal
|
||||
// error on the socket.
|
||||
l.Infoln("Local discovery (broadcast writer):", err)
|
||||
w.failed = true
|
||||
return
|
||||
} else if err, ok := err.(net.Error); ok && err.Temporary() {
|
||||
// A transient error. Lets hope for better luck in the future.
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
// Some other error that we don't expect. Bail and retry.
|
||||
l.Infoln("Local discovery (broadcast writer):", err)
|
||||
w.failed = true
|
||||
return
|
||||
} else if debug {
|
||||
l.Debugf("sent %d bytes to %s", len(bs), dst)
|
||||
}
|
||||
@@ -83,6 +143,76 @@ func (b *Broadcast) writer() {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *broadcastWriter) Stop() {
|
||||
w.conn.Close()
|
||||
}
|
||||
|
||||
func (w *broadcastWriter) String() string {
|
||||
return fmt.Sprintf("broadcastWriter@%p", w)
|
||||
}
|
||||
|
||||
type broadcastReader struct {
|
||||
port int
|
||||
outbox chan recv
|
||||
conn *net.UDPConn
|
||||
failed bool
|
||||
}
|
||||
|
||||
func (r *broadcastReader) Serve() {
|
||||
if debug {
|
||||
l.Debugln(r, "starting")
|
||||
defer l.Debugln(r, "stopping")
|
||||
}
|
||||
|
||||
var err error
|
||||
r.conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
|
||||
if err != nil {
|
||||
if !r.failed {
|
||||
l.Warnln("Local discovery over IPv4 unavailable:", err)
|
||||
r.failed = true
|
||||
}
|
||||
return
|
||||
}
|
||||
defer r.conn.Close()
|
||||
|
||||
bs := make([]byte, 65536)
|
||||
for {
|
||||
n, addr, err := r.conn.ReadFrom(bs)
|
||||
if err != nil {
|
||||
if !r.failed {
|
||||
l.Infoln("Local discovery (broadcast reader):", err)
|
||||
r.failed = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
r.failed = false
|
||||
|
||||
if debug {
|
||||
l.Debugf("recv %d bytes from %s", n, addr)
|
||||
}
|
||||
|
||||
c := make([]byte, n)
|
||||
copy(c, bs)
|
||||
select {
|
||||
case r.outbox <- recv{c, addr}:
|
||||
default:
|
||||
if debug {
|
||||
l.Debugln("dropping message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *broadcastReader) Stop() {
|
||||
r.conn.Close()
|
||||
}
|
||||
|
||||
func (r *broadcastReader) String() string {
|
||||
return fmt.Sprintf("broadcastReader@%p", r)
|
||||
}
|
||||
|
||||
func bcast(ip *net.IPNet) *net.IPNet {
|
||||
var bc = &net.IPNet{}
|
||||
bc.IP = make([]byte, len(ip.IP))
|
||||
|
||||
89
internal/config/commit_test.go
Normal file
89
internal/config/commit_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type requiresRestart struct{}
|
||||
|
||||
func (requiresRestart) VerifyConfiguration(_, _ Configuration) error {
|
||||
return nil
|
||||
}
|
||||
func (requiresRestart) CommitConfiguration(_, _ Configuration) bool {
|
||||
return false
|
||||
}
|
||||
func (requiresRestart) String() string {
|
||||
return "requiresRestart"
|
||||
}
|
||||
|
||||
type validationError struct{}
|
||||
|
||||
func (validationError) VerifyConfiguration(_, _ Configuration) error {
|
||||
return errors.New("some error")
|
||||
}
|
||||
func (validationError) CommitConfiguration(_, _ Configuration) bool {
|
||||
return true
|
||||
}
|
||||
func (validationError) String() string {
|
||||
return "validationError"
|
||||
}
|
||||
|
||||
func TestReplaceCommit(t *testing.T) {
|
||||
w := Wrap("/dev/null", Configuration{Version: 0})
|
||||
if w.Raw().Version != 0 {
|
||||
t.Fatal("Config incorrect")
|
||||
}
|
||||
|
||||
// Replace config. We should get back a clean response and the config
|
||||
// should change.
|
||||
|
||||
resp := w.Replace(Configuration{Version: 1})
|
||||
if resp.ValidationError != nil {
|
||||
t.Fatal("Should not have a validation error")
|
||||
}
|
||||
if resp.RequiresRestart {
|
||||
t.Fatal("Should not require restart")
|
||||
}
|
||||
if w.Raw().Version != 1 {
|
||||
t.Fatal("Config should have changed")
|
||||
}
|
||||
|
||||
// Now with a subscriber requiring restart. We should get a clean response
|
||||
// but with the restart flag set, and the config should change.
|
||||
|
||||
w.Subscribe(requiresRestart{})
|
||||
|
||||
resp = w.Replace(Configuration{Version: 2})
|
||||
if resp.ValidationError != nil {
|
||||
t.Fatal("Should not have a validation error")
|
||||
}
|
||||
if !resp.RequiresRestart {
|
||||
t.Fatal("Should require restart")
|
||||
}
|
||||
if w.Raw().Version != 2 {
|
||||
t.Fatal("Config should have changed")
|
||||
}
|
||||
|
||||
// Now with a subscriber that throws a validation error. The config should
|
||||
// not change.
|
||||
|
||||
w.Subscribe(validationError{})
|
||||
|
||||
resp = w.Replace(Configuration{Version: 3})
|
||||
if resp.ValidationError == nil {
|
||||
t.Fatal("Should have a validation error")
|
||||
}
|
||||
if resp.RequiresRestart {
|
||||
t.Fatal("Should not require restart")
|
||||
}
|
||||
if w.Raw().Version != 2 {
|
||||
t.Fatal("Config should not have changed")
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var l = logger.DefaultLogger
|
||||
|
||||
const (
|
||||
OldestHandledVersion = 5
|
||||
CurrentVersion = 10
|
||||
@@ -45,28 +42,28 @@ type Configuration struct {
|
||||
OriginalVersion int `xml:"-" json:"-"` // The version we read from disk, before any conversion
|
||||
}
|
||||
|
||||
func (orig Configuration) Copy() Configuration {
|
||||
c := orig
|
||||
func (cfg Configuration) Copy() Configuration {
|
||||
newCfg := cfg
|
||||
|
||||
// Deep copy FolderConfigurations
|
||||
c.Folders = make([]FolderConfiguration, len(orig.Folders))
|
||||
for i := range c.Folders {
|
||||
c.Folders[i] = orig.Folders[i].Copy()
|
||||
newCfg.Folders = make([]FolderConfiguration, len(cfg.Folders))
|
||||
for i := range newCfg.Folders {
|
||||
newCfg.Folders[i] = cfg.Folders[i].Copy()
|
||||
}
|
||||
|
||||
// Deep copy DeviceConfigurations
|
||||
c.Devices = make([]DeviceConfiguration, len(orig.Devices))
|
||||
for i := range c.Devices {
|
||||
c.Devices[i] = orig.Devices[i].Copy()
|
||||
newCfg.Devices = make([]DeviceConfiguration, len(cfg.Devices))
|
||||
for i := range newCfg.Devices {
|
||||
newCfg.Devices[i] = cfg.Devices[i].Copy()
|
||||
}
|
||||
|
||||
c.Options = orig.Options.Copy()
|
||||
newCfg.Options = cfg.Options.Copy()
|
||||
|
||||
// DeviceIDs are values
|
||||
c.IgnoredDevices = make([]protocol.DeviceID, len(orig.IgnoredDevices))
|
||||
copy(c.IgnoredDevices, orig.IgnoredDevices)
|
||||
newCfg.IgnoredDevices = make([]protocol.DeviceID, len(cfg.IgnoredDevices))
|
||||
copy(newCfg.IgnoredDevices, cfg.IgnoredDevices)
|
||||
|
||||
return c
|
||||
return newCfg
|
||||
}
|
||||
|
||||
type FolderConfiguration struct {
|
||||
@@ -78,7 +75,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.
|
||||
@@ -89,10 +85,10 @@ type FolderConfiguration struct {
|
||||
deviceIDs []protocol.DeviceID
|
||||
}
|
||||
|
||||
func (orig FolderConfiguration) Copy() FolderConfiguration {
|
||||
c := orig
|
||||
c.Devices = make([]FolderDeviceConfiguration, len(orig.Devices))
|
||||
copy(c.Devices, orig.Devices)
|
||||
func (f FolderConfiguration) Copy() FolderConfiguration {
|
||||
c := f
|
||||
c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))
|
||||
copy(c.Devices, f.Devices)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -228,9 +224,9 @@ type OptionsConfiguration struct {
|
||||
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
|
||||
StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
|
||||
UPnPEnabled bool `xml:"upnpEnabled" json:"upnpEnabled" default:"true"`
|
||||
UPnPLeaseM int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"0"`
|
||||
UPnPLeaseM int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"60"`
|
||||
UPnPRenewalM int `xml:"upnpRenewalMinutes" json:"upnpRenewalMinutes" default:"30"`
|
||||
UPnPTimeoutS int `xml:"upnpTimeoutSeconds" json:"upnpTimeoutSeconds" default:"3"`
|
||||
UPnPTimeoutS int `xml:"upnpTimeoutSeconds" json:"upnpTimeoutSeconds" default:"10"`
|
||||
URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
|
||||
URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
|
||||
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
|
||||
@@ -240,6 +236,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 {
|
||||
|
||||
@@ -42,9 +42,9 @@ func TestDefaultValues(t *testing.T) {
|
||||
ReconnectIntervalS: 60,
|
||||
StartBrowser: true,
|
||||
UPnPEnabled: true,
|
||||
UPnPLeaseM: 0,
|
||||
UPnPLeaseM: 60,
|
||||
UPnPRenewalM: 30,
|
||||
UPnPTimeoutS: 3,
|
||||
UPnPTimeoutS: 10,
|
||||
RestartOnWakeup: true,
|
||||
AutoUpgradeIntervalH: 12,
|
||||
KeepTemporariesH: 24,
|
||||
@@ -52,6 +52,7 @@ func TestDefaultValues(t *testing.T) {
|
||||
ProgressUpdateIntervalS: 5,
|
||||
SymlinksEnabled: true,
|
||||
LimitBandwidthInLan: false,
|
||||
DatabaseBlockCacheMiB: 0,
|
||||
}
|
||||
|
||||
cfg := New(device1)
|
||||
@@ -148,7 +149,7 @@ func TestOverriddenValues(t *testing.T) {
|
||||
ReconnectIntervalS: 6000,
|
||||
StartBrowser: false,
|
||||
UPnPEnabled: false,
|
||||
UPnPLeaseM: 60,
|
||||
UPnPLeaseM: 90,
|
||||
UPnPRenewalM: 15,
|
||||
UPnPTimeoutS: 15,
|
||||
RestartOnWakeup: false,
|
||||
@@ -158,6 +159,7 @@ func TestOverriddenValues(t *testing.T) {
|
||||
ProgressUpdateIntervalS: 10,
|
||||
SymlinksEnabled: false,
|
||||
LimitBandwidthInLan: true,
|
||||
DatabaseBlockCacheMiB: 42,
|
||||
}
|
||||
|
||||
cfg, err := Load("testdata/overridenvalues.xml", device1)
|
||||
|
||||
19
internal/config/debug.go
Normal file
19
internal/config/debug.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "config") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
)
|
||||
3
internal/config/testdata/overridenvalues.xml
vendored
3
internal/config/testdata/overridenvalues.xml
vendored
@@ -13,7 +13,7 @@
|
||||
<reconnectionIntervalS>6000</reconnectionIntervalS>
|
||||
<startBrowser>false</startBrowser>
|
||||
<upnpEnabled>false</upnpEnabled>
|
||||
<upnpLeaseMinutes>60</upnpLeaseMinutes>
|
||||
<upnpLeaseMinutes>90</upnpLeaseMinutes>
|
||||
<upnpRenewalMinutes>15</upnpRenewalMinutes>
|
||||
<upnpTimeoutSeconds>15</upnpTimeoutSeconds>
|
||||
<restartOnWakeup>false</restartOnWakeup>
|
||||
@@ -23,5 +23,6 @@
|
||||
<progressUpdateIntervalS>10</progressUpdateIntervalS>
|
||||
<symlinksEnabled>false</symlinksEnabled>
|
||||
<limitBandwidthInLan>true</limitBandwidthInLan>
|
||||
<databaseBlockCacheMiB>42</databaseBlockCacheMiB>
|
||||
</options>
|
||||
</configuration>
|
||||
|
||||
@@ -17,17 +17,39 @@ import (
|
||||
"github.com/syncthing/syncthing/internal/sync"
|
||||
)
|
||||
|
||||
// An interface to handle configuration changes, and a wrapper type á la
|
||||
// http.Handler
|
||||
|
||||
type Handler interface {
|
||||
Changed(Configuration) error
|
||||
// The Committer interface is implemented by objects that need to know about
|
||||
// or have a say in configuration changes.
|
||||
//
|
||||
// When the configuration is about to be changed, VerifyConfiguration() is
|
||||
// called for each subscribing object, with the old and new configuration. A
|
||||
// nil error is returned if the new configuration is acceptable (i.e. does not
|
||||
// contain any errors that would prevent it from being a valid config).
|
||||
// Otherwise an error describing the problem is returned.
|
||||
//
|
||||
// If any subscriber returns an error from VerifyConfiguration(), the
|
||||
// configuration change is not committed and an error is returned to whoever
|
||||
// tried to commit the broken config.
|
||||
//
|
||||
// If all verification calls returns nil, CommitConfiguration() is called for
|
||||
// each subscribing object. The callee returns true if the new configuration
|
||||
// has been successfully applied, otherwise false. Any Commit() call returning
|
||||
// false will result in a "restart needed" respone to the API/user. Note that
|
||||
// the new configuration will still have been applied by those who were
|
||||
// capable of doing so.
|
||||
type Committer interface {
|
||||
VerifyConfiguration(from, to Configuration) error
|
||||
CommitConfiguration(from, to Configuration) (handled bool)
|
||||
String() string
|
||||
}
|
||||
|
||||
type HandlerFunc func(Configuration) error
|
||||
type CommitResponse struct {
|
||||
ValidationError error
|
||||
RequiresRestart bool
|
||||
}
|
||||
|
||||
func (fn HandlerFunc) Changed(cfg Configuration) error {
|
||||
return fn(cfg)
|
||||
var ResponseNoRestart = CommitResponse{
|
||||
ValidationError: nil,
|
||||
RequiresRestart: false,
|
||||
}
|
||||
|
||||
// A wrapper around a Configuration that manages loads, saves and published
|
||||
@@ -42,7 +64,7 @@ type Wrapper struct {
|
||||
replaces chan Configuration
|
||||
mut sync.Mutex
|
||||
|
||||
subs []Handler
|
||||
subs []Committer
|
||||
sMut sync.Mutex
|
||||
}
|
||||
|
||||
@@ -56,7 +78,6 @@ func Wrap(path string, cfg Configuration) *Wrapper {
|
||||
sMut: sync.NewMutex(),
|
||||
}
|
||||
w.replaces = make(chan Configuration)
|
||||
go w.Serve()
|
||||
return w
|
||||
}
|
||||
|
||||
@@ -77,21 +98,6 @@ func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
|
||||
return Wrap(path, cfg), nil
|
||||
}
|
||||
|
||||
// Serve handles configuration replace events and calls any interested
|
||||
// handlers. It is started automatically by Wrap() and Load() and should not
|
||||
// be run manually.
|
||||
func (w *Wrapper) Serve() {
|
||||
for cfg := range w.replaces {
|
||||
w.sMut.Lock()
|
||||
subs := w.subs
|
||||
w.sMut.Unlock()
|
||||
|
||||
for _, h := range subs {
|
||||
h.Changed(cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the Serve() loop. Set and Replace operations will panic after a
|
||||
// Stop.
|
||||
func (w *Wrapper) Stop() {
|
||||
@@ -100,9 +106,9 @@ func (w *Wrapper) Stop() {
|
||||
|
||||
// Subscribe registers the given handler to be called on any future
|
||||
// configuration changes.
|
||||
func (w *Wrapper) Subscribe(h Handler) {
|
||||
func (w *Wrapper) Subscribe(c Committer) {
|
||||
w.sMut.Lock()
|
||||
w.subs = append(w.subs, h)
|
||||
w.subs = append(w.subs, c)
|
||||
w.sMut.Unlock()
|
||||
}
|
||||
|
||||
@@ -112,14 +118,50 @@ func (w *Wrapper) Raw() Configuration {
|
||||
}
|
||||
|
||||
// Replace swaps the current configuration object for the given one.
|
||||
func (w *Wrapper) Replace(cfg Configuration) {
|
||||
func (w *Wrapper) Replace(cfg Configuration) CommitResponse {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.replaceLocked(cfg)
|
||||
}
|
||||
|
||||
w.cfg = cfg
|
||||
func (w *Wrapper) replaceLocked(to Configuration) CommitResponse {
|
||||
from := w.cfg
|
||||
|
||||
for _, sub := range w.subs {
|
||||
if debug {
|
||||
l.Debugln(sub, "verifying configuration")
|
||||
}
|
||||
if err := sub.VerifyConfiguration(from, to); err != nil {
|
||||
if debug {
|
||||
l.Debugln(sub, "rejected config:", err)
|
||||
}
|
||||
return CommitResponse{
|
||||
ValidationError: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allOk := true
|
||||
for _, sub := range w.subs {
|
||||
if debug {
|
||||
l.Debugln(sub, "committing configuration")
|
||||
}
|
||||
ok := sub.CommitConfiguration(from, to)
|
||||
if !ok {
|
||||
if debug {
|
||||
l.Debugln(sub, "requires restart")
|
||||
}
|
||||
allOk = false
|
||||
}
|
||||
}
|
||||
|
||||
w.cfg = to
|
||||
w.deviceMap = nil
|
||||
w.folderMap = nil
|
||||
w.replaces <- cfg.Copy()
|
||||
|
||||
return CommitResponse{
|
||||
RequiresRestart: !allOk,
|
||||
}
|
||||
}
|
||||
|
||||
// Devices returns a map of devices. Device structures should not be changed,
|
||||
@@ -138,25 +180,27 @@ func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
|
||||
|
||||
// SetDevice adds a new device to the configuration, or overwrites an existing
|
||||
// device with the same ID.
|
||||
func (w *Wrapper) SetDevice(dev DeviceConfiguration) {
|
||||
func (w *Wrapper) SetDevice(dev DeviceConfiguration) CommitResponse {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
w.deviceMap = nil
|
||||
|
||||
for i := range w.cfg.Devices {
|
||||
if w.cfg.Devices[i].DeviceID == dev.DeviceID {
|
||||
w.cfg.Devices[i] = dev
|
||||
w.replaces <- w.cfg.Copy()
|
||||
return
|
||||
newCfg := w.cfg.Copy()
|
||||
replaced := false
|
||||
for i := range newCfg.Devices {
|
||||
if newCfg.Devices[i].DeviceID == dev.DeviceID {
|
||||
newCfg.Devices[i] = dev
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
newCfg.Devices = append(w.cfg.Devices, dev)
|
||||
}
|
||||
|
||||
w.cfg.Devices = append(w.cfg.Devices, dev)
|
||||
w.replaces <- w.cfg.Copy()
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// Devices returns a map of folders. Folder structures should not be changed,
|
||||
// Folders returns a map of folders. Folder structures should not be changed,
|
||||
// other than for the purpose of updating via SetFolder().
|
||||
func (w *Wrapper) Folders() map[string]FolderConfiguration {
|
||||
w.mut.Lock()
|
||||
@@ -172,22 +216,24 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
|
||||
|
||||
// SetFolder adds a new folder to the configuration, or overwrites an existing
|
||||
// folder with the same ID.
|
||||
func (w *Wrapper) SetFolder(fld FolderConfiguration) {
|
||||
func (w *Wrapper) SetFolder(fld FolderConfiguration) CommitResponse {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
w.folderMap = nil
|
||||
|
||||
for i := range w.cfg.Folders {
|
||||
if w.cfg.Folders[i].ID == fld.ID {
|
||||
w.cfg.Folders[i] = fld
|
||||
w.replaces <- w.cfg.Copy()
|
||||
return
|
||||
newCfg := w.cfg.Copy()
|
||||
replaced := false
|
||||
for i := range newCfg.Folders {
|
||||
if newCfg.Folders[i].ID == fld.ID {
|
||||
newCfg.Folders[i] = fld
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
newCfg.Folders = append(w.cfg.Folders, fld)
|
||||
}
|
||||
|
||||
w.cfg.Folders = append(w.cfg.Folders, fld)
|
||||
w.replaces <- w.cfg.Copy()
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// Options returns the current options configuration object.
|
||||
@@ -198,11 +244,12 @@ func (w *Wrapper) Options() OptionsConfiguration {
|
||||
}
|
||||
|
||||
// SetOptions replaces the current options configuration object.
|
||||
func (w *Wrapper) SetOptions(opts OptionsConfiguration) {
|
||||
func (w *Wrapper) SetOptions(opts OptionsConfiguration) CommitResponse {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
w.cfg.Options = opts
|
||||
w.replaces <- w.cfg.Copy()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.Options = opts
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// GUI returns the current GUI configuration object.
|
||||
@@ -213,15 +260,16 @@ func (w *Wrapper) GUI() GUIConfiguration {
|
||||
}
|
||||
|
||||
// SetGUI replaces the current GUI configuration object.
|
||||
func (w *Wrapper) SetGUI(gui GUIConfiguration) {
|
||||
func (w *Wrapper) SetGUI(gui GUIConfiguration) CommitResponse {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
w.cfg.GUI = gui
|
||||
w.replaces <- w.cfg.Copy()
|
||||
newCfg := w.cfg.Copy()
|
||||
newCfg.GUI = gui
|
||||
return w.replaceLocked(newCfg)
|
||||
}
|
||||
|
||||
// Returns whether or not connection attempts from the given device should be
|
||||
// silently ignored.
|
||||
// IgnoredDevice returns whether or not connection attempts from the given
|
||||
// device should be silently ignored.
|
||||
func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
// depending on who calls us. We transform paths to wire-format (NFC and
|
||||
// slashes) on the way to the database, and transform to native format
|
||||
// (varying separator and encoding) on the way back out.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
@@ -126,15 +126,22 @@ func NewBlockFinder(db *leveldb.DB, cfg *config.Wrapper) *BlockFinder {
|
||||
db: db,
|
||||
mut: sync.NewRWMutex(),
|
||||
}
|
||||
f.Changed(cfg.Raw())
|
||||
|
||||
f.CommitConfiguration(config.Configuration{}, cfg.Raw())
|
||||
cfg.Subscribe(f)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Implements config.Handler interface
|
||||
func (f *BlockFinder) Changed(cfg config.Configuration) error {
|
||||
folders := make([]string, len(cfg.Folders))
|
||||
for i, folder := range cfg.Folders {
|
||||
// VerifyConfiguration implementes the config.Committer interface
|
||||
func (f *BlockFinder) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitConfiguration implementes the config.Committer interface
|
||||
func (f *BlockFinder) CommitConfiguration(from, to config.Configuration) bool {
|
||||
folders := make([]string, len(to.Folders))
|
||||
for i, folder := range to.Folders {
|
||||
folders[i] = folder.ID
|
||||
}
|
||||
|
||||
@@ -144,14 +151,18 @@ func (f *BlockFinder) Changed(cfg config.Configuration) error {
|
||||
f.folders = folders
|
||||
f.mut.Unlock()
|
||||
|
||||
return nil
|
||||
return true
|
||||
}
|
||||
|
||||
// An iterator function which iterates over all matching blocks for the given
|
||||
// hash. The iterator function has to return either true (if they are happy with
|
||||
// the block) or false to continue iterating for whatever reason.
|
||||
// The iterator finally returns the result, whether or not a satisfying block
|
||||
// was eventually found.
|
||||
func (f *BlockFinder) String() string {
|
||||
return fmt.Sprintf("BlockFinder@%p", f)
|
||||
}
|
||||
|
||||
// Iterate takes an iterator function which iterates over all matching blocks
|
||||
// for the given hash. The iterator function has to return either true (if
|
||||
// they are happy with the block) or false to continue iterating for whatever
|
||||
// reason. The iterator finally returns the result, whether or not a
|
||||
// satisfying block was eventually found.
|
||||
func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, int32) bool) bool {
|
||||
f.mut.RLock()
|
||||
folders := f.folders
|
||||
@@ -172,8 +183,8 @@ func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, int32) bo
|
||||
return false
|
||||
}
|
||||
|
||||
// A method for repairing incorrect blockmap entries, removes the old entry
|
||||
// and replaces it with a new entry for the given block
|
||||
// Fix repairs incorrect blockmap entries, removing the old entry and
|
||||
// replacing it with a new entry for the given block
|
||||
func (f *BlockFinder) Fix(folder, file string, index int32, oldHash, newHash []byte) error {
|
||||
buf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(buf, uint32(index))
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore // this is a really tedious test for an old issue
|
||||
|
||||
package db_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
|
||||
var (
|
||||
clockTick int64
|
||||
clockMut sync.Mutex = sync.NewMutex()
|
||||
clockMut = sync.NewMutex()
|
||||
)
|
||||
|
||||
func clock(v int64) int64 {
|
||||
@@ -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)
|
||||
}
|
||||
@@ -976,11 +990,11 @@ func unmarshalTrunc(bs []byte, truncate bool) (FileIntf, error) {
|
||||
var tf FileInfoTruncated
|
||||
err := tf.UnmarshalXDR(bs)
|
||||
return tf, err
|
||||
} else {
|
||||
var tf protocol.FileInfo
|
||||
err := tf.UnmarshalXDR(bs)
|
||||
return tf, err
|
||||
}
|
||||
|
||||
var tf protocol.FileInfo
|
||||
err := tf.UnmarshalXDR(bs)
|
||||
return tf, err
|
||||
}
|
||||
|
||||
func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
|
||||
@@ -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"
|
||||
|
||||
@@ -85,17 +86,10 @@ func (d *Discoverer) StartLocal(localPort int, localMCAddr string) {
|
||||
}
|
||||
|
||||
func (d *Discoverer) startLocalIPv4Broadcasts(localPort int) {
|
||||
bb, err := beacon.NewBroadcast(localPort)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln("discover: Start local v4:", err)
|
||||
}
|
||||
l.Infoln("Local discovery over IPv4 unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
bb := beacon.NewBroadcast(localPort)
|
||||
d.beacons = append(d.beacons, bb)
|
||||
go d.recvAnnouncements(bb)
|
||||
bb.ServeBackground()
|
||||
}
|
||||
|
||||
func (d *Discoverer) startLocalIPv6Multicasts(localMCAddr string) {
|
||||
@@ -110,7 +104,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
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestGlobalDiscovery(t *testing.T) {
|
||||
|
||||
addrs := d.Lookup(device)
|
||||
if len(addrs) != 2 {
|
||||
t.Fatal("Wrong numer of addresses", addrs)
|
||||
t.Fatal("Wrong number of addresses", addrs)
|
||||
}
|
||||
|
||||
for _, addr := range []string{"test.com:1234", "best.com:2345"} {
|
||||
@@ -119,10 +119,10 @@ func TestGlobalDiscovery(t *testing.T) {
|
||||
|
||||
addrs = d.Lookup(device)
|
||||
if len(addrs) != 2 {
|
||||
t.Fatal("Wrong numer of addresses", addrs)
|
||||
t.Fatal("Wrong number of addresses", addrs)
|
||||
}
|
||||
|
||||
// Answer should be cached, so number of lookups should have not incresed
|
||||
// Answer should be cached, so number of lookups should have not increased
|
||||
for _, c := range []*DummyClient{c1, c2, c3} {
|
||||
if len(c.lookups) != 1 || c.lookups[0] != device {
|
||||
t.Fatal("Wrong lookups")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ func Convert(pattern string, flags int) (*regexp.Regexp, error) {
|
||||
return regexp.Compile(pattern)
|
||||
}
|
||||
|
||||
// Matches the pattern against the string, with the given flags,
|
||||
// and returns true if the match is successful.
|
||||
// Match matches the pattern against the string, with the given flags, and
|
||||
// returns true if the match is successful.
|
||||
func Match(pattern, s string, flags int) (bool, error) {
|
||||
exp, err := Convert(pattern, flags)
|
||||
if err != nil {
|
||||
|
||||
@@ -55,6 +55,7 @@ var testcases = []testcase{
|
||||
{"foo.txt", "foo.TXT", CaseFold, true},
|
||||
{"(?i)foo.txt", "foo.TXT", 0, true},
|
||||
{"(?i)**foo.txt", "/dev/tmp/foo.TXT", 0, true},
|
||||
{"(?i)!**foo.txt", "/dev/tmp/foo.TXT", 0, false},
|
||||
|
||||
// These characters are literals in glob, but not in regexp.
|
||||
{"hey$hello", "hey$hello", 0, true},
|
||||
|
||||
@@ -30,9 +30,8 @@ type Pattern struct {
|
||||
func (p Pattern) String() string {
|
||||
if p.include {
|
||||
return p.match.String()
|
||||
} else {
|
||||
return "(?exclude)" + p.match.String()
|
||||
}
|
||||
return "(?exclude)" + p.match.String()
|
||||
}
|
||||
|
||||
type Matcher struct {
|
||||
@@ -95,6 +94,10 @@ func (m *Matcher) Parse(r io.Reader, file string) error {
|
||||
}
|
||||
|
||||
func (m *Matcher) Match(file string) (result bool) {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
@@ -128,6 +131,10 @@ func (m *Matcher) Match(file string) (result bool) {
|
||||
|
||||
// Patterns return a list of the loaded regexp patterns, as strings
|
||||
func (m *Matcher) Patterns() []string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ func (s *stateTracker) setState(newState folderState) {
|
||||
}
|
||||
|
||||
s.current = newState
|
||||
s.err = nil
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
@@ -111,3 +110,26 @@ func (s *stateTracker) setError(err error) {
|
||||
}
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
// clearError sets the folder state to FolderIdle and clears the error
|
||||
func (s *stateTracker) clearError() {
|
||||
s.mut.Lock()
|
||||
if s.current == FolderError {
|
||||
eventData := map[string]interface{}{
|
||||
"folder": s.folder,
|
||||
"to": FolderIdle.String(),
|
||||
"from": s.current.String(),
|
||||
}
|
||||
|
||||
if !s.changed.IsZero() {
|
||||
eventData["duration"] = time.Since(s.changed).Seconds()
|
||||
}
|
||||
|
||||
s.current = FolderIdle
|
||||
s.err = nil
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
}
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
stdsync "sync"
|
||||
"time"
|
||||
@@ -33,14 +35,17 @@ import (
|
||||
"github.com/syncthing/syncthing/internal/sync"
|
||||
"github.com/syncthing/syncthing/internal/versioner"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
@@ -48,13 +53,18 @@ type service interface {
|
||||
Stop()
|
||||
Jobs() ([]string, []string) // In progress, Queued
|
||||
BringToFront(string)
|
||||
DelayScan(d time.Duration)
|
||||
IndexUpdated() // Remote index was updated notification
|
||||
|
||||
setState(state folderState)
|
||||
setError(err error)
|
||||
clearError()
|
||||
getState() (folderState, time.Time, error)
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
*suture.Supervisor
|
||||
|
||||
cfg *config.Wrapper
|
||||
db *leveldb.DB
|
||||
finder *db.BlockFinder
|
||||
@@ -83,6 +93,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 (
|
||||
@@ -94,29 +107,38 @@ 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),
|
||||
Supervisor: suture.New("model", suture.Spec{
|
||||
Log: func(line string) {
|
||||
if debug {
|
||||
l.Debugln(line)
|
||||
}
|
||||
},
|
||||
}),
|
||||
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()
|
||||
@@ -125,15 +147,16 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
|
||||
return m
|
||||
}
|
||||
|
||||
// Starts deadlock detector on the models locks which causes panics in case
|
||||
// the locks cannot be acquired in the given timeout period.
|
||||
// StartDeadlockDetector starts a deadlock detector on the models locks which
|
||||
// causes panics in case the locks cannot be acquired in the given timeout
|
||||
// period.
|
||||
func (m *Model) StartDeadlockDetector(timeout time.Duration) {
|
||||
l.Infof("Starting deadlock detector with %v timeout", timeout)
|
||||
deadlockDetect(m.fmut, timeout)
|
||||
deadlockDetect(m.pmut, timeout)
|
||||
}
|
||||
|
||||
// StartRW starts read/write processing on the current model. When in
|
||||
// StartFolderRW starts read/write processing on the current model. When in
|
||||
// read/write mode the model will attempt to keep in sync with the cluster by
|
||||
// pulling needed files from peer devices.
|
||||
func (m *Model) StartFolderRW(folder string) {
|
||||
@@ -156,19 +179,22 @@ func (m *Model) StartFolderRW(folder string) {
|
||||
if !ok {
|
||||
l.Fatalf("Requested versioning type %q that does not exist", cfg.Versioning.Type)
|
||||
}
|
||||
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)
|
||||
versioner := factory(folder, cfg.Path(), cfg.Versioning.Params)
|
||||
if service, ok := versioner.(suture.Service); ok {
|
||||
// The versioner implements the suture.Service interface, so
|
||||
// expects to be run in the background in addition to being called
|
||||
// when files are going to be archived.
|
||||
m.Add(service)
|
||||
}
|
||||
p.versioner = versioner
|
||||
}
|
||||
|
||||
go p.Serve()
|
||||
}
|
||||
|
||||
// StartRO starts read only processing on the current model. When in
|
||||
// read only mode the model will announce files to the cluster but not
|
||||
// pull in any external changes.
|
||||
// StartFolderRO starts read only processing on the current model. When in
|
||||
// read only mode the model will announce files to the cluster but not pull in
|
||||
// any external changes.
|
||||
func (m *Model) StartFolderRO(folder string) {
|
||||
m.fmut.Lock()
|
||||
cfg, ok := m.folderCfgs[folder]
|
||||
@@ -243,7 +269,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
|
||||
return res
|
||||
}
|
||||
|
||||
// Returns statistics about each device
|
||||
// DeviceStatistics returns statistics about each device
|
||||
func (m *Model) DeviceStatistics() map[string]stats.DeviceStatistics {
|
||||
var res = make(map[string]stats.DeviceStatistics)
|
||||
for id := range m.cfg.Devices() {
|
||||
@@ -252,7 +278,7 @@ func (m *Model) DeviceStatistics() map[string]stats.DeviceStatistics {
|
||||
return res
|
||||
}
|
||||
|
||||
// Returns statistics about each folder
|
||||
// FolderStatistics returns statistics about each folder
|
||||
func (m *Model) FolderStatistics() map[string]stats.FolderStatistics {
|
||||
var res = make(map[string]stats.FolderStatistics)
|
||||
for id := range m.cfg.Folders() {
|
||||
@@ -261,7 +287,8 @@ func (m *Model) FolderStatistics() map[string]stats.FolderStatistics {
|
||||
return res
|
||||
}
|
||||
|
||||
// Returns the completion status, in percent, for the given device and folder.
|
||||
// Completion returns the completion status, in percent, for the given device
|
||||
// and folder.
|
||||
func (m *Model) Completion(device protocol.DeviceID, folder string) float64 {
|
||||
var tot int64
|
||||
|
||||
@@ -375,9 +402,9 @@ func (m *Model) NeedSize(folder string) (nfiles int, bytes int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// NeedFiles returns paginated list of currently needed files in progress, queued,
|
||||
// and to be queued on next puller iteration, as well as the total number of
|
||||
// files currently needed.
|
||||
// NeedFolderFiles returns paginated list of currently needed files in
|
||||
// progress, queued, and to be queued on next puller iteration, as well as the
|
||||
// total number of files currently needed.
|
||||
func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int) {
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
@@ -465,8 +492,15 @@ func (m *Model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.F
|
||||
|
||||
m.fmut.RLock()
|
||||
files, ok := m.folderFiles[folder]
|
||||
runner := m.folderRunners[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
if runner != nil {
|
||||
// Runner may legitimately not be set if this is the "cleanup" Index
|
||||
// message at startup.
|
||||
defer runner.IndexUpdated()
|
||||
}
|
||||
|
||||
if !ok {
|
||||
l.Fatalf("Index for nonexistant folder %q", folder)
|
||||
}
|
||||
@@ -517,7 +551,8 @@ func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []prot
|
||||
}
|
||||
|
||||
m.fmut.RLock()
|
||||
files, ok := m.folderFiles[folder]
|
||||
files := m.folderFiles[folder]
|
||||
runner, ok := m.folderRunners[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
if !ok {
|
||||
@@ -550,6 +585,8 @@ func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []prot
|
||||
"items": len(fs),
|
||||
"version": files.LocalVersion(deviceID),
|
||||
})
|
||||
|
||||
runner.IndexUpdated()
|
||||
}
|
||||
|
||||
func (m *Model) folderSharedWith(folder string, deviceID protocol.DeviceID) bool {
|
||||
@@ -718,33 +755,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 {
|
||||
@@ -756,7 +821,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
|
||||
@@ -858,10 +923,7 @@ func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
|
||||
}
|
||||
|
||||
m.fmut.RLock()
|
||||
var patterns []string
|
||||
if matcher := m.folderIgnores[folder]; matcher != nil {
|
||||
patterns = matcher.Patterns()
|
||||
}
|
||||
patterns := m.folderIgnores[folder].Patterns()
|
||||
m.fmut.RUnlock()
|
||||
|
||||
return lines, patterns, nil
|
||||
@@ -1010,7 +1072,7 @@ func sendIndexTo(initial bool, minLocalVer int64, conn protocol.Connection, fold
|
||||
maxLocalVer = f.LocalVersion
|
||||
}
|
||||
|
||||
if (ignores != nil && ignores.Match(f.Name)) || symlinkInvalid(f.IsSymlink()) {
|
||||
if ignores.Match(f.Name) || symlinkInvalid(f.IsSymlink()) {
|
||||
if debug {
|
||||
l.Debugln("not sending update for ignored/unsupported symlink", f)
|
||||
}
|
||||
@@ -1040,7 +1102,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
|
||||
})
|
||||
|
||||
@@ -1063,6 +1125,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,
|
||||
@@ -1210,9 +1277,10 @@ 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: folderCfg.Hashers,
|
||||
Hashers: m.numHashers(folder),
|
||||
ShortID: m.shortID,
|
||||
}
|
||||
|
||||
@@ -1220,6 +1288,12 @@ nextSub:
|
||||
|
||||
fchan, err := w.Walk()
|
||||
if err != nil {
|
||||
// The error we get here is likely an OS level error, which might not be
|
||||
// as readable as our health check errors. Check if we can get a health
|
||||
// check error first, and use that if it's available.
|
||||
if ferr := m.CheckFolderHealth(folder); ferr != nil {
|
||||
err = ferr
|
||||
}
|
||||
runner.setError(err)
|
||||
return err
|
||||
}
|
||||
@@ -1248,12 +1322,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
|
||||
@@ -1277,11 +1352,15 @@ nextSub:
|
||||
}
|
||||
|
||||
if len(batch) == batchSizeFiles {
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
iterError = err
|
||||
return false
|
||||
}
|
||||
m.updateLocals(folder, batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
if (ignores != nil && ignores.Match(f.Name)) || symlinkInvalid(f.IsSymlink()) {
|
||||
if ignores.Match(f.Name) || symlinkInvalid(f.IsSymlink()) {
|
||||
// File has been ignored or an unsupported symlink. Set invalid bit.
|
||||
if debug {
|
||||
l.Debugln("setting invalid bit on ignored", f)
|
||||
@@ -1314,7 +1393,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)
|
||||
}
|
||||
|
||||
@@ -1322,6 +1410,37 @@ nextSub:
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) DelayScan(folder string, next time.Duration) {
|
||||
m.fmut.Lock()
|
||||
runner, ok := m.folderRunners[folder]
|
||||
m.fmut.Unlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
runner.DelayScan(next)
|
||||
}
|
||||
|
||||
// numHashers returns the number of hasher routines to use for a given folder,
|
||||
// taking into account configuration and available CPU cores.
|
||||
func (m *Model) numHashers(folder string) int {
|
||||
m.fmut.Lock()
|
||||
folderCfg := m.folderCfgs[folder]
|
||||
numFolders := len(m.folderCfgs)
|
||||
m.fmut.Unlock()
|
||||
|
||||
if folderCfg.Hashers > 0 {
|
||||
// Specific value set in the config, use that.
|
||||
return folderCfg.Hashers
|
||||
}
|
||||
|
||||
if perFolder := runtime.GOMAXPROCS(-1) / numFolders; perFolder > 0 {
|
||||
// We have CPUs to spare, divide them per folder.
|
||||
return perFolder
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer device
|
||||
func (m *Model) clusterConfig(device protocol.DeviceID) protocol.ClusterConfigMessage {
|
||||
cm := protocol.ClusterConfigMessage{
|
||||
@@ -1538,7 +1657,7 @@ func (m *Model) Availability(folder, file string) []protocol.DeviceID {
|
||||
return availableDevices
|
||||
}
|
||||
|
||||
// Bump the given files priority in the job queue
|
||||
// BringToFront bumps the given files priority in the job queue.
|
||||
func (m *Model) BringToFront(folder, file string) {
|
||||
m.pmut.RLock()
|
||||
defer m.pmut.RUnlock()
|
||||
@@ -1549,9 +1668,8 @@ func (m *Model) BringToFront(folder, file string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns current folder error, or nil if the folder is healthy.
|
||||
// Updates the Invalid field on the folder configuration struct, and emits a
|
||||
// ConfigSaved event which causes a GUI refresh.
|
||||
// CheckFolderHealth checks the folder for common errors and returns the
|
||||
// current folder error, or nil if the folder is healthy.
|
||||
func (m *Model) CheckFolderHealth(id string) error {
|
||||
folder, ok := m.cfg.Folders()[id]
|
||||
if !ok {
|
||||
@@ -1572,7 +1690,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()
|
||||
}
|
||||
@@ -1603,7 +1721,7 @@ func (m *Model) CheckFolderHealth(id string) error {
|
||||
} else if oldErr != nil {
|
||||
l.Infof("Folder %q error is cleared, restarting", folder.ID)
|
||||
if runnerExists {
|
||||
runner.setState(FolderIdle)
|
||||
runner.clearError()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1625,10 +1743,41 @@ func (m *Model) String() string {
|
||||
return fmt.Sprintf("model@%p", m)
|
||||
}
|
||||
|
||||
func (m *Model) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// TODO: This should not use reflect, and should take more care to try to handle stuff without restart.
|
||||
|
||||
// Adding, removing or changing folders requires restart
|
||||
if !reflect.DeepEqual(from.Folders, to.Folders) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Removing a device requres restart
|
||||
toDevs := make(map[protocol.DeviceID]bool, len(from.Devices))
|
||||
for _, dev := range to.Devices {
|
||||
toDevs[dev.DeviceID] = true
|
||||
}
|
||||
for _, dev := range from.Devices {
|
||||
if _, ok := toDevs[dev.DeviceID]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All of the generic options require restart
|
||||
if !reflect.DeepEqual(from.Options, to.Options) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func symlinkInvalid(isLink bool) bool {
|
||||
if !symlinks.Supported && isLink {
|
||||
SymlinkWarning.Do(func() {
|
||||
l.Warnln("Symlinks are disabled, unsupported or require Administrator priviledges. This might cause your folder to appear out of sync.")
|
||||
l.Warnln("Symlinks are disabled, unsupported or require Administrator privileges. This might cause your folder to appear out of sync.")
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -333,21 +317,22 @@ func TestDeviceRename(t *testing.T) {
|
||||
|
||||
defer os.Remove("tmpconfig.xml")
|
||||
|
||||
cfg := config.New(device1)
|
||||
cfg.Devices = []config.DeviceConfiguration{
|
||||
rawCfg := config.New(device1)
|
||||
rawCfg.Devices = []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
},
|
||||
}
|
||||
cfg := config.Wrap("tmpconfig.xml", rawCfg)
|
||||
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
m := NewModel(config.Wrap("tmpconfig.xml", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
if cfg.Devices[0].Name != "" {
|
||||
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
|
||||
if cfg.Devices()[device1].Name != "" {
|
||||
t.Errorf("Device already has a name")
|
||||
}
|
||||
|
||||
m.ClusterConfig(device1, ccm)
|
||||
if cfg.Devices[0].Name != "" {
|
||||
if cfg.Devices()[device1].Name != "" {
|
||||
t.Errorf("Device already has a name")
|
||||
}
|
||||
|
||||
@@ -358,13 +343,13 @@ func TestDeviceRename(t *testing.T) {
|
||||
},
|
||||
}
|
||||
m.ClusterConfig(device1, ccm)
|
||||
if cfg.Devices[0].Name != "tester" {
|
||||
if cfg.Devices()[device1].Name != "tester" {
|
||||
t.Errorf("Device did not get a name")
|
||||
}
|
||||
|
||||
ccm.Options[0].Value = "tester2"
|
||||
m.ClusterConfig(device1, ccm)
|
||||
if cfg.Devices[0].Name != "tester" {
|
||||
if cfg.Devices()[device1].Name != "tester" {
|
||||
t.Errorf("Device name got overwritten")
|
||||
}
|
||||
|
||||
@@ -474,6 +459,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)
|
||||
@@ -760,7 +746,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
|
||||
b := func(isfile bool, path ...string) protocol.FileInfo {
|
||||
var flags uint32 = protocol.FlagDirectory
|
||||
flags := uint32(protocol.FlagDirectory)
|
||||
blocks := []protocol.BlockInfo{}
|
||||
if isfile {
|
||||
flags = 0
|
||||
@@ -1009,7 +995,7 @@ func TestGlobalDirectorySelfFixing(t *testing.T) {
|
||||
m.AddFolder(defaultFolderConfig)
|
||||
|
||||
b := func(isfile bool, path ...string) protocol.FileInfo {
|
||||
var flags uint32 = protocol.FlagDirectory
|
||||
flags := uint32(protocol.FlagDirectory)
|
||||
blocks := []protocol.BlockInfo{}
|
||||
if isfile {
|
||||
flags = 0
|
||||
@@ -1165,56 +1151,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 +1175,5 @@ func BenchmarkTree_00100_10(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GlobalDirectoryTree("default", "", -1, false)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"time"
|
||||
@@ -27,8 +28,8 @@ type ProgressEmitter struct {
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// Creates a new progress emitter which emits DownloadProgress events every
|
||||
// interval.
|
||||
// NewProgressEmitter creates a new progress emitter which emits
|
||||
// DownloadProgress events every interval.
|
||||
func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter {
|
||||
t := &ProgressEmitter{
|
||||
stop: make(chan struct{}),
|
||||
@@ -37,13 +38,15 @@ func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter {
|
||||
timer: time.NewTimer(time.Millisecond),
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
t.Changed(cfg.Raw())
|
||||
|
||||
t.CommitConfiguration(config.Configuration{}, cfg.Raw())
|
||||
cfg.Subscribe(t)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Starts progress emitter which starts emitting DownloadProgress events as
|
||||
// the progress happens.
|
||||
// Serve starts the progress emitter which starts emitting DownloadProgress
|
||||
// events as the progress happens.
|
||||
func (t *ProgressEmitter) Serve() {
|
||||
for {
|
||||
select {
|
||||
@@ -81,19 +84,25 @@ func (t *ProgressEmitter) Serve() {
|
||||
}
|
||||
}
|
||||
|
||||
// Interface method to handle configuration changes
|
||||
func (t *ProgressEmitter) Changed(cfg config.Configuration) error {
|
||||
t.mut.Lock()
|
||||
defer t.mut.Unlock()
|
||||
|
||||
t.interval = time.Duration(cfg.Options.ProgressUpdateIntervalS) * time.Second
|
||||
if debug {
|
||||
l.Debugln("progress emitter: updated interval", t.interval)
|
||||
}
|
||||
// VerifyConfiguration implements the config.Committer interface
|
||||
func (t *ProgressEmitter) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stops the emitter.
|
||||
// CommitConfiguration implements the config.Committer interface
|
||||
func (t *ProgressEmitter) CommitConfiguration(from, to config.Configuration) bool {
|
||||
t.mut.Lock()
|
||||
defer t.mut.Unlock()
|
||||
|
||||
t.interval = time.Duration(to.Options.ProgressUpdateIntervalS) * time.Second
|
||||
if debug {
|
||||
l.Debugln("progress emitter: updated interval", t.interval)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Stop stops the emitter.
|
||||
func (t *ProgressEmitter) Stop() {
|
||||
t.stop <- struct{}{}
|
||||
}
|
||||
@@ -112,7 +121,7 @@ func (t *ProgressEmitter) Register(s *sharedPullerState) {
|
||||
t.registry[filepath.Join(s.folder, s.file.Name)] = s
|
||||
}
|
||||
|
||||
// Deregister a puller which will stop boardcasting pullers state.
|
||||
// Deregister a puller which will stop broadcasting pullers state.
|
||||
func (t *ProgressEmitter) Deregister(s *sharedPullerState) {
|
||||
t.mut.Lock()
|
||||
defer t.mut.Unlock()
|
||||
@@ -122,7 +131,7 @@ func (t *ProgressEmitter) Deregister(s *sharedPullerState) {
|
||||
delete(t.registry, filepath.Join(s.folder, s.file.Name))
|
||||
}
|
||||
|
||||
// Returns number of bytes completed in the given folder.
|
||||
// BytesCompleted returns the number of bytes completed in the given folder.
|
||||
func (t *ProgressEmitter) BytesCompleted(folder string) (bytes int64) {
|
||||
t.mut.Lock()
|
||||
defer t.mut.Unlock()
|
||||
@@ -137,3 +146,7 @@ func (t *ProgressEmitter) BytesCompleted(folder string) (bytes int64) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *ProgressEmitter) String() string {
|
||||
return fmt.Sprintf("ProgressEmitter@%p", t)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ import (
|
||||
type roFolder struct {
|
||||
stateTracker
|
||||
|
||||
folder string
|
||||
intv time.Duration
|
||||
model *Model
|
||||
stop chan struct{}
|
||||
folder string
|
||||
intv time.Duration
|
||||
timer *time.Timer
|
||||
model *Model
|
||||
stop chan struct{}
|
||||
delayScan chan time.Duration
|
||||
}
|
||||
|
||||
func newROFolder(model *Model, folder string, interval time.Duration) *roFolder {
|
||||
@@ -29,10 +31,12 @@ func newROFolder(model *Model, folder string, interval time.Duration) *roFolder
|
||||
folder: folder,
|
||||
mut: sync.NewMutex(),
|
||||
},
|
||||
folder: folder,
|
||||
intv: interval,
|
||||
model: model,
|
||||
stop: make(chan struct{}),
|
||||
folder: folder,
|
||||
intv: interval,
|
||||
timer: time.NewTimer(time.Millisecond),
|
||||
model: model,
|
||||
stop: make(chan struct{}),
|
||||
delayScan: make(chan time.Duration),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,13 +46,17 @@ func (s *roFolder) Serve() {
|
||||
defer l.Debugln(s, "exiting")
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Millisecond)
|
||||
defer timer.Stop()
|
||||
defer func() {
|
||||
s.timer.Stop()
|
||||
}()
|
||||
|
||||
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
|
||||
timer.Reset(time.Duration(sleepNanos) * time.Nanosecond)
|
||||
s.timer.Reset(time.Duration(sleepNanos) * time.Nanosecond)
|
||||
}
|
||||
|
||||
initialScanCompleted := false
|
||||
@@ -57,7 +65,7 @@ func (s *roFolder) Serve() {
|
||||
case <-s.stop:
|
||||
return
|
||||
|
||||
case <-timer.C:
|
||||
case <-s.timer.C:
|
||||
if err := s.model.CheckFolderHealth(s.folder); err != nil {
|
||||
l.Infoln("Skipping folder", s.folder, "scan due to folder error:", err)
|
||||
reschedule()
|
||||
@@ -88,6 +96,9 @@ func (s *roFolder) Serve() {
|
||||
}
|
||||
|
||||
reschedule()
|
||||
|
||||
case next := <-s.delayScan:
|
||||
s.timer.Reset(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,6 +107,9 @@ func (s *roFolder) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
func (s *roFolder) IndexUpdated() {
|
||||
}
|
||||
|
||||
func (s *roFolder) String() string {
|
||||
return fmt.Sprintf("roFolder/%s@%p", s.folder, s)
|
||||
}
|
||||
@@ -105,3 +119,7 @@ func (s *roFolder) BringToFront(string) {}
|
||||
func (s *roFolder) Jobs() ([]string, []string) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *roFolder) DelayScan(next time.Duration) {
|
||||
s.delayScan <- next
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
const (
|
||||
pauseIntv = 60 * time.Second
|
||||
nextPullIntv = 10 * time.Second
|
||||
checkPullIntv = 1 * time.Second
|
||||
shortPullIntv = 5 * time.Second
|
||||
)
|
||||
|
||||
// A pullBlockState is passed to the puller routine for each block that needs
|
||||
@@ -57,23 +57,27 @@ 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
|
||||
dbUpdates chan protocol.FileInfo
|
||||
stop chan struct{}
|
||||
queue *jobQueue
|
||||
dbUpdates chan protocol.FileInfo
|
||||
scanTimer *time.Timer
|
||||
pullTimer *time.Timer
|
||||
delayScan chan time.Duration
|
||||
remoteIndex chan struct{} // An index update was received, we should re-evaluate needs
|
||||
}
|
||||
|
||||
func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFolder {
|
||||
@@ -83,24 +87,35 @@ 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(),
|
||||
stop: make(chan struct{}),
|
||||
queue: newJobQueue(),
|
||||
pullTimer: time.NewTimer(shortPullIntv),
|
||||
scanTimer: time.NewTimer(time.Millisecond), // The first scan should be done immediately.
|
||||
delayScan: make(chan time.Duration),
|
||||
remoteIndex: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a notification if we're busy doing a pull when it comes.
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -109,12 +124,9 @@ func (p *rwFolder) Serve() {
|
||||
defer l.Debugln(p, "exiting")
|
||||
}
|
||||
|
||||
pullTimer := time.NewTimer(checkPullIntv)
|
||||
scanTimer := time.NewTimer(time.Millisecond) // The first scan should be done immediately.
|
||||
|
||||
defer func() {
|
||||
pullTimer.Stop()
|
||||
scanTimer.Stop()
|
||||
p.pullTimer.Stop()
|
||||
p.scanTimer.Stop()
|
||||
// TODO: Should there be an actual FolderStopped state?
|
||||
p.setState(FolderIdle)
|
||||
}()
|
||||
@@ -135,7 +147,7 @@ func (p *rwFolder) Serve() {
|
||||
if debug {
|
||||
l.Debugln(p, "next rescan in", intv)
|
||||
}
|
||||
scanTimer.Reset(intv)
|
||||
p.scanTimer.Reset(intv)
|
||||
}
|
||||
|
||||
// We don't start pulling files until a scan has been completed.
|
||||
@@ -146,17 +158,19 @@ func (p *rwFolder) Serve() {
|
||||
case <-p.stop:
|
||||
return
|
||||
|
||||
// TODO: We could easily add a channel here for notifications from
|
||||
// Index(), so that we immediately start a pull when new index
|
||||
// information is available. Before that though, I'd like to build a
|
||||
// repeatable benchmark of how long it takes to sync a change from
|
||||
// device A to device B, so we have something to work against.
|
||||
case <-pullTimer.C:
|
||||
case <-p.remoteIndex:
|
||||
prevVer = 0
|
||||
p.pullTimer.Reset(shortPullIntv)
|
||||
if debug {
|
||||
l.Debugln(p, "remote index updated, rescheduling pull")
|
||||
}
|
||||
|
||||
case <-p.pullTimer.C:
|
||||
if !initialScanCompleted {
|
||||
if debug {
|
||||
l.Debugln(p, "skip (initial)")
|
||||
}
|
||||
pullTimer.Reset(nextPullIntv)
|
||||
p.pullTimer.Reset(nextPullIntv)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -180,7 +194,7 @@ func (p *rwFolder) Serve() {
|
||||
if debug {
|
||||
l.Debugln(p, "skip (curVer == prevVer)", prevVer)
|
||||
}
|
||||
pullTimer.Reset(checkPullIntv)
|
||||
p.pullTimer.Reset(nextPullIntv)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -218,7 +232,7 @@ func (p *rwFolder) Serve() {
|
||||
if debug {
|
||||
l.Debugln(p, "next pull in", nextPullIntv)
|
||||
}
|
||||
pullTimer.Reset(nextPullIntv)
|
||||
p.pullTimer.Reset(nextPullIntv)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -231,7 +245,7 @@ func (p *rwFolder) Serve() {
|
||||
if debug {
|
||||
l.Debugln(p, "next pull in", pauseIntv)
|
||||
}
|
||||
pullTimer.Reset(pauseIntv)
|
||||
p.pullTimer.Reset(pauseIntv)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -240,7 +254,7 @@ func (p *rwFolder) Serve() {
|
||||
// The reason for running the scanner from within the puller is that
|
||||
// this is the easiest way to make sure we are not doing both at the
|
||||
// same time.
|
||||
case <-scanTimer.C:
|
||||
case <-p.scanTimer.C:
|
||||
if err := p.model.CheckFolderHealth(p.folder); err != nil {
|
||||
l.Infoln("Skipping folder", p.folder, "scan due to folder error:", err)
|
||||
rescheduleScan()
|
||||
@@ -268,6 +282,9 @@ func (p *rwFolder) Serve() {
|
||||
l.Infoln("Completed initial scan (rw) of folder", p.folder)
|
||||
initialScanCompleted = true
|
||||
}
|
||||
|
||||
case next := <-p.delayScan:
|
||||
p.scanTimer.Reset(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,6 +293,17 @@ func (p *rwFolder) Stop() {
|
||||
close(p.stop)
|
||||
}
|
||||
|
||||
func (p *rwFolder) IndexUpdated() {
|
||||
select {
|
||||
case p.remoteIndex <- struct{}{}:
|
||||
default:
|
||||
// We might be busy doing a pull and thus not reading from this
|
||||
// channel. The channel is 1-buffered, so one notification will be
|
||||
// queued to ensure we recheck after the pull, but beyond that we must
|
||||
// make sure to not block index receiving.
|
||||
}
|
||||
}
|
||||
|
||||
func (p *rwFolder) String() string {
|
||||
return fmt.Sprintf("rwFolder/%s@%p", p.folder, p)
|
||||
}
|
||||
@@ -511,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",
|
||||
})
|
||||
@@ -519,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 {
|
||||
@@ -548,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)
|
||||
@@ -571,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
|
||||
@@ -593,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",
|
||||
})
|
||||
@@ -610,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)
|
||||
@@ -631,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",
|
||||
})
|
||||
@@ -652,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,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",
|
||||
})
|
||||
@@ -735,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) {
|
||||
@@ -764,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",
|
||||
})
|
||||
@@ -806,7 +883,7 @@ func (p *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocks
|
||||
reused = len(file.Blocks) - len(blocks)
|
||||
if reused == 0 {
|
||||
// Otherwise, discard the file ourselves in order for the
|
||||
// sharedpuller not to panic when it fails to exlusively create a
|
||||
// sharedpuller not to panic when it fails to exclusively create a
|
||||
// file which already exists
|
||||
os.Remove(tempName)
|
||||
}
|
||||
@@ -822,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(),
|
||||
}
|
||||
@@ -840,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
|
||||
@@ -873,10 +945,10 @@ func (p *rwFolder) shortcutFile(file protocol.FileInfo) (err error) {
|
||||
}
|
||||
|
||||
p.dbUpdates <- file
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// shortcutSymlink changes the symlinks type if necessery.
|
||||
// shortcutSymlink changes the symlinks type if necessary.
|
||||
func (p *rwFolder) shortcutSymlink(file protocol.FileInfo) (err error) {
|
||||
err = symlinks.ChangeType(filepath.Join(p.dir, file.Name), file.Flags)
|
||||
if err == nil {
|
||||
@@ -893,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 {
|
||||
@@ -974,14 +1045,16 @@ 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
|
||||
}
|
||||
|
||||
// Get an fd to the temporary file. Tehcnically we don't need it until
|
||||
// Get an fd to the temporary file. Technically we don't need it until
|
||||
// after fetching the block, but if we run into an error here there is
|
||||
// no point in issuing the request to the network.
|
||||
fd, err := state.tempFile()
|
||||
if err != nil {
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1032,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
|
||||
@@ -1085,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
|
||||
@@ -1096,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.
|
||||
@@ -1116,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) {
|
||||
@@ -1131,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)
|
||||
}
|
||||
@@ -1165,6 +1217,10 @@ func (p *rwFolder) Jobs() ([]string, []string) {
|
||||
return p.queue.Jobs()
|
||||
}
|
||||
|
||||
func (p *rwFolder) DelayScan(next time.Duration) {
|
||||
p.delayScan <- next
|
||||
}
|
||||
|
||||
// dbUpdaterRoutine aggregates db updates and commits them in batches no
|
||||
// larger than 1000 items, and no more delayed than 2 seconds.
|
||||
func (p *rwFolder) dbUpdaterRoutine() {
|
||||
@@ -1190,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]
|
||||
}
|
||||
}
|
||||
@@ -1203,6 +1261,7 @@ loop:
|
||||
|
||||
if len(batch) > 0 {
|
||||
p.model.updateLocals(p.folder, batch)
|
||||
p.model.receivedFile(p.folder, batch[len(batch)-1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1246,5 +1305,13 @@ func moveForConflict(name string) error {
|
||||
ext := filepath.Ext(name)
|
||||
withoutExt := name[:len(name)-len(ext)]
|
||||
newName := withoutExt + time.Now().Format(".sync-conflict-20060102-150405") + ext
|
||||
return os.Rename(name, newName)
|
||||
err := os.Rename(name, newName)
|
||||
if os.IsNotExist(err) {
|
||||
// We were supposed to move a file away but it does not exist. Either
|
||||
// the user has already moved it away, or the conflict was between a
|
||||
// remote modification and a local delete. In either way it does not
|
||||
// matter, go ahead as if the move succeeded.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
|
||||
p.handleFile(file, copyChan, finisherChan)
|
||||
|
||||
// Receive a block at puller, to indicate that atleast a single copier
|
||||
// Receive a block at puller, to indicate that at least a single copier
|
||||
// loop has been performed.
|
||||
toPull := <-pullChan
|
||||
// Wait until copier is trying to pass something down to the puller again
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user