Compare commits
386 Commits
v0.11.0-be
...
v0.11.15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
909d60464e | ||
|
|
d855abf8b0 | ||
|
|
b611f72e08 | ||
|
|
44e3bec42e | ||
|
|
a04b005e93 | ||
|
|
1b837116e6 | ||
|
|
d16b04b683 | ||
|
|
d2e7a8004d | ||
|
|
0c28216ee5 | ||
|
|
5bb8ea7449 | ||
|
|
b78c515724 | ||
|
|
cb1a7a7bdc | ||
|
|
b8dcb7c884 | ||
|
|
1ded554a15 | ||
|
|
bc0ce7b820 | ||
|
|
1da3a57fe7 | ||
|
|
8697982302 | ||
|
|
c4168cf855 | ||
|
|
7023d3ca2b | ||
|
|
57a5d13c47 | ||
|
|
500b96240b | ||
|
|
13d961d41d | ||
|
|
fddc4c2fc0 | ||
|
|
061ec7369f | ||
|
|
8366dbd8e0 | ||
|
|
b02047e4b5 | ||
|
|
e9545c4961 | ||
|
|
966a2b1df5 | ||
|
|
dec6540967 | ||
|
|
3fe1673ce9 | ||
|
|
e9e13474c9 | ||
|
|
aee9093848 | ||
|
|
76822c7c34 | ||
|
|
5c18d34d89 | ||
|
|
970a9c7552 | ||
|
|
37a42dc408 | ||
|
|
a03c9f9457 | ||
|
|
60004ebff1 | ||
|
|
2d9fcf6828 | ||
|
|
c8ac9721d7 | ||
|
|
b1b68b58fe | ||
|
|
ca21db9481 | ||
|
|
93ad803073 | ||
|
|
6cc7f70a65 | ||
|
|
2b0c33f74d | ||
|
|
dae1d36a23 | ||
|
|
824fa8f17a | ||
|
|
31cd0b943c | ||
|
|
070eced2f6 | ||
|
|
986f8dfb2e | ||
|
|
8c0c03eb38 | ||
|
|
fd9bc20bc5 | ||
|
|
089fca2319 | ||
|
|
e936890927 | ||
|
|
0450d48f89 | ||
|
|
2463819a3d | ||
|
|
2b2cae2d50 | ||
|
|
0f1b40da71 | ||
|
|
f73d5a9ab2 | ||
|
|
4eb0e24c6e | ||
|
|
1d2235abe7 | ||
|
|
d347e54acb | ||
|
|
b5198d8119 | ||
|
|
b8b5c5ff34 | ||
|
|
a738490a3b | ||
|
|
54a8de2059 | ||
|
|
cb2c0e7ac5 | ||
|
|
510d309b8a | ||
|
|
a7ce2a7aa5 | ||
|
|
cfe24ecdd9 | ||
|
|
c5e9cb025c | ||
|
|
c3d07d60ca | ||
|
|
a0897a7456 | ||
|
|
c50ba9267c | ||
|
|
423e69916c | ||
|
|
b56c76f8ad | ||
|
|
cb2d2f000f | ||
|
|
69af77a3bd | ||
|
|
7767746d3e | ||
|
|
7219aaeb89 | ||
|
|
7af1863e81 | ||
|
|
4beb42bf45 | ||
|
|
12a3086a9e | ||
|
|
198725216f | ||
|
|
08647f1267 | ||
|
|
87811efc30 | ||
|
|
82c3e6f87f | ||
|
|
1ac40a3043 | ||
|
|
127b0c3332 | ||
|
|
a6d9150b14 | ||
|
|
7e5197c566 | ||
|
|
2d217e72bd | ||
|
|
12331cc62b | ||
|
|
2449f1e1b6 | ||
|
|
6a6593c656 | ||
|
|
ad220d61f9 | ||
|
|
1e35383b4d | ||
|
|
c8457ab005 | ||
|
|
070a308217 | ||
|
|
c1f4477376 | ||
|
|
d728320ece | ||
|
|
fee0d7168a | ||
|
|
7c23b32de3 | ||
|
|
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 | ||
|
|
15b87ae297 | ||
|
|
02fdf59839 | ||
|
|
d9da02b7a8 | ||
|
|
8f2ad6418d | ||
|
|
ff984425a3 | ||
|
|
ac1058359f | ||
|
|
9afbca3001 | ||
|
|
ec3f17cb9c | ||
|
|
73b9d5c5f9 | ||
|
|
ecc8591c95 | ||
|
|
696b67e4b1 | ||
|
|
266a5116a1 | ||
|
|
131f2be857 | ||
|
|
be7b3a9952 | ||
|
|
bb31b1785b | ||
|
|
2a60f4b1e9 | ||
|
|
33a4fb5a1a | ||
|
|
aece6e8b6c | ||
|
|
7bf55dd14f | ||
|
|
e158f17c2b | ||
|
|
c5027d9478 | ||
|
|
36c1d82146 | ||
|
|
bd4f404d45 | ||
|
|
43d39844f7 | ||
|
|
e041a4d212 | ||
|
|
433b923ea7 | ||
|
|
f8f1c72b44 | ||
|
|
542716e216 | ||
|
|
b35958d024 | ||
|
|
9ee3541655 | ||
|
|
bf7d84c12a | ||
|
|
34c691087e | ||
|
|
08c383012f | ||
|
|
e2420495f3 | ||
|
|
d530c5eda7 | ||
|
|
ef7420ecf6 | ||
|
|
c905a41e2a | ||
|
|
42ff4b5bf0 | ||
|
|
4fb74a32cc | ||
|
|
c741465328 | ||
|
|
fbca537a40 | ||
|
|
83420b0199 | ||
|
|
33d3ba1b45 | ||
|
|
497f85a236 | ||
|
|
a624c302ab | ||
|
|
cebe21a3af | ||
|
|
9eb679d70a | ||
|
|
6d84443db8 | ||
|
|
da8a1f242c | ||
|
|
946d98b71f | ||
|
|
dff51fc707 | ||
|
|
7d954dd5d1 | ||
|
|
c6300a5da8 | ||
|
|
9359daa0d9 | ||
|
|
2322e9cff7 | ||
|
|
a876e1e348 | ||
|
|
6a863c8f71 | ||
|
|
392b006b06 | ||
|
|
96289f42b7 | ||
|
|
1b69c2441c | ||
|
|
8ca85a4918 | ||
|
|
2a31031cbc | ||
|
|
d148cd8ccc | ||
|
|
d1cc1828b8 | ||
|
|
069e8cf122 | ||
|
|
45cbcaca6d | ||
|
|
102a2db1f3 | ||
|
|
9f81c85ca7 | ||
|
|
ba4a6fc0c5 | ||
|
|
aa803ce2ff | ||
|
|
a027a60f5d | ||
|
|
270649535e | ||
|
|
cf80ba71f4 | ||
|
|
b74df18a4a | ||
|
|
5cd2906a39 | ||
|
|
bc37b69d17 | ||
|
|
94f6e400ad | ||
|
|
b95a6ccf80 | ||
|
|
7df9c1b6e4 | ||
|
|
75348c0158 | ||
|
|
75fb14acaf | ||
|
|
5350315b68 | ||
|
|
658e39c270 | ||
|
|
ef7ce6c7e1 | ||
|
|
509e2411bf | ||
|
|
65c906f951 | ||
|
|
1f159e8233 | ||
|
|
936c76119d | ||
|
|
f45865606a | ||
|
|
cfc9776bae | ||
|
|
0cb7ed9e4e | ||
|
|
4b07609458 | ||
|
|
e41e58e781 | ||
|
|
f5030f1c2c | ||
|
|
2a48fb8e87 | ||
|
|
df6dbc5fa4 | ||
|
|
4b1d2839e8 |
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Text files use LF line endings in this repository
|
||||
* text=auto
|
||||
|
||||
# Except the dependencies, which we leave alone
|
||||
Godeps/** -text=auto
|
||||
|
||||
# Diffs on these files are meaningless
|
||||
gui.files.go -diff
|
||||
*.svg -diff
|
||||
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
|
||||
|
||||
14
AUTHORS
@@ -3,24 +3,33 @@
|
||||
Aaron Bieber <qbit@deftly.net>
|
||||
Alexander Graf <register-github@alex-graf.de>
|
||||
Andrew Dunham <andrew@du.nham.ca>
|
||||
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||
Antony Male <antony.male@gmail.com>
|
||||
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
Audrius Butkevicius <audrius.butkevicius@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>
|
||||
Brandon Philips <brandon@ifup.org>
|
||||
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 Bergmann <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||
Daniel Martí <mvdan@mvdan.cc>
|
||||
Dennis Wilson <dw@risu.io>
|
||||
Dominik Heidler <dominik@heidler.eu>
|
||||
Elias Jarlebring <jarlebring@gmail.com>
|
||||
Emil Hessman <emil@hessman.se>
|
||||
Erik Meitner <e.meitner@willystreet.coop>
|
||||
Federico Castagnini <federico.castagnini@gmail.com>
|
||||
Felix Ableitner <me@nutomic.com>
|
||||
Felix Unterpaintner <bigbear2nd@gmail.com>
|
||||
Francois-Xavier Gsell <fxgsell@gmail.com>
|
||||
Frank Isemann <frank@isemann.name>
|
||||
Gilli Sigurdsson <gilli@vx.is>
|
||||
Jakob Borg <jakob@nym.se>
|
||||
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||
@@ -31,9 +40,10 @@ Johan Vromans <jvromans@squirrel.nl>
|
||||
Karol Różycki <rozycki.karol@gmail.com>
|
||||
Ken'ichi Kamada <kamada@nanohz.org>
|
||||
Lode Hoste <zillode@zillode.be>
|
||||
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||
Lord Landon Agahnim <lordlandon@gmail.com>
|
||||
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
|
||||
Marc Pujol <kilburn@la3.org>
|
||||
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||
Michael Tilli <pyfisch@gmail.com>
|
||||
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
|
||||
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
|
||||
|
||||
30
Godeps/Godeps.json
generated
@@ -7,11 +7,11 @@
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/bkaradzic/go-lz4",
|
||||
"Rev": "93a831dcee242be64a9cc9803dda84af25932de7"
|
||||
"Rev": "4f7c2045dbd17b802370e2e6022200468abf02ba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/logger",
|
||||
"Rev": "f50d32b313bec2933a3e1049f7416a29f3413d29"
|
||||
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/luhn",
|
||||
@@ -19,27 +19,27 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/xdr",
|
||||
"Rev": "bccf335c34c01760bdc89f98c952fcda696e27d2"
|
||||
"Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/go-snappy/snappy",
|
||||
"Rev": "eaa750b9bf4dcb7cb20454be850613b66cda3273"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/juju/ratelimit",
|
||||
"Rev": "c5abe513796336ee2869745bff0638508450e9c5"
|
||||
"Rev": "772f5c38e468398c4511514f4f6aa9a4185bc0a0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kardianos/osext",
|
||||
"Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
|
||||
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syncthing/protocol",
|
||||
"Rev": "6277c0595c18d42e9db75dfe900463ef093a82d2"
|
||||
"Rev": "7996ef0d45b7743ff930048b6413b37b2c33cd85"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||
"Rev": "87e4e645d80ae9c537e8f2dee52b28036a5dd75e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/gosnappy/snappy",
|
||||
"Rev": "156a073208e131d7d2e212cb749feae7c339e846"
|
||||
"Rev": "a06509502ca32565bdf74afc1e573050023f261c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/thejerf/suture",
|
||||
@@ -59,19 +59,19 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/bcrypt",
|
||||
"Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
|
||||
"Rev": "1e856cbfdf9bc25eefca75f83f25d55e35ae72e0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||
"Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
|
||||
"Rev": "1e856cbfdf9bc25eefca75f83f25d55e35ae72e0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/transform",
|
||||
"Rev": "2076e9cab4147459c82bc81169e46c139d358547"
|
||||
"Rev": "df923bbb63f8ea3a26bb743e2a497abd0ab585f7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/unicode/norm",
|
||||
"Rev": "2076e9cab4147459c82bc81169e46c139d358547"
|
||||
"Rev": "df923bbb63f8ea3a26bb743e2a497abd0ab585f7"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
23
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// +build gofuzz
|
||||
|
||||
package lz4
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
|
||||
if len(data) < 4 {
|
||||
return 0
|
||||
}
|
||||
|
||||
ln := binary.LittleEndian.Uint32(data)
|
||||
if ln > (1 << 21) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if _, err := Decode(nil, data); err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
7
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/reader.go
generated
vendored
@@ -141,7 +141,7 @@ func Decode(dst, src []byte) ([]byte, error) {
|
||||
length += ln
|
||||
}
|
||||
|
||||
if int(d.spos+length) > len(d.src) {
|
||||
if int(d.spos+length) > len(d.src) || int(d.dpos+length) > len(d.dst) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
@@ -179,7 +179,12 @@ func Decode(dst, src []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
literal := d.dpos - d.ref
|
||||
|
||||
if literal < 4 {
|
||||
if int(d.dpos+4) > len(d.dst) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
d.cp(4, decr[literal])
|
||||
} else {
|
||||
length += 4
|
||||
|
||||
6
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/writer.go
generated
vendored
@@ -25,8 +25,10 @@
|
||||
|
||||
package lz4
|
||||
|
||||
import "encoding/binary"
|
||||
import "errors"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
minMatch = 4
|
||||
|
||||
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()
|
||||
|
||||
4
Godeps/_workspace/src/github.com/calmh/xdr/bench_test.go
generated
vendored
@@ -67,7 +67,7 @@ func BenchmarkThisEncode(b *testing.B) {
|
||||
func BenchmarkThisEncoder(b *testing.B) {
|
||||
w := xdr.NewWriter(ioutil.Discard)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := s.encodeXDR(w)
|
||||
_, err := s.EncodeXDRInto(w)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func BenchmarkThisDecoder(b *testing.B) {
|
||||
r := xdr.NewReader(rr)
|
||||
var t XDRBenchStruct
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := t.decodeXDR(r)
|
||||
err := t.DecodeXDRFrom(r)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
28
Godeps/_workspace/src/github.com/calmh/xdr/bench_xdr_test.go
generated
vendored
@@ -26,7 +26,9 @@ XDRBenchStruct Structure:
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 0x0000 | I3 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| uint8 |
|
||||
/ /
|
||||
\ uint8 Structure \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Bs0 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
@@ -69,7 +71,7 @@ struct XDRBenchStruct {
|
||||
|
||||
func (o XDRBenchStruct) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o XDRBenchStruct) MarshalXDR() ([]byte, error) {
|
||||
@@ -87,11 +89,11 @@ func (o XDRBenchStruct) MustMarshalXDR() []byte {
|
||||
func (o XDRBenchStruct) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.encodeXDR(xw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o XDRBenchStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
func (o XDRBenchStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteUint64(o.I1)
|
||||
xw.WriteUint32(o.I2)
|
||||
xw.WriteUint16(o.I3)
|
||||
@@ -111,16 +113,16 @@ func (o XDRBenchStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
|
||||
func (o *XDRBenchStruct) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *XDRBenchStruct) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *XDRBenchStruct) decodeXDR(xr *xdr.Reader) error {
|
||||
func (o *XDRBenchStruct) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.I1 = xr.ReadUint64()
|
||||
o.I2 = xr.ReadUint32()
|
||||
o.I3 = xr.ReadUint16()
|
||||
@@ -155,7 +157,7 @@ struct repeatReader {
|
||||
|
||||
func (o repeatReader) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o repeatReader) MarshalXDR() ([]byte, error) {
|
||||
@@ -173,27 +175,27 @@ func (o repeatReader) MustMarshalXDR() []byte {
|
||||
func (o repeatReader) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.encodeXDR(xw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o repeatReader) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
func (o repeatReader) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteBytes(o.data)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *repeatReader) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *repeatReader) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *repeatReader) decodeXDR(xr *xdr.Reader) error {
|
||||
func (o *repeatReader) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.data = xr.ReadBytes()
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
3
Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
generated
vendored
@@ -143,6 +143,9 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
{{end}}
|
||||
{{else}}
|
||||
_{{$fieldInfo.Name}}Size := int(xr.ReadUint32())
|
||||
if _{{$fieldInfo.Name}}Size < 0 {
|
||||
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
||||
}
|
||||
{{if ge $fieldInfo.Max 1}}
|
||||
if _{{$fieldInfo.Name}}Size > {{$fieldInfo.Max}} {
|
||||
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
||||
|
||||
4
Godeps/_workspace/src/github.com/calmh/xdr/encdec_test.go
generated
vendored
@@ -32,11 +32,11 @@ type TestStruct struct {
|
||||
|
||||
type Opaque [32]byte
|
||||
|
||||
func (u *Opaque) encodeXDR(w *xdr.Writer) (int, error) {
|
||||
func (u *Opaque) EncodeXDRInto(w *xdr.Writer) (int, error) {
|
||||
return w.WriteRaw(u[:])
|
||||
}
|
||||
|
||||
func (u *Opaque) decodeXDR(r *xdr.Reader) (int, error) {
|
||||
func (u *Opaque) DecodeXDRFrom(r *xdr.Reader) (int, error) {
|
||||
return r.ReadRaw(u[:])
|
||||
}
|
||||
|
||||
|
||||
43
Godeps/_workspace/src/github.com/calmh/xdr/encdec_xdr_test.go
generated
vendored
@@ -18,17 +18,23 @@ TestStruct Structure:
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| int |
|
||||
/ /
|
||||
\ int Structure \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| int8 |
|
||||
/ /
|
||||
\ int8 Structure \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| uint8 |
|
||||
/ /
|
||||
\ uint8 Structure \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| int16 |
|
||||
| 0x0000 | I16 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 0x0000 | UI16 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| int32 |
|
||||
| I32 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| UI32 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
@@ -52,7 +58,9 @@ TestStruct Structure:
|
||||
\ S (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Opaque |
|
||||
/ /
|
||||
\ Opaque Structure \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of SS |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
@@ -68,9 +76,9 @@ struct TestStruct {
|
||||
int I;
|
||||
int8 I8;
|
||||
uint8 UI8;
|
||||
int16 I16;
|
||||
int I16;
|
||||
unsigned int UI16;
|
||||
int32 I32;
|
||||
int I32;
|
||||
unsigned int UI32;
|
||||
hyper I64;
|
||||
unsigned hyper UI64;
|
||||
@@ -84,7 +92,7 @@ struct TestStruct {
|
||||
|
||||
func (o TestStruct) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
return o.EncodeXDRInto(xw)
|
||||
}
|
||||
|
||||
func (o TestStruct) MarshalXDR() ([]byte, error) {
|
||||
@@ -102,11 +110,11 @@ func (o TestStruct) MustMarshalXDR() []byte {
|
||||
func (o TestStruct) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.encodeXDR(xw)
|
||||
_, err := o.EncodeXDRInto(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
func (o TestStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteUint64(uint64(o.I))
|
||||
xw.WriteUint8(uint8(o.I8))
|
||||
xw.WriteUint8(o.UI8)
|
||||
@@ -124,7 +132,7 @@ func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("S", l, 1024)
|
||||
}
|
||||
xw.WriteString(o.S)
|
||||
_, err := o.C.encodeXDR(xw)
|
||||
_, err := o.C.EncodeXDRInto(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
@@ -140,16 +148,16 @@ func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
|
||||
func (o *TestStruct) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *TestStruct) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
return o.DecodeXDRFrom(xr)
|
||||
}
|
||||
|
||||
func (o *TestStruct) decodeXDR(xr *xdr.Reader) error {
|
||||
func (o *TestStruct) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.I = int(xr.ReadUint64())
|
||||
o.I8 = int8(xr.ReadUint8())
|
||||
o.UI8 = xr.ReadUint8()
|
||||
@@ -161,8 +169,11 @@ func (o *TestStruct) decodeXDR(xr *xdr.Reader) error {
|
||||
o.UI64 = xr.ReadUint64()
|
||||
o.BS = xr.ReadBytesMax(1024)
|
||||
o.S = xr.ReadStringMax(1024)
|
||||
(&o.C).decodeXDR(xr)
|
||||
(&o.C).DecodeXDRFrom(xr)
|
||||
_SSSize := int(xr.ReadUint32())
|
||||
if _SSSize < 0 {
|
||||
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
||||
}
|
||||
if _SSSize > 1024 {
|
||||
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
||||
}
|
||||
|
||||
3
Godeps/_workspace/src/github.com/calmh/xdr/reader.go
generated
vendored
@@ -68,7 +68,8 @@ func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
|
||||
if r.err != nil {
|
||||
return nil
|
||||
}
|
||||
if max > 0 && l > max {
|
||||
if l < 0 || max > 0 && l > max {
|
||||
// l may be negative on 32 bit builds
|
||||
r.err = ElementSizeExceeded("bytes field", l, max)
|
||||
return nil
|
||||
}
|
||||
|
||||
7
Godeps/_workspace/src/github.com/juju/ratelimit/LICENSE
generated
vendored
@@ -1,5 +1,8 @@
|
||||
This package contains an efficient token-bucket-based rate limiter.
|
||||
Copyright (C) 2015 Canonical Ltd.
|
||||
All files in this repository are licensed as follows. If you contribute
|
||||
to this repository, it is assumed that you license your contribution
|
||||
under the same license unless you state otherwise.
|
||||
|
||||
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
|
||||
|
||||
This software is licensed under the LGPLv3, included below.
|
||||
|
||||
|
||||
10
Godeps/_workspace/src/github.com/juju/ratelimit/ratelimit.go
generated
vendored
@@ -7,6 +7,7 @@
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -55,7 +56,7 @@ func NewBucketWithRate(rate float64, capacity int64) *Bucket {
|
||||
continue
|
||||
}
|
||||
tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
|
||||
if diff := abs(tb.Rate() - rate); diff/rate <= rateMargin {
|
||||
if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin {
|
||||
return tb
|
||||
}
|
||||
}
|
||||
@@ -217,10 +218,3 @@ func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
|
||||
tb.availTick = currentTick
|
||||
return
|
||||
}
|
||||
|
||||
func abs(f float64) float64 {
|
||||
if f < 0 {
|
||||
return -f
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
4
Godeps/_workspace/src/github.com/kardianos/osext/README.md
generated
vendored
@@ -4,7 +4,9 @@
|
||||
|
||||
There is sometimes utility in finding the current executable file
|
||||
that is running. This can be used for upgrading the current executable
|
||||
or finding resources located relative to the executable file.
|
||||
or finding resources located relative to the executable file. Both
|
||||
working directory and the os.Args[0] value are arbitrary and cannot
|
||||
be relied on; os.Args[0] can be "faked".
|
||||
|
||||
Multi-platform and supports:
|
||||
* Linux
|
||||
|
||||
6
Godeps/_workspace/src/github.com/kardianos/osext/osext.go
generated
vendored
@@ -16,12 +16,12 @@ func Executable() (string, error) {
|
||||
}
|
||||
|
||||
// Returns same path as Executable, returns just the folder
|
||||
// path. Excludes the executable name.
|
||||
// path. Excludes the executable name and any trailing slash.
|
||||
func ExecutableFolder() (string, error) {
|
||||
p, err := Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
folder, _ := filepath.Split(p)
|
||||
return folder, nil
|
||||
|
||||
return filepath.Dir(p), nil
|
||||
}
|
||||
|
||||
6
Godeps/_workspace/src/github.com/kardianos/osext/osext_procfs.go
generated
vendored
@@ -17,12 +17,14 @@ import (
|
||||
func executable() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
const deletedSuffix = " (deleted)"
|
||||
const deletedTag = " (deleted)"
|
||||
execpath, err := os.Readlink("/proc/self/exe")
|
||||
if err != nil {
|
||||
return execpath, err
|
||||
}
|
||||
return strings.TrimSuffix(execpath, deletedSuffix), nil
|
||||
execpath = strings.TrimSuffix(execpath, deletedTag)
|
||||
execpath = strings.TrimPrefix(execpath, deletedTag)
|
||||
return execpath, nil
|
||||
case "netbsd":
|
||||
return os.Readlink("/proc/curproc/exe")
|
||||
case "openbsd", "dragonfly":
|
||||
|
||||
23
Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go
generated
vendored
@@ -24,6 +24,29 @@ const (
|
||||
executableEnvValueDelete = "delete"
|
||||
)
|
||||
|
||||
func TestPrintExecutable(t *testing.T) {
|
||||
ef, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
t.Log("Executable:", ef)
|
||||
}
|
||||
func TestPrintExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
t.Log("Executable Folder:", ef)
|
||||
}
|
||||
func TestExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
if ef[len(ef)-1] == filepath.Separator {
|
||||
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
||||
}
|
||||
}
|
||||
func TestExecutableMatch(t *testing.T) {
|
||||
ep, err := Executable()
|
||||
if err != nil {
|
||||
|
||||
27
Godeps/_workspace/src/github.com/syncthing/protocol/message_xdr.go
generated
vendored
@@ -110,12 +110,18 @@ func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
|
||||
func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.Folder = xr.ReadString()
|
||||
_FilesSize := int(xr.ReadUint32())
|
||||
if _FilesSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Files", _FilesSize, 0)
|
||||
}
|
||||
o.Files = make([]FileInfo, _FilesSize)
|
||||
for i := range o.Files {
|
||||
(&o.Files[i]).DecodeXDRFrom(xr)
|
||||
}
|
||||
o.Flags = xr.ReadUint32()
|
||||
_OptionsSize := int(xr.ReadUint32())
|
||||
if _OptionsSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
if _OptionsSize > 64 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
@@ -236,6 +242,9 @@ func (o *FileInfo) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
(&o.Version).DecodeXDRFrom(xr)
|
||||
o.LocalVersion = int64(xr.ReadUint64())
|
||||
_BlocksSize := int(xr.ReadUint32())
|
||||
if _BlocksSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Blocks", _BlocksSize, 0)
|
||||
}
|
||||
o.Blocks = make([]BlockInfo, _BlocksSize)
|
||||
for i := range o.Blocks {
|
||||
(&o.Blocks[i]).DecodeXDRFrom(xr)
|
||||
@@ -442,6 +451,9 @@ func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.Hash = xr.ReadBytesMax(64)
|
||||
o.Flags = xr.ReadUint32()
|
||||
_OptionsSize := int(xr.ReadUint32())
|
||||
if _OptionsSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
if _OptionsSize > 64 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
@@ -633,11 +645,17 @@ func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.ClientName = xr.ReadStringMax(64)
|
||||
o.ClientVersion = xr.ReadStringMax(64)
|
||||
_FoldersSize := int(xr.ReadUint32())
|
||||
if _FoldersSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 0)
|
||||
}
|
||||
o.Folders = make([]Folder, _FoldersSize)
|
||||
for i := range o.Folders {
|
||||
(&o.Folders[i]).DecodeXDRFrom(xr)
|
||||
}
|
||||
_OptionsSize := int(xr.ReadUint32())
|
||||
if _OptionsSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
if _OptionsSize > 64 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
@@ -750,12 +768,18 @@ func (o *Folder) UnmarshalXDR(bs []byte) error {
|
||||
func (o *Folder) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.ID = xr.ReadStringMax(64)
|
||||
_DevicesSize := int(xr.ReadUint32())
|
||||
if _DevicesSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Devices", _DevicesSize, 0)
|
||||
}
|
||||
o.Devices = make([]Device, _DevicesSize)
|
||||
for i := range o.Devices {
|
||||
(&o.Devices[i]).DecodeXDRFrom(xr)
|
||||
}
|
||||
o.Flags = xr.ReadUint32()
|
||||
_OptionsSize := int(xr.ReadUint32())
|
||||
if _OptionsSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
if _OptionsSize > 64 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
@@ -862,6 +886,9 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||
o.MaxLocalVersion = int64(xr.ReadUint64())
|
||||
o.Flags = xr.ReadUint32()
|
||||
_OptionsSize := int(xr.ReadUint32())
|
||||
if _OptionsSize < 0 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
if _OptionsSize > 64 {
|
||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||
}
|
||||
|
||||
8
Godeps/_workspace/src/github.com/syncthing/protocol/nativemodel_windows.go
generated
vendored
@@ -25,12 +25,12 @@ type nativeModel struct {
|
||||
}
|
||||
|
||||
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||
fixupFiles(files)
|
||||
fixupFiles(folder, files)
|
||||
m.next.Index(deviceID, folder, files, flags, options)
|
||||
}
|
||||
|
||||
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||
fixupFiles(files)
|
||||
fixupFiles(folder, files)
|
||||
m.next.IndexUpdate(deviceID, folder, files, flags, options)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (m nativeModel) Close(deviceID DeviceID, err error) {
|
||||
m.next.Close(deviceID, err)
|
||||
}
|
||||
|
||||
func fixupFiles(files []FileInfo) {
|
||||
func fixupFiles(folder string, files []FileInfo) {
|
||||
for i, f := range files {
|
||||
if strings.ContainsAny(f.Name, disallowedCharacters) {
|
||||
if f.IsDeleted() {
|
||||
@@ -56,7 +56,7 @@ func fixupFiles(files []FileInfo) {
|
||||
continue
|
||||
}
|
||||
files[i].Flags |= FlagInvalid
|
||||
l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
|
||||
l.Warnf("File name %q (folder %q) contains invalid characters; marked as invalid.", f.Name, folder)
|
||||
}
|
||||
files[i].Name = filepath.FromSlash(files[i].Name)
|
||||
}
|
||||
|
||||
68
Godeps/_workspace/src/github.com/syncthing/protocol/protocol.go
generated
vendored
@@ -31,8 +31,7 @@ const (
|
||||
|
||||
const (
|
||||
stateInitial = iota
|
||||
stateCCRcvd
|
||||
stateIdxRcvd
|
||||
stateReady
|
||||
)
|
||||
|
||||
// FileInfo flags
|
||||
@@ -90,6 +89,7 @@ type Model interface {
|
||||
}
|
||||
|
||||
type Connection interface {
|
||||
Start()
|
||||
ID() DeviceID
|
||||
Name() string
|
||||
Index(folder string, files []FileInfo, flags uint32, options []Option) error
|
||||
@@ -103,7 +103,6 @@ type rawConnection struct {
|
||||
id DeviceID
|
||||
name string
|
||||
receiver Model
|
||||
state int
|
||||
|
||||
cr *countingReader
|
||||
cw *countingWriter
|
||||
@@ -142,9 +141,9 @@ type isEofer interface {
|
||||
IsEOF() bool
|
||||
}
|
||||
|
||||
const (
|
||||
pingTimeout = 30 * time.Second
|
||||
pingIdleTime = 60 * time.Second
|
||||
var (
|
||||
PingTimeout = 30 * time.Second
|
||||
PingIdleTime = 60 * time.Second
|
||||
)
|
||||
|
||||
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection {
|
||||
@@ -155,7 +154,6 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
|
||||
id: deviceID,
|
||||
name: name,
|
||||
receiver: nativeModel{receiver},
|
||||
state: stateInitial,
|
||||
cr: cr,
|
||||
cw: cw,
|
||||
outbox: make(chan hdrMsg),
|
||||
@@ -164,12 +162,16 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
|
||||
compression: compress,
|
||||
}
|
||||
|
||||
return wireFormatConnection{&c}
|
||||
}
|
||||
|
||||
// Start creates the goroutines for sending and receiving of messages. It must
|
||||
// be called exactly once after creating a connection.
|
||||
func (c *rawConnection) Start() {
|
||||
go c.readerLoop()
|
||||
go c.writerLoop()
|
||||
go c.pingerLoop()
|
||||
go c.idGenerator()
|
||||
|
||||
return wireFormatConnection{&c}
|
||||
}
|
||||
|
||||
func (c *rawConnection) ID() DeviceID {
|
||||
@@ -285,6 +287,7 @@ func (c *rawConnection) readerLoop() (err error) {
|
||||
c.close(err)
|
||||
}()
|
||||
|
||||
state := stateInitial
|
||||
for {
|
||||
select {
|
||||
case <-c.closed:
|
||||
@@ -298,47 +301,54 @@ func (c *rawConnection) readerLoop() (err error) {
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case ClusterConfigMessage:
|
||||
if state != stateInitial {
|
||||
return fmt.Errorf("protocol error: cluster config message in state %d", state)
|
||||
}
|
||||
go c.receiver.ClusterConfig(c.id, msg)
|
||||
state = stateReady
|
||||
|
||||
case IndexMessage:
|
||||
switch hdr.msgType {
|
||||
case messageTypeIndex:
|
||||
if c.state < stateCCRcvd {
|
||||
return fmt.Errorf("protocol error: index message in state %d", c.state)
|
||||
if state != stateReady {
|
||||
return fmt.Errorf("protocol error: index message in state %d", state)
|
||||
}
|
||||
c.handleIndex(msg)
|
||||
c.state = stateIdxRcvd
|
||||
state = stateReady
|
||||
|
||||
case messageTypeIndexUpdate:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: index update message in state %d", c.state)
|
||||
if state != stateReady {
|
||||
return fmt.Errorf("protocol error: index update message in state %d", state)
|
||||
}
|
||||
c.handleIndexUpdate(msg)
|
||||
state = stateReady
|
||||
}
|
||||
|
||||
case RequestMessage:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: request message in state %d", c.state)
|
||||
if state != stateReady {
|
||||
return fmt.Errorf("protocol error: request message in state %d", state)
|
||||
}
|
||||
// Requests are handled asynchronously
|
||||
go c.handleRequest(hdr.msgID, msg)
|
||||
|
||||
case ResponseMessage:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: response message in state %d", c.state)
|
||||
if state != stateReady {
|
||||
return fmt.Errorf("protocol error: response message in state %d", state)
|
||||
}
|
||||
c.handleResponse(hdr.msgID, msg)
|
||||
|
||||
case pingMessage:
|
||||
if state != stateReady {
|
||||
return fmt.Errorf("protocol error: ping message in state %d", state)
|
||||
}
|
||||
c.send(hdr.msgID, messageTypePong, pongMessage{})
|
||||
|
||||
case pongMessage:
|
||||
c.handlePong(hdr.msgID)
|
||||
|
||||
case ClusterConfigMessage:
|
||||
if c.state != stateInitial {
|
||||
return fmt.Errorf("protocol error: cluster config message in state %d", c.state)
|
||||
if state != stateReady {
|
||||
return fmt.Errorf("protocol error: pong message in state %d", state)
|
||||
}
|
||||
go c.receiver.ClusterConfig(c.id, msg)
|
||||
c.state = stateCCRcvd
|
||||
c.handlePong(hdr.msgID)
|
||||
|
||||
case CloseMessage:
|
||||
return errors.New(msg.Reason)
|
||||
@@ -679,17 +689,17 @@ func (c *rawConnection) idGenerator() {
|
||||
|
||||
func (c *rawConnection) pingerLoop() {
|
||||
var rc = make(chan bool, 1)
|
||||
ticker := time.Tick(pingIdleTime / 2)
|
||||
ticker := time.Tick(PingIdleTime / 2)
|
||||
for {
|
||||
select {
|
||||
case <-ticker:
|
||||
if d := time.Since(c.cr.Last()); d < pingIdleTime {
|
||||
if d := time.Since(c.cr.Last()); d < PingIdleTime {
|
||||
if debug {
|
||||
l.Debugln(c.id, "ping skipped after rd", d)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if d := time.Since(c.cw.Last()); d < pingIdleTime {
|
||||
if d := time.Since(c.cw.Last()); d < PingIdleTime {
|
||||
if debug {
|
||||
l.Debugln(c.id, "ping skipped after wr", d)
|
||||
}
|
||||
@@ -709,7 +719,7 @@ func (c *rawConnection) pingerLoop() {
|
||||
if !ok {
|
||||
c.close(fmt.Errorf("ping failure"))
|
||||
}
|
||||
case <-time.After(pingTimeout):
|
||||
case <-time.After(PingTimeout):
|
||||
c.close(fmt.Errorf("ping timeout"))
|
||||
case <-c.closed:
|
||||
return
|
||||
|
||||
40
Godeps/_workspace/src/github.com/syncthing/protocol/protocol_test.go
generated
vendored
@@ -67,8 +67,12 @@ func TestPing(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, nil, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
c1 := NewConnection(c1ID, br, aw, nil, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
c0 := NewConnection(c0ID, ar, bw, newTestModel(), "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
c0.Start()
|
||||
c1 := NewConnection(c1ID, br, aw, newTestModel(), "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
c1.Start()
|
||||
c0.ClusterConfig(ClusterConfigMessage{})
|
||||
c1.ClusterConfig(ClusterConfigMessage{})
|
||||
|
||||
if ok := c0.ping(); !ok {
|
||||
t.Error("c0 ping failed")
|
||||
@@ -81,8 +85,8 @@ func TestPing(t *testing.T) {
|
||||
func TestPingErr(t *testing.T) {
|
||||
e := errors.New("something broke")
|
||||
|
||||
for i := 0; i < 16; i++ {
|
||||
for j := 0; j < 16; j++ {
|
||||
for i := 0; i < 32; i++ {
|
||||
for j := 0; j < 32; j++ {
|
||||
m0 := newTestModel()
|
||||
m1 := newTestModel()
|
||||
|
||||
@@ -92,12 +96,18 @@ func TestPingErr(t *testing.T) {
|
||||
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||
|
||||
c0 := NewConnection(c0ID, ar, ebw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, eaw, m1, "name", CompressAlways)
|
||||
c0.Start()
|
||||
c1 := NewConnection(c1ID, br, eaw, m1, "name", CompressAlways)
|
||||
c1.Start()
|
||||
c0.ClusterConfig(ClusterConfigMessage{})
|
||||
c1.ClusterConfig(ClusterConfigMessage{})
|
||||
|
||||
res := c0.ping()
|
||||
if (i < 8 || j < 8) && res {
|
||||
// This should have resulted in failure, as there is no way an empty ClusterConfig plus a Ping message fits in eight bytes.
|
||||
t.Errorf("Unexpected ping success; i=%d, j=%d", i, j)
|
||||
} else if (i >= 12 && j >= 12) && !res {
|
||||
} else if (i >= 28 && j >= 28) && !res {
|
||||
// This should have worked though, as 28 bytes is plenty for both.
|
||||
t.Errorf("Unexpected ping fail; i=%d, j=%d", i, j)
|
||||
}
|
||||
}
|
||||
@@ -168,7 +178,11 @@ func TestVersionErr(t *testing.T) {
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||
c0.Start()
|
||||
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||
c1.Start()
|
||||
c0.ClusterConfig(ClusterConfigMessage{})
|
||||
c1.ClusterConfig(ClusterConfigMessage{})
|
||||
|
||||
w := xdr.NewWriter(c0.cw)
|
||||
w.WriteUint32(encodeHeader(header{
|
||||
@@ -191,7 +205,11 @@ func TestTypeErr(t *testing.T) {
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||
c0.Start()
|
||||
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||
c1.Start()
|
||||
c0.ClusterConfig(ClusterConfigMessage{})
|
||||
c1.ClusterConfig(ClusterConfigMessage{})
|
||||
|
||||
w := xdr.NewWriter(c0.cw)
|
||||
w.WriteUint32(encodeHeader(header{
|
||||
@@ -214,7 +232,11 @@ func TestClose(t *testing.T) {
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||
c0.Start()
|
||||
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||
c1.Start()
|
||||
c0.ClusterConfig(ClusterConfigMessage{})
|
||||
c1.ClusterConfig(ClusterConfigMessage{})
|
||||
|
||||
c0.close(nil)
|
||||
|
||||
|
||||
10
Godeps/_workspace/src/github.com/syncthing/protocol/vector.go
generated
vendored
@@ -103,3 +103,13 @@ func (a Vector) Concurrent(b Vector) bool {
|
||||
comp := a.Compare(b)
|
||||
return comp == ConcurrentGreater || comp == ConcurrentLesser
|
||||
}
|
||||
|
||||
// Counter returns the current value of the given counter ID.
|
||||
func (v Vector) Counter(id uint64) uint64 {
|
||||
for _, c := range v {
|
||||
if c.ID == id {
|
||||
return c.Value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
14
Godeps/_workspace/src/github.com/syncthing/protocol/vector_test.go
generated
vendored
@@ -118,5 +118,17 @@ func TestMerge(t *testing.T) {
|
||||
t.Errorf("%d: %+v.Merge(%+v) == %+v (expected %+v)", i, tc.a, tc.b, m, tc.m)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCounterValue(t *testing.T) {
|
||||
v0 := Vector{Counter{42, 1}, Counter{64, 5}}
|
||||
if v0.Counter(42) != 1 {
|
||||
t.Error("Counter error, %d != %d", v0.Counter(42), 1)
|
||||
}
|
||||
if v0.Counter(64) != 5 {
|
||||
t.Error("Counter error, %d != %d", v0.Counter(64), 5)
|
||||
}
|
||||
if v0.Counter(72) != 0 {
|
||||
t.Error("Counter error, %d != %d", v0.Counter(72), 0)
|
||||
}
|
||||
}
|
||||
|
||||
4
Godeps/_workspace/src/github.com/syncthing/protocol/wireformat.go
generated
vendored
@@ -12,6 +12,10 @@ type wireFormatConnection struct {
|
||||
next Connection
|
||||
}
|
||||
|
||||
func (c wireFormatConnection) Start() {
|
||||
c.next.Start()
|
||||
}
|
||||
|
||||
func (c wireFormatConnection) ID() DeviceID {
|
||||
return c.next.ID()
|
||||
}
|
||||
|
||||
403
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go
generated
vendored
@@ -63,13 +63,14 @@ type DB struct {
|
||||
journalAckC chan error
|
||||
|
||||
// Compaction.
|
||||
tcompCmdC chan cCmd
|
||||
tcompPauseC chan chan<- struct{}
|
||||
mcompCmdC chan cCmd
|
||||
compErrC chan error
|
||||
compPerErrC chan error
|
||||
compErrSetC chan error
|
||||
compStats []cStats
|
||||
tcompCmdC chan cCmd
|
||||
tcompPauseC chan chan<- struct{}
|
||||
mcompCmdC chan cCmd
|
||||
compErrC chan error
|
||||
compPerErrC chan error
|
||||
compErrSetC chan error
|
||||
compWriteLocking bool
|
||||
compStats []cStats
|
||||
|
||||
// Close.
|
||||
closeW sync.WaitGroup
|
||||
@@ -108,28 +109,44 @@ func openDB(s *session) (*DB, error) {
|
||||
closeC: make(chan struct{}),
|
||||
}
|
||||
|
||||
if err := db.recoverJournal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read-only mode.
|
||||
readOnly := s.o.GetReadOnly()
|
||||
|
||||
// Remove any obsolete files.
|
||||
if err := db.checkAndCleanFiles(); err != nil {
|
||||
// Close journal.
|
||||
if db.journal != nil {
|
||||
db.journal.Close()
|
||||
db.journalWriter.Close()
|
||||
if readOnly {
|
||||
// Recover journals (read-only mode).
|
||||
if err := db.recoverJournalRO(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
} else {
|
||||
// Recover journals.
|
||||
if err := db.recoverJournal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove any obsolete files.
|
||||
if err := db.checkAndCleanFiles(); err != nil {
|
||||
// Close journal.
|
||||
if db.journal != nil {
|
||||
db.journal.Close()
|
||||
db.journalWriter.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Doesn't need to be included in the wait group.
|
||||
go db.compactionError()
|
||||
go db.mpoolDrain()
|
||||
|
||||
db.closeW.Add(3)
|
||||
go db.tCompaction()
|
||||
go db.mCompaction()
|
||||
go db.jWriter()
|
||||
if readOnly {
|
||||
db.SetReadOnly()
|
||||
} else {
|
||||
db.closeW.Add(3)
|
||||
go db.tCompaction()
|
||||
go db.mCompaction()
|
||||
go db.jWriter()
|
||||
}
|
||||
|
||||
s.logf("db@open done T·%v", time.Since(start))
|
||||
|
||||
@@ -275,7 +292,7 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
// We will drop corrupted table.
|
||||
strict = o.GetStrict(opt.StrictRecovery)
|
||||
|
||||
rec = &sessionRecord{numLevel: o.GetNumLevel()}
|
||||
rec = &sessionRecord{}
|
||||
bpool = util.NewBufferPool(o.GetBlockSize() + 5)
|
||||
)
|
||||
buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) {
|
||||
@@ -450,132 +467,136 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
}
|
||||
|
||||
func (db *DB) recoverJournal() error {
|
||||
// Get all tables and sort it by file number.
|
||||
journalFiles_, err := db.s.getFiles(storage.TypeJournal)
|
||||
// Get all journals and sort it by file number.
|
||||
allJournalFiles, err := db.s.getFiles(storage.TypeJournal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
journalFiles := files(journalFiles_)
|
||||
journalFiles.sort()
|
||||
files(allJournalFiles).sort()
|
||||
|
||||
// Discard older journal.
|
||||
prev := -1
|
||||
for i, file := range journalFiles {
|
||||
if file.Num() >= db.s.stJournalNum {
|
||||
if prev >= 0 {
|
||||
i--
|
||||
journalFiles[i] = journalFiles[prev]
|
||||
}
|
||||
journalFiles = journalFiles[i:]
|
||||
break
|
||||
} else if file.Num() == db.s.stPrevJournalNum {
|
||||
prev = i
|
||||
// Journals that will be recovered.
|
||||
var recJournalFiles []storage.File
|
||||
for _, jf := range allJournalFiles {
|
||||
if jf.Num() >= db.s.stJournalNum || jf.Num() == db.s.stPrevJournalNum {
|
||||
recJournalFiles = append(recJournalFiles, jf)
|
||||
}
|
||||
}
|
||||
|
||||
var jr *journal.Reader
|
||||
var of storage.File
|
||||
var mem *memdb.DB
|
||||
batch := new(Batch)
|
||||
cm := newCMem(db.s)
|
||||
buf := new(util.Buffer)
|
||||
// Options.
|
||||
strict := db.s.o.GetStrict(opt.StrictJournal)
|
||||
checksum := db.s.o.GetStrict(opt.StrictJournalChecksum)
|
||||
writeBuffer := db.s.o.GetWriteBuffer()
|
||||
recoverJournal := func(file storage.File) error {
|
||||
db.logf("journal@recovery recovering @%d", file.Num())
|
||||
reader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
var (
|
||||
of storage.File // Obsolete file.
|
||||
rec = &sessionRecord{}
|
||||
)
|
||||
|
||||
// Create/reset journal reader instance.
|
||||
if jr == nil {
|
||||
jr = journal.NewReader(reader, dropper{db.s, file}, strict, checksum)
|
||||
} else {
|
||||
jr.Reset(reader, dropper{db.s, file}, strict, checksum)
|
||||
}
|
||||
|
||||
// Flush memdb and remove obsolete journal file.
|
||||
if of != nil {
|
||||
if mem.Len() > 0 {
|
||||
if err := cm.flush(mem, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := cm.commit(file.Num(), db.seq); err != nil {
|
||||
return err
|
||||
}
|
||||
cm.reset()
|
||||
of.Remove()
|
||||
of = nil
|
||||
}
|
||||
|
||||
// Replay journal to memdb.
|
||||
mem.Reset()
|
||||
for {
|
||||
r, err := jr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return errors.SetFile(err, file)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if _, err := buf.ReadFrom(r); err != nil {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
// This is error returned due to corruption, with strict == false.
|
||||
continue
|
||||
} else {
|
||||
return errors.SetFile(err, file)
|
||||
}
|
||||
}
|
||||
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mem); err != nil {
|
||||
if strict || !errors.IsCorrupted(err) {
|
||||
return errors.SetFile(err, file)
|
||||
} else {
|
||||
db.s.logf("journal error: %v (skipped)", err)
|
||||
// We won't apply sequence number as it might be corrupted.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Save sequence number.
|
||||
db.seq = batch.seq + uint64(batch.Len())
|
||||
|
||||
// Flush it if large enough.
|
||||
if mem.Size() >= writeBuffer {
|
||||
if err := cm.flush(mem, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
mem.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
of = file
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recover all journals.
|
||||
if len(journalFiles) > 0 {
|
||||
db.logf("journal@recovery F·%d", len(journalFiles))
|
||||
// Recover journals.
|
||||
if len(recJournalFiles) > 0 {
|
||||
db.logf("journal@recovery F·%d", len(recJournalFiles))
|
||||
|
||||
// Mark file number as used.
|
||||
db.s.markFileNum(journalFiles[len(journalFiles)-1].Num())
|
||||
db.s.markFileNum(recJournalFiles[len(recJournalFiles)-1].Num())
|
||||
|
||||
mem = memdb.New(db.s.icmp, writeBuffer)
|
||||
for _, file := range journalFiles {
|
||||
if err := recoverJournal(file); err != nil {
|
||||
var (
|
||||
// Options.
|
||||
strict = db.s.o.GetStrict(opt.StrictJournal)
|
||||
checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
|
||||
writeBuffer = db.s.o.GetWriteBuffer()
|
||||
|
||||
jr *journal.Reader
|
||||
mdb = memdb.New(db.s.icmp, writeBuffer)
|
||||
buf = &util.Buffer{}
|
||||
batch = &Batch{}
|
||||
)
|
||||
|
||||
for _, jf := range recJournalFiles {
|
||||
db.logf("journal@recovery recovering @%d", jf.Num())
|
||||
|
||||
fr, err := jf.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create or reset journal reader instance.
|
||||
if jr == nil {
|
||||
jr = journal.NewReader(fr, dropper{db.s, jf}, strict, checksum)
|
||||
} else {
|
||||
jr.Reset(fr, dropper{db.s, jf}, strict, checksum)
|
||||
}
|
||||
|
||||
// Flush memdb and remove obsolete journal file.
|
||||
if of != nil {
|
||||
if mdb.Len() > 0 {
|
||||
if _, err := db.s.flushMemdb(rec, mdb, -1); err != nil {
|
||||
fr.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rec.setJournalNum(jf.Num())
|
||||
rec.setSeqNum(db.seq)
|
||||
if err := db.s.commit(rec); err != nil {
|
||||
fr.Close()
|
||||
return err
|
||||
}
|
||||
rec.resetAddedTables()
|
||||
|
||||
of.Remove()
|
||||
of = nil
|
||||
}
|
||||
|
||||
// Replay journal to memdb.
|
||||
mdb.Reset()
|
||||
for {
|
||||
r, err := jr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
return errors.SetFile(err, jf)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if _, err := buf.ReadFrom(r); err != nil {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
// This is error returned due to corruption, with strict == false.
|
||||
continue
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
return errors.SetFile(err, jf)
|
||||
}
|
||||
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
|
||||
if !strict && errors.IsCorrupted(err) {
|
||||
db.s.logf("journal error: %v (skipped)", err)
|
||||
// We won't apply sequence number as it might be corrupted.
|
||||
continue
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
return errors.SetFile(err, jf)
|
||||
}
|
||||
|
||||
// Save sequence number.
|
||||
db.seq = batch.seq + uint64(batch.Len())
|
||||
|
||||
// Flush it if large enough.
|
||||
if mdb.Size() >= writeBuffer {
|
||||
if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
|
||||
fr.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
mdb.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
of = jf
|
||||
}
|
||||
|
||||
// Flush the last journal.
|
||||
if mem.Len() > 0 {
|
||||
if err := cm.flush(mem, 0); err != nil {
|
||||
// Flush the last memdb.
|
||||
if mdb.Len() > 0 {
|
||||
if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -587,8 +608,10 @@ func (db *DB) recoverJournal() error {
|
||||
}
|
||||
|
||||
// Commit.
|
||||
if err := cm.commit(db.journalFile.Num(), db.seq); err != nil {
|
||||
// Close journal.
|
||||
rec.setJournalNum(db.journalFile.Num())
|
||||
rec.setSeqNum(db.seq)
|
||||
if err := db.s.commit(rec); err != nil {
|
||||
// Close journal on error.
|
||||
if db.journal != nil {
|
||||
db.journal.Close()
|
||||
db.journalWriter.Close()
|
||||
@@ -604,6 +627,103 @@ func (db *DB) recoverJournal() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) recoverJournalRO() error {
|
||||
// Get all journals and sort it by file number.
|
||||
allJournalFiles, err := db.s.getFiles(storage.TypeJournal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files(allJournalFiles).sort()
|
||||
|
||||
// Journals that will be recovered.
|
||||
var recJournalFiles []storage.File
|
||||
for _, jf := range allJournalFiles {
|
||||
if jf.Num() >= db.s.stJournalNum || jf.Num() == db.s.stPrevJournalNum {
|
||||
recJournalFiles = append(recJournalFiles, jf)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// Options.
|
||||
strict = db.s.o.GetStrict(opt.StrictJournal)
|
||||
checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
|
||||
writeBuffer = db.s.o.GetWriteBuffer()
|
||||
|
||||
mdb = memdb.New(db.s.icmp, writeBuffer)
|
||||
)
|
||||
|
||||
// Recover journals.
|
||||
if len(recJournalFiles) > 0 {
|
||||
db.logf("journal@recovery RO·Mode F·%d", len(recJournalFiles))
|
||||
|
||||
var (
|
||||
jr *journal.Reader
|
||||
buf = &util.Buffer{}
|
||||
batch = &Batch{}
|
||||
)
|
||||
|
||||
for _, jf := range recJournalFiles {
|
||||
db.logf("journal@recovery recovering @%d", jf.Num())
|
||||
|
||||
fr, err := jf.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create or reset journal reader instance.
|
||||
if jr == nil {
|
||||
jr = journal.NewReader(fr, dropper{db.s, jf}, strict, checksum)
|
||||
} else {
|
||||
jr.Reset(fr, dropper{db.s, jf}, strict, checksum)
|
||||
}
|
||||
|
||||
// Replay journal to memdb.
|
||||
for {
|
||||
r, err := jr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
return errors.SetFile(err, jf)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if _, err := buf.ReadFrom(r); err != nil {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
// This is error returned due to corruption, with strict == false.
|
||||
continue
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
return errors.SetFile(err, jf)
|
||||
}
|
||||
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
|
||||
if !strict && errors.IsCorrupted(err) {
|
||||
db.s.logf("journal error: %v (skipped)", err)
|
||||
// We won't apply sequence number as it might be corrupted.
|
||||
continue
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
return errors.SetFile(err, jf)
|
||||
}
|
||||
|
||||
// Save sequence number.
|
||||
db.seq = batch.seq + uint64(batch.Len())
|
||||
}
|
||||
|
||||
fr.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Set memDB.
|
||||
db.mem = &memDB{db: db, DB: mdb, ref: 1}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
ikey := newIkey(key, seq, ktSeek)
|
||||
|
||||
@@ -614,7 +734,7 @@ func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, er
|
||||
}
|
||||
defer m.decref()
|
||||
|
||||
mk, mv, me := m.mdb.Find(ikey)
|
||||
mk, mv, me := m.Find(ikey)
|
||||
if me == nil {
|
||||
ukey, _, kt, kerr := parseIkey(mk)
|
||||
if kerr != nil {
|
||||
@@ -652,7 +772,7 @@ func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err er
|
||||
}
|
||||
defer m.decref()
|
||||
|
||||
mk, _, me := m.mdb.Find(ikey)
|
||||
mk, _, me := m.Find(ikey)
|
||||
if me == nil {
|
||||
ukey, _, kt, kerr := parseIkey(mk)
|
||||
if kerr != nil {
|
||||
@@ -784,7 +904,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
||||
|
||||
const prefix = "leveldb."
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
return "", errors.New("leveldb: GetProperty: unknown property: " + name)
|
||||
return "", ErrNotFound
|
||||
}
|
||||
p := name[len(prefix):]
|
||||
|
||||
@@ -798,7 +918,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
||||
var rest string
|
||||
n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest)
|
||||
if n != 1 || int(level) >= db.s.o.GetNumLevel() {
|
||||
err = errors.New("leveldb: GetProperty: invalid property: " + name)
|
||||
err = ErrNotFound
|
||||
} else {
|
||||
value = fmt.Sprint(v.tLen(int(level)))
|
||||
}
|
||||
@@ -837,7 +957,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
||||
case p == "aliveiters":
|
||||
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters))
|
||||
default:
|
||||
err = errors.New("leveldb: GetProperty: unknown property: " + name)
|
||||
err = ErrNotFound
|
||||
}
|
||||
|
||||
return
|
||||
@@ -900,6 +1020,9 @@ func (db *DB) Close() error {
|
||||
var err error
|
||||
select {
|
||||
case err = <-db.compErrC:
|
||||
if err == ErrReadOnly {
|
||||
err = nil
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
114
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go
generated
vendored
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/memdb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
@@ -62,58 +61,8 @@ func (p *cStatsStaging) stopTimer() {
|
||||
}
|
||||
}
|
||||
|
||||
type cMem struct {
|
||||
s *session
|
||||
level int
|
||||
rec *sessionRecord
|
||||
}
|
||||
|
||||
func newCMem(s *session) *cMem {
|
||||
return &cMem{s: s, rec: &sessionRecord{numLevel: s.o.GetNumLevel()}}
|
||||
}
|
||||
|
||||
func (c *cMem) flush(mem *memdb.DB, level int) error {
|
||||
s := c.s
|
||||
|
||||
// Write memdb to table.
|
||||
iter := mem.NewIterator(nil)
|
||||
defer iter.Release()
|
||||
t, n, err := s.tops.createFrom(iter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pick level.
|
||||
if level < 0 {
|
||||
v := s.version()
|
||||
level = v.pickLevel(t.imin.ukey(), t.imax.ukey())
|
||||
v.release()
|
||||
}
|
||||
c.rec.addTableFile(level, t)
|
||||
|
||||
s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
|
||||
|
||||
c.level = level
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cMem) reset() {
|
||||
c.rec = &sessionRecord{numLevel: c.s.o.GetNumLevel()}
|
||||
}
|
||||
|
||||
func (c *cMem) commit(journal, seq uint64) error {
|
||||
c.rec.setJournalNum(journal)
|
||||
c.rec.setSeqNum(seq)
|
||||
|
||||
// Commit changes.
|
||||
return c.s.commit(c.rec)
|
||||
}
|
||||
|
||||
func (db *DB) compactionError() {
|
||||
var (
|
||||
err error
|
||||
wlocked bool
|
||||
)
|
||||
var err error
|
||||
noerr:
|
||||
// No error.
|
||||
for {
|
||||
@@ -121,7 +70,7 @@ noerr:
|
||||
case err = <-db.compErrSetC:
|
||||
switch {
|
||||
case err == nil:
|
||||
case errors.IsCorrupted(err):
|
||||
case err == ErrReadOnly, errors.IsCorrupted(err):
|
||||
goto hasperr
|
||||
default:
|
||||
goto haserr
|
||||
@@ -139,7 +88,7 @@ haserr:
|
||||
switch {
|
||||
case err == nil:
|
||||
goto noerr
|
||||
case errors.IsCorrupted(err):
|
||||
case err == ErrReadOnly, errors.IsCorrupted(err):
|
||||
goto hasperr
|
||||
default:
|
||||
}
|
||||
@@ -155,9 +104,9 @@ hasperr:
|
||||
case db.compPerErrC <- err:
|
||||
case db.writeLockC <- struct{}{}:
|
||||
// Hold write lock, so that write won't pass-through.
|
||||
wlocked = true
|
||||
db.compWriteLocking = true
|
||||
case _, _ = <-db.closeC:
|
||||
if wlocked {
|
||||
if db.compWriteLocking {
|
||||
// We should release the lock or Close will hang.
|
||||
<-db.writeLockC
|
||||
}
|
||||
@@ -287,21 +236,18 @@ func (db *DB) compactionExitTransact() {
|
||||
}
|
||||
|
||||
func (db *DB) memCompaction() {
|
||||
mem := db.getFrozenMem()
|
||||
if mem == nil {
|
||||
mdb := db.getFrozenMem()
|
||||
if mdb == nil {
|
||||
return
|
||||
}
|
||||
defer mem.decref()
|
||||
defer mdb.decref()
|
||||
|
||||
c := newCMem(db.s)
|
||||
stats := new(cStatsStaging)
|
||||
|
||||
db.logf("mem@flush N·%d S·%s", mem.mdb.Len(), shortenb(mem.mdb.Size()))
|
||||
db.logf("memdb@flush N·%d S·%s", mdb.Len(), shortenb(mdb.Size()))
|
||||
|
||||
// Don't compact empty memdb.
|
||||
if mem.mdb.Len() == 0 {
|
||||
db.logf("mem@flush skipping")
|
||||
// drop frozen mem
|
||||
if mdb.Len() == 0 {
|
||||
db.logf("memdb@flush skipping")
|
||||
// drop frozen memdb
|
||||
db.dropFrozenMem()
|
||||
return
|
||||
}
|
||||
@@ -317,13 +263,20 @@ func (db *DB) memCompaction() {
|
||||
return
|
||||
}
|
||||
|
||||
db.compactionTransactFunc("mem@flush", func(cnt *compactionTransactCounter) (err error) {
|
||||
var (
|
||||
rec = &sessionRecord{}
|
||||
stats = &cStatsStaging{}
|
||||
flushLevel int
|
||||
)
|
||||
|
||||
db.compactionTransactFunc("memdb@flush", func(cnt *compactionTransactCounter) (err error) {
|
||||
stats.startTimer()
|
||||
defer stats.stopTimer()
|
||||
return c.flush(mem.mdb, -1)
|
||||
flushLevel, err = db.s.flushMemdb(rec, mdb.DB, -1)
|
||||
stats.stopTimer()
|
||||
return
|
||||
}, func() error {
|
||||
for _, r := range c.rec.addedTables {
|
||||
db.logf("mem@flush revert @%d", r.num)
|
||||
for _, r := range rec.addedTables {
|
||||
db.logf("memdb@flush revert @%d", r.num)
|
||||
f := db.s.getTableFile(r.num)
|
||||
if err := f.Remove(); err != nil {
|
||||
return err
|
||||
@@ -332,20 +285,23 @@ func (db *DB) memCompaction() {
|
||||
return nil
|
||||
})
|
||||
|
||||
db.compactionTransactFunc("mem@commit", func(cnt *compactionTransactCounter) (err error) {
|
||||
db.compactionTransactFunc("memdb@commit", func(cnt *compactionTransactCounter) (err error) {
|
||||
stats.startTimer()
|
||||
defer stats.stopTimer()
|
||||
return c.commit(db.journalFile.Num(), db.frozenSeq)
|
||||
rec.setJournalNum(db.journalFile.Num())
|
||||
rec.setSeqNum(db.frozenSeq)
|
||||
err = db.s.commit(rec)
|
||||
stats.stopTimer()
|
||||
return
|
||||
}, nil)
|
||||
|
||||
db.logf("mem@flush committed F·%d T·%v", len(c.rec.addedTables), stats.duration)
|
||||
db.logf("memdb@flush committed F·%d T·%v", len(rec.addedTables), stats.duration)
|
||||
|
||||
for _, r := range c.rec.addedTables {
|
||||
for _, r := range rec.addedTables {
|
||||
stats.write += r.size
|
||||
}
|
||||
db.compStats[c.level].add(stats)
|
||||
db.compStats[flushLevel].add(stats)
|
||||
|
||||
// Drop frozen mem.
|
||||
// Drop frozen memdb.
|
||||
db.dropFrozenMem()
|
||||
|
||||
// Resume table compaction.
|
||||
@@ -557,7 +513,7 @@ func (b *tableCompactionBuilder) revert() error {
|
||||
func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
|
||||
defer c.release()
|
||||
|
||||
rec := &sessionRecord{numLevel: db.s.o.GetNumLevel()}
|
||||
rec := &sessionRecord{}
|
||||
rec.addCompPtr(c.level, c.imax)
|
||||
|
||||
if !noTrivial && c.trivial() {
|
||||
|
||||
32
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_iter.go
generated
vendored
@@ -8,6 +8,7 @@ package leveldb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -39,11 +40,11 @@ func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It
|
||||
ti := v.getIterators(slice, ro)
|
||||
n := len(ti) + 2
|
||||
i := make([]iterator.Iterator, 0, n)
|
||||
emi := em.mdb.NewIterator(slice)
|
||||
emi := em.NewIterator(slice)
|
||||
emi.SetReleaser(&memdbReleaser{m: em})
|
||||
i = append(i, emi)
|
||||
if fm != nil {
|
||||
fmi := fm.mdb.NewIterator(slice)
|
||||
fmi := fm.NewIterator(slice)
|
||||
fmi.SetReleaser(&memdbReleaser{m: fm})
|
||||
i = append(i, fmi)
|
||||
}
|
||||
@@ -80,6 +81,10 @@ func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *d
|
||||
return iter
|
||||
}
|
||||
|
||||
func (db *DB) iterSamplingRate() int {
|
||||
return rand.Intn(2 * db.s.o.GetIteratorSamplingRate())
|
||||
}
|
||||
|
||||
type dir int
|
||||
|
||||
const (
|
||||
@@ -98,11 +103,21 @@ type dbIter struct {
|
||||
seq uint64
|
||||
strict bool
|
||||
|
||||
dir dir
|
||||
key []byte
|
||||
value []byte
|
||||
err error
|
||||
releaser util.Releaser
|
||||
smaplingGap int
|
||||
dir dir
|
||||
key []byte
|
||||
value []byte
|
||||
err error
|
||||
releaser util.Releaser
|
||||
}
|
||||
|
||||
func (i *dbIter) sampleSeek() {
|
||||
ikey := i.iter.Key()
|
||||
i.smaplingGap -= len(ikey) + len(i.iter.Value())
|
||||
for i.smaplingGap < 0 {
|
||||
i.smaplingGap += i.db.iterSamplingRate()
|
||||
i.db.sampleSeek(ikey)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *dbIter) setErr(err error) {
|
||||
@@ -175,6 +190,7 @@ func (i *dbIter) Seek(key []byte) bool {
|
||||
func (i *dbIter) next() bool {
|
||||
for {
|
||||
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
||||
i.sampleSeek()
|
||||
if seq <= i.seq {
|
||||
switch kt {
|
||||
case ktDel:
|
||||
@@ -225,6 +241,7 @@ func (i *dbIter) prev() bool {
|
||||
if i.iter.Valid() {
|
||||
for {
|
||||
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
||||
i.sampleSeek()
|
||||
if seq <= i.seq {
|
||||
if !del && i.icmp.uCompare(ukey, i.key) < 0 {
|
||||
return true
|
||||
@@ -266,6 +283,7 @@ func (i *dbIter) Prev() bool {
|
||||
case dirForward:
|
||||
for i.iter.Prev() {
|
||||
if ukey, _, _, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
||||
i.sampleSeek()
|
||||
if i.icmp.uCompare(ukey, i.key) < 0 {
|
||||
goto cont
|
||||
}
|
||||
|
||||
23
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_state.go
generated
vendored
@@ -15,8 +15,8 @@ import (
|
||||
)
|
||||
|
||||
type memDB struct {
|
||||
db *DB
|
||||
mdb *memdb.DB
|
||||
db *DB
|
||||
*memdb.DB
|
||||
ref int32
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ func (m *memDB) incref() {
|
||||
func (m *memDB) decref() {
|
||||
if ref := atomic.AddInt32(&m.ref, -1); ref == 0 {
|
||||
// Only put back memdb with std capacity.
|
||||
if m.mdb.Capacity() == m.db.s.o.GetWriteBuffer() {
|
||||
m.mdb.Reset()
|
||||
m.db.mpoolPut(m.mdb)
|
||||
if m.Capacity() == m.db.s.o.GetWriteBuffer() {
|
||||
m.Reset()
|
||||
m.db.mpoolPut(m.DB)
|
||||
}
|
||||
m.db = nil
|
||||
m.mdb = nil
|
||||
m.DB = nil
|
||||
} else if ref < 0 {
|
||||
panic("negative memdb ref")
|
||||
}
|
||||
@@ -48,6 +48,15 @@ func (db *DB) addSeq(delta uint64) {
|
||||
atomic.AddUint64(&db.seq, delta)
|
||||
}
|
||||
|
||||
func (db *DB) sampleSeek(ikey iKey) {
|
||||
v := db.s.version()
|
||||
if v.sampleSeek(ikey) {
|
||||
// Trigger table compaction.
|
||||
db.compSendTrigger(db.tcompCmdC)
|
||||
}
|
||||
v.release()
|
||||
}
|
||||
|
||||
func (db *DB) mpoolPut(mem *memdb.DB) {
|
||||
defer func() {
|
||||
recover()
|
||||
@@ -117,7 +126,7 @@ func (db *DB) newMem(n int) (mem *memDB, err error) {
|
||||
}
|
||||
mem = &memDB{
|
||||
db: db,
|
||||
mdb: mdb,
|
||||
DB: mdb,
|
||||
ref: 2,
|
||||
}
|
||||
db.mem = mem
|
||||
|
||||
148
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_test.go
generated
vendored
@@ -405,19 +405,21 @@ func (h *dbHarness) compactRange(min, max string) {
|
||||
t.Log("DB range compaction done")
|
||||
}
|
||||
|
||||
func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
|
||||
t := h.t
|
||||
db := h.db
|
||||
|
||||
s, err := db.SizeOf([]util.Range{
|
||||
func (h *dbHarness) sizeOf(start, limit string) uint64 {
|
||||
sz, err := h.db.SizeOf([]util.Range{
|
||||
{[]byte(start), []byte(limit)},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("SizeOf: got error: ", err)
|
||||
h.t.Error("SizeOf: got error: ", err)
|
||||
}
|
||||
if s.Sum() < low || s.Sum() > hi {
|
||||
t.Errorf("sizeof %q to %q not in range, want %d - %d, got %d",
|
||||
shorten(start), shorten(limit), low, hi, s.Sum())
|
||||
return sz.Sum()
|
||||
}
|
||||
|
||||
func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
|
||||
sz := h.sizeOf(start, limit)
|
||||
if sz < low || sz > hi {
|
||||
h.t.Errorf("sizeOf %q to %q not in range, want %d - %d, got %d",
|
||||
shorten(start), shorten(limit), low, hi, sz)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2443,7 +2445,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
|
||||
rec := &sessionRecord{}
|
||||
rec.addTableFile(i, tf)
|
||||
if err := s.commit(rec); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2453,7 +2455,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
||||
// Build grandparent.
|
||||
v := s.version()
|
||||
c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
|
||||
rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
|
||||
rec := &sessionRecord{}
|
||||
b := &tableCompactionBuilder{
|
||||
s: s,
|
||||
c: c,
|
||||
@@ -2477,7 +2479,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
||||
// Build level-1.
|
||||
v = s.version()
|
||||
c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...))
|
||||
rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
|
||||
rec = &sessionRecord{}
|
||||
b = &tableCompactionBuilder{
|
||||
s: s,
|
||||
c: c,
|
||||
@@ -2521,7 +2523,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
||||
// Compaction with transient error.
|
||||
v = s.version()
|
||||
c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
|
||||
rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
|
||||
rec = &sessionRecord{}
|
||||
b = &tableCompactionBuilder{
|
||||
s: s,
|
||||
c: c,
|
||||
@@ -2577,3 +2579,123 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
||||
}
|
||||
v.release()
|
||||
}
|
||||
|
||||
func testDB_IterTriggeredCompaction(t *testing.T, limitDiv int) {
|
||||
const (
|
||||
vSize = 200 * opt.KiB
|
||||
tSize = 100 * opt.MiB
|
||||
mIter = 100
|
||||
n = tSize / vSize
|
||||
)
|
||||
|
||||
h := newDbHarnessWopt(t, &opt.Options{
|
||||
Compression: opt.NoCompression,
|
||||
DisableBlockCache: true,
|
||||
})
|
||||
defer h.close()
|
||||
|
||||
key := func(x int) string {
|
||||
return fmt.Sprintf("v%06d", x)
|
||||
}
|
||||
|
||||
// Fill.
|
||||
value := strings.Repeat("x", vSize)
|
||||
for i := 0; i < n; i++ {
|
||||
h.put(key(i), value)
|
||||
}
|
||||
h.compactMem()
|
||||
|
||||
// Delete all.
|
||||
for i := 0; i < n; i++ {
|
||||
h.delete(key(i))
|
||||
}
|
||||
h.compactMem()
|
||||
|
||||
var (
|
||||
limit = n / limitDiv
|
||||
|
||||
startKey = key(0)
|
||||
limitKey = key(limit)
|
||||
maxKey = key(n)
|
||||
slice = &util.Range{Limit: []byte(limitKey)}
|
||||
|
||||
initialSize0 = h.sizeOf(startKey, limitKey)
|
||||
initialSize1 = h.sizeOf(limitKey, maxKey)
|
||||
)
|
||||
|
||||
t.Logf("inital size %s [rest %s]", shortenb(int(initialSize0)), shortenb(int(initialSize1)))
|
||||
|
||||
for r := 0; true; r++ {
|
||||
if r >= mIter {
|
||||
t.Fatal("taking too long to compact")
|
||||
}
|
||||
|
||||
// Iterates.
|
||||
iter := h.db.NewIterator(slice, h.ro)
|
||||
for iter.Next() {
|
||||
}
|
||||
if err := iter.Error(); err != nil {
|
||||
t.Fatalf("Iter err: %v", err)
|
||||
}
|
||||
iter.Release()
|
||||
|
||||
// Wait compaction.
|
||||
h.waitCompaction()
|
||||
|
||||
// Check size.
|
||||
size0 := h.sizeOf(startKey, limitKey)
|
||||
size1 := h.sizeOf(limitKey, maxKey)
|
||||
t.Logf("#%03d size %s [rest %s]", r, shortenb(int(size0)), shortenb(int(size1)))
|
||||
if size0 < initialSize0/10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if initialSize1 > 0 {
|
||||
h.sizeAssert(limitKey, maxKey, initialSize1/4-opt.MiB, initialSize1+opt.MiB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDB_IterTriggeredCompaction(t *testing.T) {
|
||||
testDB_IterTriggeredCompaction(t, 1)
|
||||
}
|
||||
|
||||
func TestDB_IterTriggeredCompactionHalf(t *testing.T) {
|
||||
testDB_IterTriggeredCompaction(t, 2)
|
||||
}
|
||||
|
||||
func TestDB_ReadOnly(t *testing.T) {
|
||||
h := newDbHarness(t)
|
||||
defer h.close()
|
||||
|
||||
h.put("foo", "v1")
|
||||
h.put("bar", "v2")
|
||||
h.compactMem()
|
||||
|
||||
h.put("xfoo", "v1")
|
||||
h.put("xbar", "v2")
|
||||
|
||||
t.Log("Trigger read-only")
|
||||
if err := h.db.SetReadOnly(); err != nil {
|
||||
h.close()
|
||||
t.Fatalf("SetReadOnly error: %v", err)
|
||||
}
|
||||
|
||||
h.stor.SetEmuErr(storage.TypeAll, tsOpCreate, tsOpReplace, tsOpRemove, tsOpWrite, tsOpWrite, tsOpSync)
|
||||
|
||||
ro := func(key, value, wantValue string) {
|
||||
if err := h.db.Put([]byte(key), []byte(value), h.wo); err != ErrReadOnly {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
h.getVal(key, wantValue)
|
||||
}
|
||||
|
||||
ro("foo", "vx", "v1")
|
||||
|
||||
h.o.ReadOnly = true
|
||||
h.reopenDB()
|
||||
|
||||
ro("foo", "vx", "v1")
|
||||
ro("bar", "vx", "v2")
|
||||
h.assertNumKeys(4)
|
||||
}
|
||||
|
||||
75
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_write.go
generated
vendored
@@ -63,24 +63,24 @@ func (db *DB) rotateMem(n int) (mem *memDB, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
|
||||
func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
|
||||
delayed := false
|
||||
flush := func() (retry bool) {
|
||||
v := db.s.version()
|
||||
defer v.release()
|
||||
mem = db.getEffectiveMem()
|
||||
mdb = db.getEffectiveMem()
|
||||
defer func() {
|
||||
if retry {
|
||||
mem.decref()
|
||||
mem = nil
|
||||
mdb.decref()
|
||||
mdb = nil
|
||||
}
|
||||
}()
|
||||
nn = mem.mdb.Free()
|
||||
mdbFree = mdb.Free()
|
||||
switch {
|
||||
case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed:
|
||||
delayed = true
|
||||
time.Sleep(time.Millisecond)
|
||||
case nn >= n:
|
||||
case mdbFree >= n:
|
||||
return false
|
||||
case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger():
|
||||
delayed = true
|
||||
@@ -90,15 +90,15 @@ func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
|
||||
}
|
||||
default:
|
||||
// Allow memdb to grow if it has no entry.
|
||||
if mem.mdb.Len() == 0 {
|
||||
nn = n
|
||||
if mdb.Len() == 0 {
|
||||
mdbFree = n
|
||||
} else {
|
||||
mem.decref()
|
||||
mem, err = db.rotateMem(n)
|
||||
mdb.decref()
|
||||
mdb, err = db.rotateMem(n)
|
||||
if err == nil {
|
||||
nn = mem.mdb.Free()
|
||||
mdbFree = mdb.Free()
|
||||
} else {
|
||||
nn = 0
|
||||
mdbFree = 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -157,18 +157,18 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
mem, memFree, err := db.flush(b.size())
|
||||
mdb, mdbFree, err := db.flush(b.size())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer mem.decref()
|
||||
defer mdb.decref()
|
||||
|
||||
// Calculate maximum size of the batch.
|
||||
m := 1 << 20
|
||||
if x := b.size(); x <= 128<<10 {
|
||||
m = x + (128 << 10)
|
||||
}
|
||||
m = minInt(m, memFree)
|
||||
m = minInt(m, mdbFree)
|
||||
|
||||
// Merge with other batch.
|
||||
drain:
|
||||
@@ -197,7 +197,7 @@ drain:
|
||||
select {
|
||||
case db.journalC <- b:
|
||||
// Write into memdb
|
||||
if berr := b.memReplay(mem.mdb); berr != nil {
|
||||
if berr := b.memReplay(mdb.DB); berr != nil {
|
||||
panic(berr)
|
||||
}
|
||||
case err = <-db.compPerErrC:
|
||||
@@ -211,7 +211,7 @@ drain:
|
||||
case err = <-db.journalAckC:
|
||||
if err != nil {
|
||||
// Revert memdb if error detected
|
||||
if berr := b.revertMemReplay(mem.mdb); berr != nil {
|
||||
if berr := b.revertMemReplay(mdb.DB); berr != nil {
|
||||
panic(berr)
|
||||
}
|
||||
return
|
||||
@@ -225,7 +225,7 @@ drain:
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if berr := b.memReplay(mem.mdb); berr != nil {
|
||||
if berr := b.memReplay(mdb.DB); berr != nil {
|
||||
panic(berr)
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ drain:
|
||||
// Set last seq number.
|
||||
db.addSeq(uint64(b.Len()))
|
||||
|
||||
if b.size() >= memFree {
|
||||
if b.size() >= mdbFree {
|
||||
db.rotateMem(0)
|
||||
}
|
||||
return
|
||||
@@ -249,8 +249,7 @@ func (db *DB) Put(key, value []byte, wo *opt.WriteOptions) error {
|
||||
return db.Write(b, wo)
|
||||
}
|
||||
|
||||
// Delete deletes the value for the given key. It returns ErrNotFound if
|
||||
// the DB does not contain the key.
|
||||
// Delete deletes the value for the given key.
|
||||
//
|
||||
// It is safe to modify the contents of the arguments after Delete returns.
|
||||
func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error {
|
||||
@@ -290,9 +289,9 @@ func (db *DB) CompactRange(r util.Range) error {
|
||||
}
|
||||
|
||||
// Check for overlaps in memdb.
|
||||
mem := db.getEffectiveMem()
|
||||
defer mem.decref()
|
||||
if isMemOverlaps(db.s.icmp, mem.mdb, r.Start, r.Limit) {
|
||||
mdb := db.getEffectiveMem()
|
||||
defer mdb.decref()
|
||||
if isMemOverlaps(db.s.icmp, mdb.DB, r.Start, r.Limit) {
|
||||
// Memdb compaction.
|
||||
if _, err := db.rotateMem(0); err != nil {
|
||||
<-db.writeLockC
|
||||
@@ -309,3 +308,31 @@ func (db *DB) CompactRange(r util.Range) error {
|
||||
// Table compaction.
|
||||
return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit)
|
||||
}
|
||||
|
||||
// SetReadOnly makes DB read-only. It will stay read-only until reopened.
|
||||
func (db *DB) SetReadOnly() error {
|
||||
if err := db.ok(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock writer.
|
||||
select {
|
||||
case db.writeLockC <- struct{}{}:
|
||||
db.compWriteLocking = true
|
||||
case err := <-db.compPerErrC:
|
||||
return err
|
||||
case _, _ = <-db.closeC:
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
// Set compaction read-only.
|
||||
select {
|
||||
case db.compErrSetC <- ErrReadOnly:
|
||||
case perr := <-db.compPerErrC:
|
||||
return perr
|
||||
case _, _ = <-db.closeC:
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
1
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors.go
generated
vendored
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.ErrNotFound
|
||||
ErrReadOnly = errors.New("leveldb: read-only mode")
|
||||
ErrSnapshotReleased = errors.New("leveldb: snapshot released")
|
||||
ErrIterReleased = errors.New("leveldb: iterator released")
|
||||
ErrClosed = errors.New("leveldb: closed")
|
||||
|
||||
9
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go
generated
vendored
@@ -206,6 +206,7 @@ func (p *DB) randHeight() (h int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Must hold RW-lock if prev == true, as it use shared prevNode slice.
|
||||
func (p *DB) findGE(key []byte, prev bool) (int, bool) {
|
||||
node := 0
|
||||
h := p.maxHeight - 1
|
||||
@@ -302,7 +303,7 @@ func (p *DB) Put(key []byte, value []byte) error {
|
||||
node := len(p.nodeData)
|
||||
p.nodeData = append(p.nodeData, kvOffset, len(key), len(value), h)
|
||||
for i, n := range p.prevNode[:h] {
|
||||
m := n + 4 + i
|
||||
m := n + nNext + i
|
||||
p.nodeData = append(p.nodeData, p.nodeData[m])
|
||||
p.nodeData[m] = node
|
||||
}
|
||||
@@ -434,20 +435,22 @@ func (p *DB) Len() int {
|
||||
|
||||
// Reset resets the DB to initial empty state. Allows reuse the buffer.
|
||||
func (p *DB) Reset() {
|
||||
p.mu.Lock()
|
||||
p.rnd = rand.New(rand.NewSource(0xdeadbeef))
|
||||
p.maxHeight = 1
|
||||
p.n = 0
|
||||
p.kvSize = 0
|
||||
p.kvData = p.kvData[:0]
|
||||
p.nodeData = p.nodeData[:4+tMaxHeight]
|
||||
p.nodeData = p.nodeData[:nNext+tMaxHeight]
|
||||
p.nodeData[nKV] = 0
|
||||
p.nodeData[nKey] = 0
|
||||
p.nodeData[nVal] = 0
|
||||
p.nodeData[nHeight] = tMaxHeight
|
||||
for n := 0; n < tMaxHeight; n++ {
|
||||
p.nodeData[4+n] = 0
|
||||
p.nodeData[nNext+n] = 0
|
||||
p.prevNode[n] = 0
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// New creates a new initalized in-memory key/value DB. The capacity
|
||||
|
||||
50
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go
generated
vendored
@@ -34,10 +34,11 @@ var (
|
||||
DefaultCompactionTotalSize = 10 * MiB
|
||||
DefaultCompactionTotalSizeMultiplier = 10.0
|
||||
DefaultCompressionType = SnappyCompression
|
||||
DefaultOpenFilesCacher = LRUCacher
|
||||
DefaultOpenFilesCacheCapacity = 500
|
||||
DefaultIteratorSamplingRate = 1 * MiB
|
||||
DefaultMaxMemCompationLevel = 2
|
||||
DefaultNumLevel = 7
|
||||
DefaultOpenFilesCacher = LRUCacher
|
||||
DefaultOpenFilesCacheCapacity = 500
|
||||
DefaultWriteBuffer = 4 * MiB
|
||||
DefaultWriteL0PauseTrigger = 12
|
||||
DefaultWriteL0SlowdownTrigger = 8
|
||||
@@ -249,6 +250,11 @@ type Options struct {
|
||||
// The default value (DefaultCompression) uses snappy compression.
|
||||
Compression Compression
|
||||
|
||||
// DisableBufferPool allows disable use of util.BufferPool functionality.
|
||||
//
|
||||
// The default value is false.
|
||||
DisableBufferPool bool
|
||||
|
||||
// DisableBlockCache allows disable use of cache.Cache functionality on
|
||||
// 'sorted table' block.
|
||||
//
|
||||
@@ -288,6 +294,13 @@ type Options struct {
|
||||
// The default value is nil.
|
||||
Filter filter.Filter
|
||||
|
||||
// IteratorSamplingRate defines approximate gap (in bytes) between read
|
||||
// sampling of an iterator. The samples will be used to determine when
|
||||
// compaction should be triggered.
|
||||
//
|
||||
// The default is 1MiB.
|
||||
IteratorSamplingRate int
|
||||
|
||||
// MaxMemCompationLevel defines maximum level a newly compacted 'memdb'
|
||||
// will be pushed into if doesn't creates overlap. This should less than
|
||||
// NumLevel. Use -1 for level-0.
|
||||
@@ -313,6 +326,11 @@ type Options struct {
|
||||
// The default value is 500.
|
||||
OpenFilesCacheCapacity int
|
||||
|
||||
// If true then opens DB in read-only mode.
|
||||
//
|
||||
// The default value is false.
|
||||
ReadOnly bool
|
||||
|
||||
// Strict defines the DB strict level.
|
||||
Strict Strict
|
||||
|
||||
@@ -464,6 +482,20 @@ func (o *Options) GetCompression() Compression {
|
||||
return o.Compression
|
||||
}
|
||||
|
||||
func (o *Options) GetDisableBufferPool() bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
return o.DisableBufferPool
|
||||
}
|
||||
|
||||
func (o *Options) GetDisableBlockCache() bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
return o.DisableBlockCache
|
||||
}
|
||||
|
||||
func (o *Options) GetDisableCompactionBackoff() bool {
|
||||
if o == nil {
|
||||
return false
|
||||
@@ -492,6 +524,13 @@ func (o *Options) GetFilter() filter.Filter {
|
||||
return o.Filter
|
||||
}
|
||||
|
||||
func (o *Options) GetIteratorSamplingRate() int {
|
||||
if o == nil || o.IteratorSamplingRate <= 0 {
|
||||
return DefaultIteratorSamplingRate
|
||||
}
|
||||
return o.IteratorSamplingRate
|
||||
}
|
||||
|
||||
func (o *Options) GetMaxMemCompationLevel() int {
|
||||
level := DefaultMaxMemCompationLevel
|
||||
if o != nil {
|
||||
@@ -533,6 +572,13 @@ func (o *Options) GetOpenFilesCacheCapacity() int {
|
||||
return o.OpenFilesCacheCapacity
|
||||
}
|
||||
|
||||
func (o *Options) GetReadOnly() bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
return o.ReadOnly
|
||||
}
|
||||
|
||||
func (o *Options) GetStrict(strict Strict) bool {
|
||||
if o == nil || o.Strict == 0 {
|
||||
return DefaultStrict&strict != 0
|
||||
|
||||
264
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go
generated
vendored
@@ -11,10 +11,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/journal"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
@@ -127,11 +125,16 @@ func (s *session) recover() (err error) {
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
strict := s.o.GetStrict(opt.StrictManifest)
|
||||
jr := journal.NewReader(reader, dropper{s, m}, strict, true)
|
||||
|
||||
staging := s.stVersion.newStaging()
|
||||
rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
|
||||
var (
|
||||
// Options.
|
||||
numLevel = s.o.GetNumLevel()
|
||||
strict = s.o.GetStrict(opt.StrictManifest)
|
||||
|
||||
jr = journal.NewReader(reader, dropper{s, m}, strict, true)
|
||||
rec = &sessionRecord{}
|
||||
staging = s.stVersion.newStaging()
|
||||
)
|
||||
for {
|
||||
var r io.Reader
|
||||
r, err = jr.Next()
|
||||
@@ -143,7 +146,7 @@ func (s *session) recover() (err error) {
|
||||
return errors.SetFile(err, m)
|
||||
}
|
||||
|
||||
err = rec.decode(r)
|
||||
err = rec.decode(r, numLevel)
|
||||
if err == nil {
|
||||
// save compact pointers
|
||||
for _, r := range rec.compPtrs {
|
||||
@@ -206,250 +209,3 @@ func (s *session) commit(r *sessionRecord) (err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Pick a compaction based on current state; need external synchronization.
|
||||
func (s *session) pickCompaction() *compaction {
|
||||
v := s.version()
|
||||
|
||||
var level int
|
||||
var t0 tFiles
|
||||
if v.cScore >= 1 {
|
||||
level = v.cLevel
|
||||
cptr := s.stCompPtrs[level]
|
||||
tables := v.tables[level]
|
||||
for _, t := range tables {
|
||||
if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
|
||||
t0 = append(t0, t)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(t0) == 0 {
|
||||
t0 = append(t0, tables[0])
|
||||
}
|
||||
} else {
|
||||
if p := atomic.LoadPointer(&v.cSeek); p != nil {
|
||||
ts := (*tSet)(p)
|
||||
level = ts.level
|
||||
t0 = append(t0, ts.table)
|
||||
} else {
|
||||
v.release()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return newCompaction(s, v, level, t0)
|
||||
}
|
||||
|
||||
// Create compaction from given level and range; need external synchronization.
|
||||
func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction {
|
||||
v := s.version()
|
||||
|
||||
t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0)
|
||||
if len(t0) == 0 {
|
||||
v.release()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if level > 0 {
|
||||
limit := uint64(v.s.o.GetCompactionSourceLimit(level))
|
||||
total := uint64(0)
|
||||
for i, t := range t0 {
|
||||
total += t.size
|
||||
if total >= limit {
|
||||
s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
|
||||
t0 = t0[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newCompaction(s, v, level, t0)
|
||||
}
|
||||
|
||||
func newCompaction(s *session, v *version, level int, t0 tFiles) *compaction {
|
||||
c := &compaction{
|
||||
s: s,
|
||||
v: v,
|
||||
level: level,
|
||||
tables: [2]tFiles{t0, nil},
|
||||
maxGPOverlaps: uint64(s.o.GetCompactionGPOverlaps(level)),
|
||||
tPtrs: make([]int, s.o.GetNumLevel()),
|
||||
}
|
||||
c.expand()
|
||||
c.save()
|
||||
return c
|
||||
}
|
||||
|
||||
// compaction represent a compaction state.
|
||||
type compaction struct {
|
||||
s *session
|
||||
v *version
|
||||
|
||||
level int
|
||||
tables [2]tFiles
|
||||
maxGPOverlaps uint64
|
||||
|
||||
gp tFiles
|
||||
gpi int
|
||||
seenKey bool
|
||||
gpOverlappedBytes uint64
|
||||
imin, imax iKey
|
||||
tPtrs []int
|
||||
released bool
|
||||
|
||||
snapGPI int
|
||||
snapSeenKey bool
|
||||
snapGPOverlappedBytes uint64
|
||||
snapTPtrs []int
|
||||
}
|
||||
|
||||
func (c *compaction) save() {
|
||||
c.snapGPI = c.gpi
|
||||
c.snapSeenKey = c.seenKey
|
||||
c.snapGPOverlappedBytes = c.gpOverlappedBytes
|
||||
c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...)
|
||||
}
|
||||
|
||||
func (c *compaction) restore() {
|
||||
c.gpi = c.snapGPI
|
||||
c.seenKey = c.snapSeenKey
|
||||
c.gpOverlappedBytes = c.snapGPOverlappedBytes
|
||||
c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...)
|
||||
}
|
||||
|
||||
func (c *compaction) release() {
|
||||
if !c.released {
|
||||
c.released = true
|
||||
c.v.release()
|
||||
}
|
||||
}
|
||||
|
||||
// Expand compacted tables; need external synchronization.
|
||||
func (c *compaction) expand() {
|
||||
limit := uint64(c.s.o.GetCompactionExpandLimit(c.level))
|
||||
vt0, vt1 := c.v.tables[c.level], c.v.tables[c.level+1]
|
||||
|
||||
t0, t1 := c.tables[0], c.tables[1]
|
||||
imin, imax := t0.getRange(c.s.icmp)
|
||||
// We expand t0 here just incase ukey hop across tables.
|
||||
t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.level == 0)
|
||||
if len(t0) != len(c.tables[0]) {
|
||||
imin, imax = t0.getRange(c.s.icmp)
|
||||
}
|
||||
t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
|
||||
// Get entire range covered by compaction.
|
||||
amin, amax := append(t0, t1...).getRange(c.s.icmp)
|
||||
|
||||
// See if we can grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up.
|
||||
if len(t1) > 0 {
|
||||
exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.level == 0)
|
||||
if len(exp0) > len(t0) && t1.size()+exp0.size() < limit {
|
||||
xmin, xmax := exp0.getRange(c.s.icmp)
|
||||
exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
|
||||
if len(exp1) == len(t1) {
|
||||
c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
|
||||
c.level, c.level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
|
||||
len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
|
||||
imin, imax = xmin, xmax
|
||||
t0, t1 = exp0, exp1
|
||||
amin, amax = append(t0, t1...).getRange(c.s.icmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the set of grandparent files that overlap this compaction
|
||||
// (parent == level+1; grandparent == level+2)
|
||||
if c.level+2 < c.s.o.GetNumLevel() {
|
||||
c.gp = c.v.tables[c.level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
|
||||
}
|
||||
|
||||
c.tables[0], c.tables[1] = t0, t1
|
||||
c.imin, c.imax = imin, imax
|
||||
}
|
||||
|
||||
// Check whether compaction is trivial.
|
||||
func (c *compaction) trivial() bool {
|
||||
return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= c.maxGPOverlaps
|
||||
}
|
||||
|
||||
func (c *compaction) baseLevelForKey(ukey []byte) bool {
|
||||
for level, tables := range c.v.tables[c.level+2:] {
|
||||
for c.tPtrs[level] < len(tables) {
|
||||
t := tables[c.tPtrs[level]]
|
||||
if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
|
||||
// We've advanced far enough.
|
||||
if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
|
||||
// Key falls in this file's range, so definitely not base level.
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
c.tPtrs[level]++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *compaction) shouldStopBefore(ikey iKey) bool {
|
||||
for ; c.gpi < len(c.gp); c.gpi++ {
|
||||
gp := c.gp[c.gpi]
|
||||
if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
|
||||
break
|
||||
}
|
||||
if c.seenKey {
|
||||
c.gpOverlappedBytes += gp.size
|
||||
}
|
||||
}
|
||||
c.seenKey = true
|
||||
|
||||
if c.gpOverlappedBytes > c.maxGPOverlaps {
|
||||
// Too much overlap for current output; start new output.
|
||||
c.gpOverlappedBytes = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Creates an iterator.
|
||||
func (c *compaction) newIterator() iterator.Iterator {
|
||||
// Creates iterator slice.
|
||||
icap := len(c.tables)
|
||||
if c.level == 0 {
|
||||
// Special case for level-0
|
||||
icap = len(c.tables[0]) + 1
|
||||
}
|
||||
its := make([]iterator.Iterator, 0, icap)
|
||||
|
||||
// Options.
|
||||
ro := &opt.ReadOptions{
|
||||
DontFillCache: true,
|
||||
Strict: opt.StrictOverride,
|
||||
}
|
||||
strict := c.s.o.GetStrict(opt.StrictCompaction)
|
||||
if strict {
|
||||
ro.Strict |= opt.StrictReader
|
||||
}
|
||||
|
||||
for i, tables := range c.tables {
|
||||
if len(tables) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Level-0 is not sorted and may overlaps each other.
|
||||
if c.level+i == 0 {
|
||||
for _, t := range tables {
|
||||
its = append(its, c.s.tops.newIterator(t, nil, ro))
|
||||
}
|
||||
} else {
|
||||
it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict)
|
||||
its = append(its, it)
|
||||
}
|
||||
}
|
||||
|
||||
return iterator.NewMergedIterator(its, c.s.icmp, strict)
|
||||
}
|
||||
|
||||
287
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_compaction.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/memdb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
func (s *session) pickMemdbLevel(umin, umax []byte) int {
|
||||
v := s.version()
|
||||
defer v.release()
|
||||
return v.pickMemdbLevel(umin, umax)
|
||||
}
|
||||
|
||||
func (s *session) flushMemdb(rec *sessionRecord, mdb *memdb.DB, level int) (level_ int, err error) {
|
||||
// Create sorted table.
|
||||
iter := mdb.NewIterator(nil)
|
||||
defer iter.Release()
|
||||
t, n, err := s.tops.createFrom(iter)
|
||||
if err != nil {
|
||||
return level, err
|
||||
}
|
||||
|
||||
// Pick level and add to record.
|
||||
if level < 0 {
|
||||
level = s.pickMemdbLevel(t.imin.ukey(), t.imax.ukey())
|
||||
}
|
||||
rec.addTableFile(level, t)
|
||||
|
||||
s.logf("memdb@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
|
||||
return level, nil
|
||||
}
|
||||
|
||||
// Pick a compaction based on current state; need external synchronization.
|
||||
func (s *session) pickCompaction() *compaction {
|
||||
v := s.version()
|
||||
|
||||
var level int
|
||||
var t0 tFiles
|
||||
if v.cScore >= 1 {
|
||||
level = v.cLevel
|
||||
cptr := s.stCompPtrs[level]
|
||||
tables := v.tables[level]
|
||||
for _, t := range tables {
|
||||
if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
|
||||
t0 = append(t0, t)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(t0) == 0 {
|
||||
t0 = append(t0, tables[0])
|
||||
}
|
||||
} else {
|
||||
if p := atomic.LoadPointer(&v.cSeek); p != nil {
|
||||
ts := (*tSet)(p)
|
||||
level = ts.level
|
||||
t0 = append(t0, ts.table)
|
||||
} else {
|
||||
v.release()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return newCompaction(s, v, level, t0)
|
||||
}
|
||||
|
||||
// Create compaction from given level and range; need external synchronization.
|
||||
func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction {
|
||||
v := s.version()
|
||||
|
||||
t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0)
|
||||
if len(t0) == 0 {
|
||||
v.release()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if level > 0 {
|
||||
limit := uint64(v.s.o.GetCompactionSourceLimit(level))
|
||||
total := uint64(0)
|
||||
for i, t := range t0 {
|
||||
total += t.size
|
||||
if total >= limit {
|
||||
s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
|
||||
t0 = t0[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newCompaction(s, v, level, t0)
|
||||
}
|
||||
|
||||
func newCompaction(s *session, v *version, level int, t0 tFiles) *compaction {
|
||||
c := &compaction{
|
||||
s: s,
|
||||
v: v,
|
||||
level: level,
|
||||
tables: [2]tFiles{t0, nil},
|
||||
maxGPOverlaps: uint64(s.o.GetCompactionGPOverlaps(level)),
|
||||
tPtrs: make([]int, s.o.GetNumLevel()),
|
||||
}
|
||||
c.expand()
|
||||
c.save()
|
||||
return c
|
||||
}
|
||||
|
||||
// compaction represent a compaction state.
|
||||
type compaction struct {
|
||||
s *session
|
||||
v *version
|
||||
|
||||
level int
|
||||
tables [2]tFiles
|
||||
maxGPOverlaps uint64
|
||||
|
||||
gp tFiles
|
||||
gpi int
|
||||
seenKey bool
|
||||
gpOverlappedBytes uint64
|
||||
imin, imax iKey
|
||||
tPtrs []int
|
||||
released bool
|
||||
|
||||
snapGPI int
|
||||
snapSeenKey bool
|
||||
snapGPOverlappedBytes uint64
|
||||
snapTPtrs []int
|
||||
}
|
||||
|
||||
func (c *compaction) save() {
|
||||
c.snapGPI = c.gpi
|
||||
c.snapSeenKey = c.seenKey
|
||||
c.snapGPOverlappedBytes = c.gpOverlappedBytes
|
||||
c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...)
|
||||
}
|
||||
|
||||
func (c *compaction) restore() {
|
||||
c.gpi = c.snapGPI
|
||||
c.seenKey = c.snapSeenKey
|
||||
c.gpOverlappedBytes = c.snapGPOverlappedBytes
|
||||
c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...)
|
||||
}
|
||||
|
||||
func (c *compaction) release() {
|
||||
if !c.released {
|
||||
c.released = true
|
||||
c.v.release()
|
||||
}
|
||||
}
|
||||
|
||||
// Expand compacted tables; need external synchronization.
|
||||
func (c *compaction) expand() {
|
||||
limit := uint64(c.s.o.GetCompactionExpandLimit(c.level))
|
||||
vt0, vt1 := c.v.tables[c.level], c.v.tables[c.level+1]
|
||||
|
||||
t0, t1 := c.tables[0], c.tables[1]
|
||||
imin, imax := t0.getRange(c.s.icmp)
|
||||
// We expand t0 here just incase ukey hop across tables.
|
||||
t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.level == 0)
|
||||
if len(t0) != len(c.tables[0]) {
|
||||
imin, imax = t0.getRange(c.s.icmp)
|
||||
}
|
||||
t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
|
||||
// Get entire range covered by compaction.
|
||||
amin, amax := append(t0, t1...).getRange(c.s.icmp)
|
||||
|
||||
// See if we can grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up.
|
||||
if len(t1) > 0 {
|
||||
exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.level == 0)
|
||||
if len(exp0) > len(t0) && t1.size()+exp0.size() < limit {
|
||||
xmin, xmax := exp0.getRange(c.s.icmp)
|
||||
exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
|
||||
if len(exp1) == len(t1) {
|
||||
c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
|
||||
c.level, c.level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
|
||||
len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
|
||||
imin, imax = xmin, xmax
|
||||
t0, t1 = exp0, exp1
|
||||
amin, amax = append(t0, t1...).getRange(c.s.icmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the set of grandparent files that overlap this compaction
|
||||
// (parent == level+1; grandparent == level+2)
|
||||
if c.level+2 < c.s.o.GetNumLevel() {
|
||||
c.gp = c.v.tables[c.level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
|
||||
}
|
||||
|
||||
c.tables[0], c.tables[1] = t0, t1
|
||||
c.imin, c.imax = imin, imax
|
||||
}
|
||||
|
||||
// Check whether compaction is trivial.
|
||||
func (c *compaction) trivial() bool {
|
||||
return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= c.maxGPOverlaps
|
||||
}
|
||||
|
||||
func (c *compaction) baseLevelForKey(ukey []byte) bool {
|
||||
for level, tables := range c.v.tables[c.level+2:] {
|
||||
for c.tPtrs[level] < len(tables) {
|
||||
t := tables[c.tPtrs[level]]
|
||||
if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
|
||||
// We've advanced far enough.
|
||||
if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
|
||||
// Key falls in this file's range, so definitely not base level.
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
c.tPtrs[level]++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *compaction) shouldStopBefore(ikey iKey) bool {
|
||||
for ; c.gpi < len(c.gp); c.gpi++ {
|
||||
gp := c.gp[c.gpi]
|
||||
if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
|
||||
break
|
||||
}
|
||||
if c.seenKey {
|
||||
c.gpOverlappedBytes += gp.size
|
||||
}
|
||||
}
|
||||
c.seenKey = true
|
||||
|
||||
if c.gpOverlappedBytes > c.maxGPOverlaps {
|
||||
// Too much overlap for current output; start new output.
|
||||
c.gpOverlappedBytes = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Creates an iterator.
|
||||
func (c *compaction) newIterator() iterator.Iterator {
|
||||
// Creates iterator slice.
|
||||
icap := len(c.tables)
|
||||
if c.level == 0 {
|
||||
// Special case for level-0.
|
||||
icap = len(c.tables[0]) + 1
|
||||
}
|
||||
its := make([]iterator.Iterator, 0, icap)
|
||||
|
||||
// Options.
|
||||
ro := &opt.ReadOptions{
|
||||
DontFillCache: true,
|
||||
Strict: opt.StrictOverride,
|
||||
}
|
||||
strict := c.s.o.GetStrict(opt.StrictCompaction)
|
||||
if strict {
|
||||
ro.Strict |= opt.StrictReader
|
||||
}
|
||||
|
||||
for i, tables := range c.tables {
|
||||
if len(tables) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Level-0 is not sorted and may overlaps each other.
|
||||
if c.level+i == 0 {
|
||||
for _, t := range tables {
|
||||
its = append(its, c.s.tops.newIterator(t, nil, ro))
|
||||
}
|
||||
} else {
|
||||
it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict)
|
||||
its = append(its, it)
|
||||
}
|
||||
}
|
||||
|
||||
return iterator.NewMergedIterator(its, c.s.icmp, strict)
|
||||
}
|
||||
14
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record.go
generated
vendored
@@ -52,8 +52,6 @@ type dtRecord struct {
|
||||
}
|
||||
|
||||
type sessionRecord struct {
|
||||
numLevel int
|
||||
|
||||
hasRec int
|
||||
comparer string
|
||||
journalNum uint64
|
||||
@@ -230,7 +228,7 @@ func (p *sessionRecord) readBytes(field string, r byteReader) []byte {
|
||||
return x
|
||||
}
|
||||
|
||||
func (p *sessionRecord) readLevel(field string, r io.ByteReader) int {
|
||||
func (p *sessionRecord) readLevel(field string, r io.ByteReader, numLevel int) int {
|
||||
if p.err != nil {
|
||||
return 0
|
||||
}
|
||||
@@ -238,14 +236,14 @@ func (p *sessionRecord) readLevel(field string, r io.ByteReader) int {
|
||||
if p.err != nil {
|
||||
return 0
|
||||
}
|
||||
if x >= uint64(p.numLevel) {
|
||||
if x >= uint64(numLevel) {
|
||||
p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "invalid level number"})
|
||||
return 0
|
||||
}
|
||||
return int(x)
|
||||
}
|
||||
|
||||
func (p *sessionRecord) decode(r io.Reader) error {
|
||||
func (p *sessionRecord) decode(r io.Reader, numLevel int) error {
|
||||
br, ok := r.(byteReader)
|
||||
if !ok {
|
||||
br = bufio.NewReader(r)
|
||||
@@ -286,13 +284,13 @@ func (p *sessionRecord) decode(r io.Reader) error {
|
||||
p.setSeqNum(x)
|
||||
}
|
||||
case recCompPtr:
|
||||
level := p.readLevel("comp-ptr.level", br)
|
||||
level := p.readLevel("comp-ptr.level", br, numLevel)
|
||||
ikey := p.readBytes("comp-ptr.ikey", br)
|
||||
if p.err == nil {
|
||||
p.addCompPtr(level, iKey(ikey))
|
||||
}
|
||||
case recAddTable:
|
||||
level := p.readLevel("add-table.level", br)
|
||||
level := p.readLevel("add-table.level", br, numLevel)
|
||||
num := p.readUvarint("add-table.num", br)
|
||||
size := p.readUvarint("add-table.size", br)
|
||||
imin := p.readBytes("add-table.imin", br)
|
||||
@@ -301,7 +299,7 @@ func (p *sessionRecord) decode(r io.Reader) error {
|
||||
p.addTable(level, num, size, imin, imax)
|
||||
}
|
||||
case recDelTable:
|
||||
level := p.readLevel("del-table.level", br)
|
||||
level := p.readLevel("del-table.level", br, numLevel)
|
||||
num := p.readUvarint("del-table.num", br)
|
||||
if p.err == nil {
|
||||
p.delTable(level, num)
|
||||
|
||||
6
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go
generated
vendored
@@ -19,8 +19,8 @@ func decodeEncode(v *sessionRecord) (res bool, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
v2 := &sessionRecord{numLevel: opt.DefaultNumLevel}
|
||||
err = v.decode(b)
|
||||
v2 := &sessionRecord{}
|
||||
err = v.decode(b, opt.DefaultNumLevel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func decodeEncode(v *sessionRecord) (res bool, err error) {
|
||||
|
||||
func TestSessionRecord_EncodeDecode(t *testing.T) {
|
||||
big := uint64(1) << 50
|
||||
v := &sessionRecord{numLevel: opt.DefaultNumLevel}
|
||||
v := &sessionRecord{}
|
||||
i := uint64(0)
|
||||
test := func() {
|
||||
res, err := decodeEncode(v)
|
||||
|
||||
2
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_util.go
generated
vendored
@@ -182,7 +182,7 @@ func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
|
||||
defer v.release()
|
||||
}
|
||||
if rec == nil {
|
||||
rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
|
||||
rec = &sessionRecord{}
|
||||
}
|
||||
s.fillRecord(rec, true)
|
||||
v.fillRecord(rec)
|
||||
|
||||
10
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage_test.go
generated
vendored
@@ -42,6 +42,8 @@ type tsOp uint
|
||||
const (
|
||||
tsOpOpen tsOp = iota
|
||||
tsOpCreate
|
||||
tsOpReplace
|
||||
tsOpRemove
|
||||
tsOpRead
|
||||
tsOpReadAt
|
||||
tsOpWrite
|
||||
@@ -241,6 +243,10 @@ func (tf tsFile) Replace(newfile storage.File) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if tf.shouldErr(tsOpReplace) {
|
||||
err = errors.New("leveldb.testStorage: emulated create error")
|
||||
return
|
||||
}
|
||||
err = tf.File.Replace(newfile.(tsFile).File)
|
||||
if err != nil {
|
||||
ts.t.Errorf("E: cannot replace file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
|
||||
@@ -258,6 +264,10 @@ func (tf tsFile) Remove() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if tf.shouldErr(tsOpRemove) {
|
||||
err = errors.New("leveldb.testStorage: emulated create error")
|
||||
return
|
||||
}
|
||||
err = tf.File.Remove()
|
||||
if err != nil {
|
||||
ts.t.Errorf("E: cannot remove file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
|
||||
|
||||
8
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table.go
generated
vendored
@@ -441,22 +441,26 @@ func newTableOps(s *session) *tOps {
|
||||
var (
|
||||
cacher cache.Cacher
|
||||
bcache *cache.Cache
|
||||
bpool *util.BufferPool
|
||||
)
|
||||
if s.o.GetOpenFilesCacheCapacity() > 0 {
|
||||
cacher = cache.NewLRU(s.o.GetOpenFilesCacheCapacity())
|
||||
}
|
||||
if !s.o.DisableBlockCache {
|
||||
if !s.o.GetDisableBlockCache() {
|
||||
var bcacher cache.Cacher
|
||||
if s.o.GetBlockCacheCapacity() > 0 {
|
||||
bcacher = cache.NewLRU(s.o.GetBlockCacheCapacity())
|
||||
}
|
||||
bcache = cache.NewCache(bcacher)
|
||||
}
|
||||
if !s.o.GetDisableBufferPool() {
|
||||
bpool = util.NewBufferPool(s.o.GetBlockSize() + 5)
|
||||
}
|
||||
return &tOps{
|
||||
s: s,
|
||||
cache: cache.NewCache(cacher),
|
||||
bcache: bcache,
|
||||
bpool: util.NewBufferPool(s.o.GetBlockSize() + 5),
|
||||
bpool: bpool,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/reader.go
generated
vendored
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/syndtr/gosnappy/snappy"
|
||||
"github.com/google/go-snappy/snappy"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/cache"
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
|
||||
2
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/writer.go
generated
vendored
@@ -12,7 +12,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/syndtr/gosnappy/snappy"
|
||||
"github.com/google/go-snappy/snappy"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||
|
||||
27
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/version.go
generated
vendored
@@ -136,9 +136,8 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
|
||||
if !tseek {
|
||||
if tset == nil {
|
||||
tset = &tSet{level, t}
|
||||
} else if tset.table.consumeSeek() <= 0 {
|
||||
} else {
|
||||
tseek = true
|
||||
tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +202,28 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
|
||||
return true
|
||||
})
|
||||
|
||||
if tseek && tset.table.consumeSeek() <= 0 {
|
||||
tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (v *version) sampleSeek(ikey iKey) (tcomp bool) {
|
||||
var tset *tSet
|
||||
|
||||
v.walkOverlapping(ikey, func(level int, t *tFile) bool {
|
||||
if tset == nil {
|
||||
tset = &tSet{level, t}
|
||||
return true
|
||||
} else {
|
||||
if tset.table.consumeSeek() <= 0 {
|
||||
tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset))
|
||||
}
|
||||
return false
|
||||
}
|
||||
}, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -279,7 +300,7 @@ func (v *version) offsetOf(ikey iKey) (n uint64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (v *version) pickLevel(umin, umax []byte) (level int) {
|
||||
func (v *version) pickMemdbLevel(umin, umax []byte) (level int) {
|
||||
if !v.tables[0].overlaps(v.s.icmp, umin, umax, true) {
|
||||
var overlaps tFiles
|
||||
maxLevel := v.s.o.GetMaxMemCompationLevel()
|
||||
|
||||
1
Godeps/_workspace/src/github.com/thejerf/suture/pre-commit
generated
vendored
@@ -9,3 +9,4 @@ if [ ! -z "$GOLINTOUT" -o "$?" != 0 ]; then
|
||||
fi
|
||||
|
||||
go test
|
||||
|
||||
|
||||
12
NICKS
@@ -3,6 +3,8 @@
|
||||
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>
|
||||
Vilbrekin <vilbrekin@gmail.com>
|
||||
@@ -13,16 +15,21 @@ asdil12 <dominik@heidler.eu>
|
||||
bencurthoys <ben@bencurthoys.com>
|
||||
bigbear2nd <bigbear2nd@gmail.com>
|
||||
brendanlong <self@brendanlong.com>
|
||||
brgmnn <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||
bsidhom <bsidhom@gmail.com>
|
||||
calmh <jakob@nym.se>
|
||||
canton7 <antony.male@gmail.com>
|
||||
cdata <chris@scriptolo.gy>
|
||||
cdhowie <me@chrishowie.com>
|
||||
ceh <emil@hessman.se>
|
||||
cqcallaw <enlightened.despot@gmail.com>
|
||||
dzarda <dzardacz@gmail.com>
|
||||
facastagnini <federico.castagnini@gmail.com>
|
||||
filoozoom <philippe@schommers.be>
|
||||
frioux <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
fti7 <frank@isemann.name>
|
||||
gillisig <gilli@vx.is>
|
||||
jarlebring <jarlebring@gmail.com>
|
||||
jedie <github.com@jensdiemer.de> <git@jensdiemer.de>
|
||||
jpjp <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||
kamadak <kamada@nanohz.org>
|
||||
@@ -31,6 +38,7 @@ kozec <kozec@kozec.com>
|
||||
krozycki <rozycki.karol@gmail.com>
|
||||
marcindziadus <dziadus.marcin@gmail.com>
|
||||
marclaporte <marc@marclaporte.com>
|
||||
mogwa1 <devriesb@gmail.com>
|
||||
moshen <moshen.colin@gmail.com>
|
||||
mvdan <mvdan@mvdan.cc>
|
||||
pascalj <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||
@@ -39,9 +47,9 @@ philips <brandon@ifup.org>
|
||||
piobpl <piotrb10@gmail.com>
|
||||
pluby <phill.luby@newredo.com>
|
||||
pyfisch <pyfisch@gmail.com>
|
||||
qbit <qbit@deftly.net>
|
||||
ralder <ralder@yandex.ru>
|
||||
rumpelsepp <stefan@sevenbyte.org>
|
||||
qbit <qbit@deftly.net>
|
||||
sciurius <jvromans@squirrel.nl>
|
||||
seehuhn <voss@seehuhn.de>
|
||||
snnd <dw@risu.io>
|
||||
@@ -50,3 +58,5 @@ tnn2 <tnn@nygren.pp.se>
|
||||
tojrobinson <tully@tojr.org>
|
||||
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||
veeti <veeti.paananen@rojekti.fi>
|
||||
wsgcsysadmin <e.meitner@willystreet.coo>
|
||||
zukoo <fxgsell@gmail.com>
|
||||
|
||||
34
README.md
@@ -1,60 +1,58 @@
|
||||
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. The following are the project 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. The protocol should be well defined, unambiguous,
|
||||
collaborating devices. This protocol should be well defined, unambiguous,
|
||||
easily understood, free to use, efficient, secure and language neutral.
|
||||
This is the [Block Exchange
|
||||
This is called the [Block Exchange
|
||||
Protocol](https://github.com/syncthing/specs/blob/master/BEPv1.md).
|
||||
|
||||
2. Provide the reference implementation to demonstrate the usability of
|
||||
said protocol. This is the `syncthing` utility. It is the hope that
|
||||
alternative, compatible implementations of the protocol will come to
|
||||
exist.
|
||||
said protocol. This is the `syncthing` utility. We hope that
|
||||
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
|
||||
to developers and users (when awake and present, etc.).
|
||||
to developers and users.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Building Syncthing from source is easy, and there's a
|
||||
[guide](https://github.com/syncthing/syncthing/wiki/Building).
|
||||
that describes it for both Unix and Windows.
|
||||
[guide](http://docs.syncthing.net/dev/building.html).
|
||||
that describes it for both Unix and Windows systems.
|
||||
|
||||
Signed Releases
|
||||
---------------
|
||||
|
||||
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
|
||||
with the key D26E6ED000654A3E (see http://syncthing.net/security.html).
|
||||
with the key D26E6ED000654A3E (see https://syncthing.net/security.html).
|
||||
For release binaries, MD5 and SHA1 checksums are calculated and signed,
|
||||
available in the md5sum.txt.asc and sha1sum.txt.asc files.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
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](https://github.com/syncthing/syncthing/blob/master/LICENSE).
|
||||
[MPLv2 License](https://github.com/syncthing/syncthing/blob/master/LICENSE).
|
||||
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
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
@@ -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
@@ -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"
|
||||
;;
|
||||
|
||||
@@ -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
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -36,8 +36,7 @@ const (
|
||||
func Assets() map[string][]byte {
|
||||
var assets = make(map[string][]byte, {{.Assets | len}})
|
||||
{{range $asset := .Assets}}
|
||||
assets["{{$asset.Name}}"], _ = base64.StdEncoding.DecodeString("{{$asset.Data}}")
|
||||
{{end}}
|
||||
assets["{{$asset.Name}}"], _ = base64.StdEncoding.DecodeString("{{$asset.Data}}"){{end}}
|
||||
return assets
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
69
cmd/syncthing/audit.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
)
|
||||
|
||||
// The auditSvc subscribes to events and writes these in JSON format, one
|
||||
// event per line, to the specified writer.
|
||||
type auditSvc struct {
|
||||
w io.Writer // audit destination
|
||||
stop chan struct{} // signals time to stop
|
||||
started chan struct{} // signals startup complete
|
||||
stopped chan struct{} // signals stop complete
|
||||
}
|
||||
|
||||
func newAuditSvc(w io.Writer) *auditSvc {
|
||||
return &auditSvc{
|
||||
w: w,
|
||||
stop: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Serve runs the audit service.
|
||||
func (s *auditSvc) Serve() {
|
||||
defer close(s.stopped)
|
||||
sub := events.Default.Subscribe(events.AllEvents)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
enc := json.NewEncoder(s.w)
|
||||
|
||||
// We're ready to start processing events.
|
||||
close(s.started)
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-sub.C():
|
||||
enc.Encode(ev)
|
||||
case <-s.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the audit service.
|
||||
func (s *auditSvc) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
// WaitForStart returns once the audit service is ready to receive events, or
|
||||
// immediately if it's already running.
|
||||
func (s *auditSvc) WaitForStart() {
|
||||
<-s.started
|
||||
}
|
||||
|
||||
// WaitForStop returns once the audit service has stopped.
|
||||
// (Needed by the tests.)
|
||||
func (s *auditSvc) WaitForStop() {
|
||||
<-s.stopped
|
||||
}
|
||||
54
cmd/syncthing/auditsvc_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
)
|
||||
|
||||
func TestAuditService(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
svc := newAuditSvc(buf)
|
||||
|
||||
// Event sent before start, will not be logged
|
||||
events.Default.Log(events.Ping, "the first event")
|
||||
|
||||
go svc.Serve()
|
||||
svc.WaitForStart()
|
||||
|
||||
// Event that should end up in the audit log
|
||||
events.Default.Log(events.Ping, "the second event")
|
||||
|
||||
// We need to give the events time to arrive, since the channels are buffered etc.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
svc.Stop()
|
||||
svc.WaitForStop()
|
||||
|
||||
// This event should not be logged, since we have stopped.
|
||||
events.Default.Log(events.Ping, "the third event")
|
||||
|
||||
result := string(buf.Bytes())
|
||||
t.Log(result)
|
||||
|
||||
if strings.Contains(result, "first event") {
|
||||
t.Error("Unexpected first event")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "second event") {
|
||||
t.Error("Missing second event")
|
||||
}
|
||||
|
||||
if strings.Contains(result, "third event") {
|
||||
t.Error("Missing third event")
|
||||
}
|
||||
}
|
||||
@@ -15,23 +15,84 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/protocol"
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
"github.com/syncthing/syncthing/internal/model"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
func listenConnect(myID protocol.DeviceID, m *model.Model, tlsCfg *tls.Config) {
|
||||
var conns = make(chan *tls.Conn)
|
||||
// The connection service listens on TLS and dials configured unconnected
|
||||
// devices. Successful connections are handed to the model.
|
||||
type connectionSvc struct {
|
||||
*suture.Supervisor
|
||||
cfg *config.Wrapper
|
||||
myID protocol.DeviceID
|
||||
model *model.Model
|
||||
tlsCfg *tls.Config
|
||||
conns chan *tls.Conn
|
||||
}
|
||||
|
||||
// Listen
|
||||
for _, addr := range cfg.Options().ListenAddress {
|
||||
go listenTLS(conns, addr, tlsCfg)
|
||||
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.Model, tlsCfg *tls.Config) *connectionSvc {
|
||||
svc := &connectionSvc{
|
||||
Supervisor: suture.NewSimple("connectionSvc"),
|
||||
cfg: cfg,
|
||||
myID: myID,
|
||||
model: model,
|
||||
tlsCfg: tlsCfg,
|
||||
conns: make(chan *tls.Conn),
|
||||
}
|
||||
|
||||
// Connect
|
||||
go dialTLS(m, conns, tlsCfg)
|
||||
// There are several moving parts here; one routine per listening address
|
||||
// to handle incoming connections, one routine to periodically attempt
|
||||
// outgoing connections, and lastly one routine to the the common handling
|
||||
// regardless of whether the connection was incoming or outgoing. It ends
|
||||
// up as in the diagram below. We embed a Supervisor to manage the
|
||||
// routines (i.e. log and restart if they crash or exit, etc).
|
||||
//
|
||||
// +-----------------+
|
||||
// Incoming | +---------------+-+ +-----------------+
|
||||
// Connections | | | | | Outgoing
|
||||
// -------------->| | svc.listen | | | Connections
|
||||
// | | (1 per listen | | svc.connect |-------------->
|
||||
// | | address) | | |
|
||||
// +-+ | | |
|
||||
// +-----------------+ +-----------------+
|
||||
// v v
|
||||
// | |
|
||||
// | |
|
||||
// +------------+-----------+
|
||||
// |
|
||||
// | svc.conns
|
||||
// v
|
||||
// +-----------------+
|
||||
// | |
|
||||
// | |
|
||||
// | svc.handle |------> model.AddConnection()
|
||||
// | |
|
||||
// | |
|
||||
// +-----------------+
|
||||
//
|
||||
// TODO: Clean shutdown, and/or handling config changes on the fly. We
|
||||
// partly do this now - new devices and addresses will be picked up, but
|
||||
// not new listen addresses and we don't support disconnecting devices
|
||||
// that are removed and so on...
|
||||
|
||||
svc.Add(serviceFunc(svc.connect))
|
||||
for _, addr := range svc.cfg.Options().ListenAddress {
|
||||
addr := addr
|
||||
listener := serviceFunc(func() {
|
||||
svc.listen(addr)
|
||||
})
|
||||
svc.Add(listener)
|
||||
}
|
||||
svc.Add(serviceFunc(svc.handle))
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func (s *connectionSvc) handle() {
|
||||
next:
|
||||
for conn := range conns {
|
||||
for conn := range s.conns {
|
||||
cs := conn.ConnectionState()
|
||||
|
||||
// We should have negotiated the next level protocol "bep/1.0" as part
|
||||
@@ -55,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)
|
||||
@@ -67,15 +128,15 @@ 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 m.ConnectedTo(remoteID) {
|
||||
if s.model.ConnectedTo(remoteID) {
|
||||
l.Infof("Connected to already connected device (%s)", remoteID)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
for deviceID, deviceCfg := range cfg.Devices() {
|
||||
for deviceID, deviceCfg := range s.cfg.Devices() {
|
||||
if deviceID == remoteID {
|
||||
// Verify the name on the certificate. By default we set it to
|
||||
// "syncthing" when generating, but the user may have replaced
|
||||
@@ -97,7 +158,7 @@ next:
|
||||
// If rate limiting is set, and based on the address we should
|
||||
// limit the connection, then we wrap it in a limiter.
|
||||
|
||||
limit := shouldLimit(conn.RemoteAddr())
|
||||
limit := s.shouldLimit(conn.RemoteAddr())
|
||||
|
||||
wr := io.Writer(conn)
|
||||
if limit && writeRateLimit != nil {
|
||||
@@ -110,23 +171,19 @@ next:
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
|
||||
protoConn := protocol.NewConnection(remoteID, rd, wr, m, name, deviceCfg.Compression)
|
||||
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression)
|
||||
|
||||
l.Infof("Established secure connection to %s at %s", remoteID, name)
|
||||
if debugNet {
|
||||
l.Debugf("cipher suite: %04X in lan: %t", conn.ConnectionState().CipherSuite, !limit)
|
||||
}
|
||||
events.Default.Log(events.DeviceConnected, map[string]string{
|
||||
"id": remoteID.String(),
|
||||
"addr": conn.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
m.AddConnection(conn, protoConn)
|
||||
s.model.AddConnection(conn, protoConn)
|
||||
continue next
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.IgnoredDevice(remoteID) {
|
||||
if !s.cfg.IgnoredDevice(remoteID) {
|
||||
events.Default.Log(events.DeviceRejected, map[string]string{
|
||||
"device": remoteID.String(),
|
||||
"address": conn.RemoteAddr().String(),
|
||||
@@ -140,7 +197,7 @@ next:
|
||||
}
|
||||
}
|
||||
|
||||
func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
|
||||
func (s *connectionSvc) listen(addr string) {
|
||||
if debugNet {
|
||||
l.Debugln("listening on", addr)
|
||||
}
|
||||
@@ -166,9 +223,9 @@ func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
|
||||
}
|
||||
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
setTCPOptions(tcpConn)
|
||||
s.setTCPOptions(tcpConn)
|
||||
|
||||
tc := tls.Server(conn, tlsCfg)
|
||||
tc := tls.Server(conn, s.tlsCfg)
|
||||
err = tc.Handshake()
|
||||
if err != nil {
|
||||
l.Infoln("TLS handshake:", err)
|
||||
@@ -176,21 +233,20 @@ func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
|
||||
continue
|
||||
}
|
||||
|
||||
conns <- tc
|
||||
s.conns <- tc
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
|
||||
func (s *connectionSvc) connect() {
|
||||
delay := time.Second
|
||||
for {
|
||||
nextDevice:
|
||||
for deviceID, deviceCfg := range cfg.Devices() {
|
||||
for deviceID, deviceCfg := range s.cfg.Devices() {
|
||||
if deviceID == myID {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.ConnectedTo(deviceID) {
|
||||
if s.model.ConnectedTo(deviceID) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -238,9 +294,9 @@ func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
|
||||
continue
|
||||
}
|
||||
|
||||
setTCPOptions(conn)
|
||||
s.setTCPOptions(conn)
|
||||
|
||||
tc := tls.Client(conn, tlsCfg)
|
||||
tc := tls.Client(conn, s.tlsCfg)
|
||||
err = tc.Handshake()
|
||||
if err != nil {
|
||||
l.Infoln("TLS handshake:", err)
|
||||
@@ -248,20 +304,20 @@ func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
|
||||
continue
|
||||
}
|
||||
|
||||
conns <- tc
|
||||
s.conns <- tc
|
||||
continue nextDevice
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
delay *= 2
|
||||
if maxD := time.Duration(cfg.Options().ReconnectIntervalS) * time.Second; delay > maxD {
|
||||
if maxD := time.Duration(s.cfg.Options().ReconnectIntervalS) * time.Second; delay > maxD {
|
||||
delay = maxD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setTCPOptions(conn *net.TCPConn) {
|
||||
func (*connectionSvc) setTCPOptions(conn *net.TCPConn) {
|
||||
var err error
|
||||
if err = conn.SetLinger(0); err != nil {
|
||||
l.Infoln(err)
|
||||
@@ -277,8 +333,8 @@ func setTCPOptions(conn *net.TCPConn) {
|
||||
}
|
||||
}
|
||||
|
||||
func shouldLimit(addr net.Addr) bool {
|
||||
if cfg.Options().LimitBandwidthInLan {
|
||||
func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
|
||||
if s.cfg.Options().LimitBandwidthInLan {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -293,3 +349,24 @@ func 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"
|
||||
)
|
||||
|
||||
@@ -12,26 +12,27 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
"github.com/syncthing/syncthing/internal/sync"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
sessions = make(map[string]bool)
|
||||
sessionsMut sync.Mutex
|
||||
sessionsMut = sync.NewMutex()
|
||||
)
|
||||
|
||||
func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := r.Cookie("sessionid")
|
||||
cookie, err := r.Cookie(cookieName)
|
||||
if err == nil && cookie != nil {
|
||||
sessionsMut.Lock()
|
||||
_, ok := sessions[cookie.Value]
|
||||
@@ -42,6 +43,10 @@ func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handle
|
||||
}
|
||||
}
|
||||
|
||||
if debugHTTP {
|
||||
l.Debugln("Sessionless HTTP request with authentication; this is expensive.")
|
||||
}
|
||||
|
||||
error := func() {
|
||||
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
|
||||
@@ -82,7 +87,7 @@ func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handle
|
||||
sessions[sessionid] = true
|
||||
sessionsMut.Unlock()
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "sessionid",
|
||||
Name: cookieName,
|
||||
Value: sessionid,
|
||||
MaxAge: 0,
|
||||
})
|
||||
|
||||
@@ -12,19 +12,19 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"github.com/syncthing/syncthing/internal/sync"
|
||||
)
|
||||
|
||||
var csrfTokens []string
|
||||
var csrfMut sync.Mutex
|
||||
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
|
||||
// is currently set.
|
||||
func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
|
||||
func csrfMiddleware(unique, prefix, apiKey string, next http.Handler) http.Handler {
|
||||
loadCsrfTokens()
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Allow requests carrying a valid API key
|
||||
@@ -35,10 +35,10 @@ func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
|
||||
|
||||
// Allow requests for the front page, and set a CSRF cookie if there isn't already a valid one.
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
cookie, err := r.Cookie("CSRF-Token")
|
||||
cookie, err := r.Cookie("CSRF-Token-" + unique)
|
||||
if err != nil || !validCsrfToken(cookie.Value) {
|
||||
cookie = &http.Cookie{
|
||||
Name: "CSRF-Token",
|
||||
Name: "CSRF-Token-" + unique,
|
||||
Value: newCsrfToken(),
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
@@ -54,7 +54,7 @@ func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// Verify the CSRF token
|
||||
token := r.Header.Get("X-CSRF-Token")
|
||||
token := r.Header.Get("X-CSRF-Token-" + unique)
|
||||
if !validCsrfToken(token) {
|
||||
http.Error(w, "CSRF Error", 403)
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
)
|
||||
@@ -29,6 +30,8 @@ const (
|
||||
locLogFile = "logFile"
|
||||
locCsrfTokens = "csrfTokens"
|
||||
locPanicLog = "panicLog"
|
||||
locAuditLog = "auditLog"
|
||||
locGUIAssets = "GUIAssets"
|
||||
locDefFolder = "defFolder"
|
||||
)
|
||||
|
||||
@@ -48,7 +51,9 @@ var locations = map[locationEnum]string{
|
||||
locDatabase: "${config}/index-v0.11.0.db",
|
||||
locLogFile: "${config}/syncthing.log", // -logfile on Windows
|
||||
locCsrfTokens: "${config}/csrftokens.txt",
|
||||
locPanicLog: "${config}/panic-20060102-150405.log", // passed through time.Format()
|
||||
locPanicLog: "${config}/panic-${timestamp}.log",
|
||||
locAuditLog: "${config}/audit-${timestamp}.log",
|
||||
locGUIAssets: "${config}/gui",
|
||||
locDefFolder: "${home}/Sync",
|
||||
}
|
||||
|
||||
@@ -107,3 +112,14 @@ func homeDir() string {
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
func timestampedLoc(key locationEnum) string {
|
||||
// We take the roundtrip via "${timestamp}" instead of passing the path
|
||||
// directly through time.Format() to avoid issues when the path we are
|
||||
// expanding contains numbers; otherwise for example
|
||||
// /home/user2006/.../panic-20060102-150405.log would get both instances of
|
||||
// 2006 replaced by 2015...
|
||||
tpl := locations[key]
|
||||
now := time.Now().Format("20060102-150405")
|
||||
return strings.Replace(tpl, "${timestamp}", now, -1)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -35,10 +36,10 @@ 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"
|
||||
"github.com/thejerf/suture"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,7 @@ var (
|
||||
BuildHost = "unknown"
|
||||
BuildUser = "unknown"
|
||||
IsRelease bool
|
||||
IsBeta bool
|
||||
LongVersion string
|
||||
)
|
||||
|
||||
@@ -77,9 +79,15 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a clean release build.
|
||||
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-beta[\d\.]+)?$`)
|
||||
// Check for a clean release build. A release is something like "v0.1.2",
|
||||
// with an optional suffix of letters and dot separated numbers like
|
||||
// "-beta3.47". If there's more stuff, like a plus sign and a commit hash
|
||||
// and so on, then it's not a release. If there's a dash anywhere in
|
||||
// there, it's some kind of beta or prerelease version.
|
||||
|
||||
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z]+[\d\.]+)?$`)
|
||||
IsRelease = exp.MatchString(Version)
|
||||
IsBeta = strings.Contains(Version, "-")
|
||||
|
||||
stamp, _ := strconv.Atoi(BuildStamp)
|
||||
BuildDate = time.Unix(int64(stamp), 0)
|
||||
@@ -101,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
|
||||
)
|
||||
@@ -144,10 +150,12 @@ are mostly useful for developers. Use with care.
|
||||
- "events" (the events package)
|
||||
- "files" (the files package)
|
||||
- "http" (the main package; HTTP requests)
|
||||
- "locks" (the sync package; trace long held locks)
|
||||
- "net" (the main package; connections & network messages)
|
||||
- "model" (the model package)
|
||||
- "scanner" (the scanner package)
|
||||
- "stats" (the stats package)
|
||||
- "suture" (the suture package; service management)
|
||||
- "upnp" (the upnp package)
|
||||
- "xdr" (the xdr package)
|
||||
- "all" (all of the above)
|
||||
@@ -187,6 +195,8 @@ var (
|
||||
noConsole bool
|
||||
generateDir string
|
||||
logFile string
|
||||
auditEnabled bool
|
||||
verbose bool
|
||||
noRestart = os.Getenv("STNORESTART") != ""
|
||||
noUpgrade = os.Getenv("STNOUPGRADE") != ""
|
||||
guiAddress = os.Getenv("STGUIADDRESS") // legacy
|
||||
@@ -223,6 +233,8 @@ func main() {
|
||||
flag.BoolVar(&doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
|
||||
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()
|
||||
@@ -240,6 +252,10 @@ func main() {
|
||||
l.Fatalln(err)
|
||||
}
|
||||
|
||||
if guiAssets == "" {
|
||||
guiAssets = locations[locGUIAssets]
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if logFile == "" {
|
||||
// Use the default log file location
|
||||
@@ -268,7 +284,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)
|
||||
}
|
||||
@@ -323,7 +339,7 @@ func main() {
|
||||
}
|
||||
|
||||
if doUpgrade || doUpgradeCheck {
|
||||
rel, err := upgrade.LatestGithubRelease(Version)
|
||||
rel, err := upgrade.LatestRelease(Version)
|
||||
if err != nil {
|
||||
l.Fatalln("Upgrade:", err) // exits 1
|
||||
}
|
||||
@@ -339,7 +355,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)
|
||||
@@ -364,17 +386,76 @@ 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() {
|
||||
var err error
|
||||
// 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.
|
||||
mainSvc := suture.New("main", suture.Spec{
|
||||
Log: func(line string) {
|
||||
if debugSuture {
|
||||
l.Debugln(line)
|
||||
}
|
||||
},
|
||||
})
|
||||
mainSvc.ServeBackground()
|
||||
|
||||
// Set a log prefix similar to the ID we will have later on, or early log
|
||||
// lines look ugly.
|
||||
l.SetPrefix("[start] ")
|
||||
|
||||
if auditEnabled {
|
||||
startAuditing(mainSvc)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
mainSvc.Add(newVerboseSvc())
|
||||
}
|
||||
|
||||
// Event subscription for the API; must start early to catch the early events.
|
||||
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000)
|
||||
|
||||
if len(os.Getenv("GOMAXPROCS")) == 0 {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
|
||||
events.Default.Log(events.Starting, map[string]string{"home": baseDirs["config"]})
|
||||
|
||||
// Ensure that that we have a certificate and key.
|
||||
cert, err = tls.LoadX509KeyPair(locations[locCertFile], locations[locKeyFile])
|
||||
cert, err := tls.LoadX509KeyPair(locations[locCertFile], locations[locKeyFile])
|
||||
if err != nil {
|
||||
cert, err = newCertificate(locations[locCertFile], locations[locKeyFile], tlsDefaultCommonName)
|
||||
if err != nil {
|
||||
@@ -392,6 +473,13 @@ func syncthingMain() {
|
||||
l.Infoln(LongVersion)
|
||||
l.Infoln("My ID:", myID)
|
||||
|
||||
// Emit the Starting event, now that we know who we are.
|
||||
|
||||
events.Default.Log(events.Starting, map[string]string{
|
||||
"home": baseDirs["config"],
|
||||
"myID": myID.String(),
|
||||
})
|
||||
|
||||
// Prepare to be able to save configuration
|
||||
|
||||
cfgFile := locations[locConfigFile]
|
||||
@@ -433,6 +521,10 @@ func syncthingMain() {
|
||||
cfg.Save()
|
||||
}
|
||||
|
||||
if err := checkShortIDs(cfg); err != nil {
|
||||
l.Fatalln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one if the following:\n ", err)
|
||||
}
|
||||
|
||||
if len(profiler) > 0 {
|
||||
go func() {
|
||||
l.Debugln("Starting profiler on", profiler)
|
||||
@@ -473,6 +565,9 @@ func syncthingMain() {
|
||||
symlinks.Supported = false
|
||||
}
|
||||
|
||||
protocol.PingTimeout = time.Duration(opts.PingTimeoutS) * time.Second
|
||||
protocol.PingIdleTime = time.Duration(opts.PingIdleTimeS) * time.Second
|
||||
|
||||
if opts.MaxSendKbps > 0 {
|
||||
writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxSendKbps), int64(5*1000*opts.MaxSendKbps))
|
||||
}
|
||||
@@ -490,10 +585,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?")
|
||||
@@ -509,10 +603,16 @@ func syncthingMain() {
|
||||
}
|
||||
|
||||
m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb)
|
||||
cfg.Subscribe(m)
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(cfg, m)
|
||||
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
|
||||
it, err := strconv.Atoi(t)
|
||||
if err == nil {
|
||||
m.StartDeadlockDetector(time.Duration(it) * time.Second)
|
||||
}
|
||||
} else if !IsRelease || IsBeta {
|
||||
m.StartDeadlockDetector(20 * 60 * time.Second)
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -525,38 +625,46 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
mainSvc.Add(m)
|
||||
|
||||
// GUI
|
||||
|
||||
setupGUI(mainSvc, cfg, m, apiSub)
|
||||
|
||||
// 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)
|
||||
go listenConnect(myID, m, tlsCfg)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
|
||||
cfg.Subscribe(connectionSvc)
|
||||
mainSvc.Add(connectionSvc)
|
||||
|
||||
if cpuProfile {
|
||||
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
|
||||
@@ -587,16 +695,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()
|
||||
}
|
||||
@@ -611,18 +716,73 @@ func syncthingMain() {
|
||||
}
|
||||
}
|
||||
|
||||
events.Default.Log(events.StartupComplete, nil)
|
||||
events.Default.Log(events.StartupComplete, map[string]string{
|
||||
"myID": myID.String(),
|
||||
})
|
||||
go generatePingEvents()
|
||||
|
||||
cleanConfigDirectory()
|
||||
|
||||
code := <-stop
|
||||
|
||||
mainSvc.Stop()
|
||||
|
||||
l.Okln("Exiting")
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func setupGUI(cfg *config.Wrapper, m *model.Model) {
|
||||
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)
|
||||
if err != nil {
|
||||
l.Fatalln("Audit:", err)
|
||||
}
|
||||
|
||||
auditSvc := newAuditSvc(fd)
|
||||
mainSvc.Add(auditSvc)
|
||||
|
||||
// We wait for the audit service to fully start before we return, to
|
||||
// ensure we capture all events from the start.
|
||||
auditSvc.WaitForStart()
|
||||
|
||||
l.Infoln("Audit log in", auditFile)
|
||||
}
|
||||
|
||||
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription) {
|
||||
opts := cfg.Options()
|
||||
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
|
||||
|
||||
@@ -651,10 +811,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(myID, guiCfg, guiAssets, m, apiSub)
|
||||
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
|
||||
@@ -704,108 +867,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()
|
||||
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.UPnPRenewal > 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().UPnPLease*60)
|
||||
if err == nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func renewUPnP(port int) {
|
||||
for {
|
||||
opts := cfg.Options()
|
||||
time.Sleep(time.Duration(opts.UPnPRenewal) * time.Minute)
|
||||
|
||||
// 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()
|
||||
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.UPnPLease*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])
|
||||
}
|
||||
@@ -840,7 +901,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)
|
||||
}
|
||||
@@ -950,7 +1011,7 @@ func autoUpgrade() {
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
rel, err := upgrade.LatestGithubRelease(Version)
|
||||
rel, err := upgrade.LatestRelease(Version)
|
||||
if err == upgrade.ErrUpgradeUnsupported {
|
||||
events.Default.Unsubscribe(sub)
|
||||
return
|
||||
@@ -989,6 +1050,7 @@ func autoUpgrade() {
|
||||
func cleanConfigDirectory() {
|
||||
patterns := map[string]time.Duration{
|
||||
"panic-*.log": 7 * 24 * time.Hour, // keep panic logs for a week
|
||||
"audit-*.log": 7 * 24 * time.Hour, // keep audit logs for a week
|
||||
"index": 14 * 24 * time.Hour, // keep old index format for two weeks
|
||||
"config.xml.v*": 30 * 24 * time.Hour, // old config versions for a month
|
||||
"*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
|
||||
@@ -997,14 +1059,14 @@ 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
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
info, err := os.Lstat(file)
|
||||
info, err := osutil.Lstat(file)
|
||||
if err != nil {
|
||||
l.Infoln("Cleaning:", err)
|
||||
continue
|
||||
@@ -1020,3 +1082,18 @@ func cleanConfigDirectory() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkShortIDs verifies that the configuration won't result in duplicate
|
||||
// short ID:s; that is, that the devices in the cluster all have unique
|
||||
// initial 64 bits.
|
||||
func checkShortIDs(cfg *config.Wrapper) error {
|
||||
exists := make(map[uint64]protocol.DeviceID)
|
||||
for deviceID := range cfg.Devices() {
|
||||
shortID := deviceID.Short()
|
||||
if otherID, ok := exists[shortID]; ok {
|
||||
return fmt.Errorf("%v in conflict with %v", deviceID, otherID)
|
||||
}
|
||||
exists[shortID] = deviceID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import (
|
||||
)
|
||||
|
||||
func TestFolderErrors(t *testing.T) {
|
||||
// This test intentionally avoids starting the folders. If they are
|
||||
// started, they will perform an initial scan, which will create missing
|
||||
// folder markers and race with the stuff we do in the test.
|
||||
|
||||
fcfg := config.FolderConfiguration{
|
||||
ID: "folder",
|
||||
RawPath: "testdata/testfolder",
|
||||
@@ -29,10 +33,8 @@ func TestFolderErrors(t *testing.T) {
|
||||
})
|
||||
|
||||
for _, file := range []string{".stfolder", "testfolder/.stfolder", "testfolder"} {
|
||||
os.Remove("testdata/" + file)
|
||||
_, err := os.Stat("testdata/" + file)
|
||||
if err == nil {
|
||||
t.Error("Found unexpected file")
|
||||
if err := os.Remove("testdata/" + file); err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +59,12 @@ func TestFolderErrors(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
os.Remove("testdata/testfolder/.stfolder")
|
||||
os.Remove("testdata/testfolder/")
|
||||
if err := os.Remove("testdata/testfolder/.stfolder"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Remove("testdata/testfolder/"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Case 2 - new folder, marker created
|
||||
|
||||
@@ -79,7 +85,9 @@ func TestFolderErrors(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
os.Remove("testdata/.stfolder")
|
||||
if err := os.Remove("testdata/.stfolder"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Case 3 - Folder marker missing
|
||||
|
||||
@@ -91,7 +99,7 @@ func TestFolderErrors(t *testing.T) {
|
||||
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
|
||||
m.AddFolder(fcfg)
|
||||
|
||||
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
|
||||
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
|
||||
t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
|
||||
}
|
||||
|
||||
@@ -107,8 +115,12 @@ func TestFolderErrors(t *testing.T) {
|
||||
|
||||
// Case 4 - Folder path missing
|
||||
|
||||
os.Remove("testdata/testfolder/.stfolder")
|
||||
os.Remove("testdata/testfolder/")
|
||||
if err := os.Remove("testdata/testfolder/.stfolder"); err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Remove("testdata/testfolder"); err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fcfg.RawPath = "testdata/testfolder"
|
||||
cfg = config.Wrap("testdata/subfolder", config.Configuration{
|
||||
@@ -118,15 +130,17 @@ func TestFolderErrors(t *testing.T) {
|
||||
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
|
||||
m.AddFolder(fcfg)
|
||||
|
||||
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder path missing" {
|
||||
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder path missing" {
|
||||
t.Error("Incorrect error: Folder path missing !=", m.CheckFolderHealth("folder"))
|
||||
}
|
||||
|
||||
// Case 4.1 - recover after folder path missing
|
||||
|
||||
os.Mkdir("testdata/testfolder", 0700)
|
||||
if err := os.Mkdir("testdata/testfolder", 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
|
||||
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
|
||||
t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
|
||||
}
|
||||
|
||||
@@ -140,3 +154,27 @@ func TestFolderErrors(t *testing.T) {
|
||||
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortIDCheck(t *testing.T) {
|
||||
cfg := config.Wrap("/tmp/test", config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
|
||||
},
|
||||
})
|
||||
|
||||
if err := checkShortIDs(cfg); err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
|
||||
cfg = config.Wrap("/tmp/test", config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}},
|
||||
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same
|
||||
},
|
||||
})
|
||||
|
||||
if err := checkShortIDs(cfg); err == nil {
|
||||
t.Error("Should have gotten an error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@ import (
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"github.com/syncthing/syncthing/internal/sync"
|
||||
)
|
||||
|
||||
var (
|
||||
stdoutFirstLines []string // The first 10 lines of stdout
|
||||
stdoutLastLines []string // The last 50 lines of stdout
|
||||
stdoutMut sync.Mutex
|
||||
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
|
||||
@@ -163,14 +175,15 @@ func copyStderr(stderr io.ReadCloser, dst io.Writer) {
|
||||
dst.Write([]byte(line))
|
||||
|
||||
if strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:") {
|
||||
panicFd, err = os.Create(time.Now().Format(locations[locPanicLog]))
|
||||
panicFd, err = os.Create(timestampedLoc(locPanicLog))
|
||||
if err != nil {
|
||||
l.Warnln("Create panic log:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
||||
l.Warnln("Please create an issue at https://github.com/syncthing/syncthing/issues/ with the panic log attached")
|
||||
l.Warnln("Please check for existing issues with similar panic message at https://github.com/syncthing/syncthing/issues/")
|
||||
l.Warnln("If no issue with similar panic message exists, please create a new issue with the panic log attached")
|
||||
|
||||
stdoutMut.Lock()
|
||||
for _, line := range stdoutFirstLines {
|
||||
@@ -192,7 +205,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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,45 +7,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
"github.com/syncthing/syncthing/internal/model"
|
||||
"github.com/syncthing/syncthing/internal/sync"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -66,21 +72,25 @@ func (c *folderSummarySvc) listenForUpdates() {
|
||||
data := ev.Data.(map[string]interface{})
|
||||
folder := data["folder"].(string)
|
||||
|
||||
if ev.Type == events.StateChanged && data["to"].(string) == "idle" && data["from"].(string) == "syncing" {
|
||||
// The folder changed to idle from syncing. We should do an
|
||||
// immediate refresh to update the GUI. The send to
|
||||
// c.immediate must be nonblocking so that we can continue
|
||||
// handling events.
|
||||
switch ev.Type {
|
||||
case events.StateChanged:
|
||||
if data["to"].(string) == "idle" && data["from"].(string) == "syncing" {
|
||||
// The folder changed to idle from syncing. We should do an
|
||||
// immediate refresh to update the GUI. The send to
|
||||
// c.immediate must be nonblocking so that we can continue
|
||||
// handling events.
|
||||
|
||||
select {
|
||||
case c.immediate <- folder:
|
||||
c.foldersMut.Lock()
|
||||
delete(c.folders, folder)
|
||||
c.foldersMut.Unlock()
|
||||
select {
|
||||
case c.immediate <- folder:
|
||||
c.foldersMut.Lock()
|
||||
delete(c.folders, folder)
|
||||
c.foldersMut.Unlock()
|
||||
|
||||
default:
|
||||
default:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
default:
|
||||
// This folder needs to be refreshed whenever we do the next
|
||||
// refresh.
|
||||
|
||||
@@ -127,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
|
||||
}
|
||||
@@ -182,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
@@ -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
@@ -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]
|
||||
|
||||
@@ -4,9 +4,8 @@ background under Mac OS X.
|
||||
1. Install the `syncthing` binary in a directory called `bin` in your
|
||||
home directory.
|
||||
|
||||
2. Edit the `syncthing.plist` file in the two places that refer to your
|
||||
home directory; that is, replace `/Users/jb` with your actual home
|
||||
directory location.
|
||||
2. Edit the `syncthing.plist` by replacing `USERNAME` with your actual
|
||||
username such as `jb`.
|
||||
|
||||
3. Copy the `syncthing.plist` file to `~/Library/LaunchAgents`.
|
||||
|
||||
@@ -15,3 +14,6 @@ background under Mac OS X.
|
||||
|
||||
You probably want to turn off "Start Browser" among the settings to
|
||||
avoid it opening a browser window on each login.
|
||||
|
||||
Logs are in `~/Library/Logs/Syncthing.log` and, for crashes and exceptions,
|
||||
`~/Library/Logs/Syncthing-Error.log`.
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!--
|
||||
Make sure the "syncthing" executable is located in ~/bin/syncthing.
|
||||
Replace the string "USERNAME" in this file with your username, such as "jb".
|
||||
Copy this file to ~/Library/LaunchAgents/syncthing.plist.
|
||||
Execute "launchctl load ~/Library/LaunchAgents/syncthing.plist".
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
@@ -7,13 +13,13 @@
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Users/jb/bin/syncthing</string>
|
||||
<string>/Users/USERNAME/bin/syncthing</string>
|
||||
</array>
|
||||
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>HOME</key>
|
||||
<string>/Users/jb</string>
|
||||
<string>/Users/USERNAME</string>
|
||||
<key>STNORESTART</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
@@ -26,5 +32,11 @@
|
||||
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Users/USERNAME/Library/Logs/Syncthing.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Users/USERNAME/Library/Logs/Syncthing-Errors.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -239,7 +245,7 @@ ul.three-columns li, ul.two-columns li {
|
||||
|
||||
/** Footer nav on small devices **/
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@media (max-width: 991px) {
|
||||
body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -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,21 +1,29 @@
|
||||
{
|
||||
"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": "Адреси",
|
||||
"Advanced": "Допълнителни",
|
||||
"Advanced Configuration": "Допълнителни настройки",
|
||||
"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 компютъра също ще бъдат добавени към този компютър.",
|
||||
"Automatic upgrades": "Автоматични ъпдейти",
|
||||
"Be careful!": "Внимавай!",
|
||||
"Bugs": "Бъгове",
|
||||
"CPU Utilization": "Натоварване на Процесора",
|
||||
"Changelog": "Сипъск с промени",
|
||||
"Clean out after": "Изчисти след",
|
||||
"Close": "Затвори",
|
||||
"Command": "Команда",
|
||||
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
|
||||
@@ -25,6 +33,7 @@
|
||||
"Copied from original": "Копиран от оригинала",
|
||||
"Copyright © 2015 the following Contributors:": "Правата запазени © 2015 Сътрудници:",
|
||||
"Delete": "Изтрий",
|
||||
"Deleted": "Изтрито",
|
||||
"Device ID": "Идентификатор на устройство",
|
||||
"Device Identification": "Идентификация на устройство",
|
||||
"Device Name": "Име на устройство",
|
||||
@@ -44,14 +53,19 @@
|
||||
"Enter ignore patterns, one per line.": "Добави шаблони за игнориране, по един на ред.",
|
||||
"Error": "Грешка",
|
||||
"External File Versioning": "Външно упраление на версиите",
|
||||
"Failed Items": "Неуспешни",
|
||||
"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.": "Файловете биват преместени в .stversions папка, когато са заменен или изтрити от 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": "Папка",
|
||||
"Folder ID": "Идентификатор на папка",
|
||||
"Folder Master": "Главна папка",
|
||||
"Folder Path": "Път до папката",
|
||||
"Folders": "Папки",
|
||||
"GUI": "Потребителски интефейс",
|
||||
"GUI Authentication Password": "Парола за Потребителския Интерфейс",
|
||||
"GUI Authentication User": "Потребител за Потребителския Интерфейс",
|
||||
"GUI Listen Addresses": "Адрес за Свързване с Потребителския Интерфейс",
|
||||
@@ -59,18 +73,23 @@
|
||||
"Global Discovery": "Глобавно Откриване",
|
||||
"Global Discovery Server": "Сървър за Глобално Откриване",
|
||||
"Global State": "Глобално състояние",
|
||||
"Help": "Помощ",
|
||||
"Ignore": "Игнорирай",
|
||||
"Ignore Patterns": "Шаблони за Игнориране",
|
||||
"Ignore Permissions": "Игнорирай Права за Достъп",
|
||||
"Incoming Rate Limit (KiB/s)": "Входящ Лимит на Скоростта (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправилни настройки могат да повредят съдържанието на папката и да попречат на по-нататъшно синхронизиране.",
|
||||
"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": "Локално състояние",
|
||||
"Local State (Total)": "Локално Състояние (Общо)",
|
||||
"Major Upgrade": "Основно Обновяване",
|
||||
"Maximum Age": "Максимална Възраст",
|
||||
"Metadata Only": "Само мета информация",
|
||||
"Move to top of queue": "Премести в началото на опашката",
|
||||
@@ -78,22 +97,28 @@
|
||||
"Never": "Никога",
|
||||
"New Device": "Ново устройство",
|
||||
"New Folder": "Нова папка",
|
||||
"Newest First": "Най-новите първо",
|
||||
"No": "Не",
|
||||
"No File Versioning": "Няма Файлови Версии",
|
||||
"Notice": "Известие",
|
||||
"OK": "ОК",
|
||||
"Off": "Изключено",
|
||||
"Out Of Sync": "Не Синхронизиран",
|
||||
"Oldest First": "Най-старите първо",
|
||||
"Options": "Настройки",
|
||||
"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 +145,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 +171,28 @@
|
||||
"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 following items could not be synchronized.": "Следните не могат да бъдат синхронизирани.",
|
||||
"The maximum age must be a number and cannot be blank.": "Максималната възраст трябва да е число и не може д ае празна.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максималното време да се пазят весрсии (в дни, сложи 0, за да пазиш версии завинаги).",
|
||||
"The number of days must be a number and cannot be blank.": "Броят дни трябва да бъде число и неможе да бъде празно.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Броят дни за запазване на файловете в кошчето. Нула значи завинаги.",
|
||||
"The number of old versions to keep, per file.": "Броят стари версии, които да бъдат пазени за всеки файл.",
|
||||
"The number of versions must be a number and cannot be blank.": "Броят версии трябва да бъде число и не може да бъде празно.",
|
||||
"The path cannot be blank.": "Пътят неможе да бъде празен.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Ще бъдат спрени и автоматично синхронизирани, когато грешката бъде оправена.",
|
||||
"This is a major version upgrade.": "Това е нова основна версия.",
|
||||
"Trash Can File Versioning": "Версии на файлове в кошчето",
|
||||
"Unknown": "Неясен",
|
||||
"Unshared": "Споделянето прекратено",
|
||||
"Unused": "Неизползван",
|
||||
"Up to Date": "Актуален",
|
||||
"Updated": "Обновено",
|
||||
"Upgrade": "Обнови",
|
||||
"Upgrade To {%version%}": "Обновен До {{version}}",
|
||||
"Upgrading": "Обновяване",
|
||||
"Upload Rate": "Скорост на Качване",
|
||||
"Uptime": "Работи вече",
|
||||
"Use HTTPS for GUI": "Използвай HTTPS за Потребителския Интерфейс",
|
||||
"Version": "Версия",
|
||||
"Versions Path": "Път до Версиите",
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
{
|
||||
"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",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"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",
|
||||
"Be careful!": "Be careful!",
|
||||
"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",
|
||||
"Deleted": "Deleted",
|
||||
"Device ID": "ID del dispositiu",
|
||||
"Device Identification": "Identificació del dispositiu",
|
||||
"Device Name": "Nom del dispositiu",
|
||||
@@ -41,17 +50,22 @@
|
||||
"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",
|
||||
"Failed Items": "Failed Items",
|
||||
"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": "Folder",
|
||||
"Folder ID": "ID de carpeta",
|
||||
"Folder Master": "Carpeta mestre",
|
||||
"Folder Path": "Camí de carpeta",
|
||||
"Folders": "Carpetes",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Contrasenya d'autenticació GUI",
|
||||
"GUI Authentication User": "Usuari d'autenticació GUI",
|
||||
"GUI Listen Addresses": "Adreça d'escolta del GUI",
|
||||
@@ -59,43 +73,54 @@
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Tasca Límit d'Entrada (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"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",
|
||||
"Out Of Sync": "Fora de la Sincronització",
|
||||
"Off": "Desactivar",
|
||||
"Oldest First": "Més antic primer",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"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 +145,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,32 +158,41 @@
|
||||
"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.",
|
||||
"The folder path cannot be blank.": "El camí a la carpeta no pot estar en blanc.",
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "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",
|
||||
"Updated": "Updated",
|
||||
"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",
|
||||
|
||||
207
gui/assets/lang/lang-ca@valencia.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "Un nombre negatiu de dies no té sentit.",
|
||||
"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": "Accions",
|
||||
"Add": "Afegir",
|
||||
"Add Device": "Afegir dispositiu",
|
||||
"Add Folder": "Afegir carpeta",
|
||||
"Add new folder?": "Afegir nova carpeta?",
|
||||
"Address": "Direcció",
|
||||
"Addresses": "Direccions",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"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",
|
||||
"Be careful!": "Be careful!",
|
||||
"Bugs": "Errors (Bugs)",
|
||||
"CPU Utilization": "Utilització de la CPU",
|
||||
"Changelog": "Registre de canvis",
|
||||
"Clean out after": "Netejar després de",
|
||||
"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",
|
||||
"Deleted": "Esborrat",
|
||||
"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",
|
||||
"Failed Items": "Failed Items",
|
||||
"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.": "Els arxius es menejen a la carpeta .stversions quant són substituïts o esborrats per 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": "Folder",
|
||||
"Folder ID": "ID de carpeta",
|
||||
"Folder Master": "Carpeta principal",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
"Folders": "Carpetes",
|
||||
"GUI": "GUI",
|
||||
"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": "Ajuda",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrons a ignorar",
|
||||
"Ignore Permissions": "Permisos a ignorar",
|
||||
"Incoming Rate Limit (KiB/s)": "Límit de descàrrega (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Estat Local (Total)",
|
||||
"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",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.": "El nombre de dies deu ser un nombre i no pot estar en blanc.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "El nombre de dies per a mantindre els arxius a la paperera. Cero vol dir \"per a sempre\".",
|
||||
"The number of old versions to keep, per file.": "El nombre de versions antigues per a guardar, per cada fitxer.",
|
||||
"The number of versions must be a number and cannot be blank.": "El nombre de versions deu ser un nombre i no pot estar buit.",
|
||||
"The path cannot be blank.": "La ruta no pot estar buida.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "L'interval de reescaneig deu ser un nombre positiu de segons.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "Aquesta és una actualització important de la versió.",
|
||||
"Trash Can File Versioning": "Versionat d'arxius de la paperera",
|
||||
"Unknown": "Desconegut",
|
||||
"Unshared": "No compartit",
|
||||
"Unused": "No utilitzat",
|
||||
"Up to Date": "Actualitzat",
|
||||
"Updated": "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,21 +1,29 @@
|
||||
{
|
||||
"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ář",
|
||||
"Add new folder?": "Přidat nový adresář?",
|
||||
"Address": "Adresa",
|
||||
"Addresses": "Adresy",
|
||||
"Advanced": "Pokročilé",
|
||||
"Advanced Configuration": "Pokročilá nastavení",
|
||||
"All Data": "Všechna data",
|
||||
"Allow Anonymous Usage Reporting?": "Povolit anonymní hlášení o používání?",
|
||||
"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": "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",
|
||||
"Be careful!": "Pozor!",
|
||||
"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",
|
||||
@@ -23,8 +31,9 @@
|
||||
"Connection Error": "Chyba připojení",
|
||||
"Copied from elsewhere": "Zkopírováno odjinud",
|
||||
"Copied from original": "Zkopírováno z originálu",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 následující přispěvatelé:",
|
||||
"Delete": "Smazat",
|
||||
"Deleted": "Smazáno",
|
||||
"Device ID": "ID přístroje",
|
||||
"Device Identification": "Identifikace přístroje",
|
||||
"Device Name": "Jméno přístroje",
|
||||
@@ -44,14 +53,19 @@
|
||||
"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 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.",
|
||||
"Failed Items": "Selhalo",
|
||||
"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": "Adresář",
|
||||
"Folder ID": "ID adresáře",
|
||||
"Folder Master": "Master adresář",
|
||||
"Folder Path": "Cesta k adresáři",
|
||||
"Folders": "Adresáře",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Přihlašovací heslo pro GUI",
|
||||
"GUI Authentication User": "Přihlašovací jméno pro GUI",
|
||||
"GUI Listen Addresses": "Adresa naslouchání GUI",
|
||||
@@ -59,18 +73,23 @@
|
||||
"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í",
|
||||
"Incoming Rate Limit (KiB/s)": "Omezení příchozí rychlosti (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Nesprávné nastavení může poškodit obsah Vašich složek a učinit Syncthing nefunkční.",
|
||||
"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",
|
||||
"Local State (Total)": "Místní status (Celkem)",
|
||||
"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 +97,28 @@
|
||||
"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",
|
||||
"Out Of Sync": "Nesesynchronizováno",
|
||||
"Oldest First": "Od nejstaršího",
|
||||
"Options": "Nastavení",
|
||||
"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 +143,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",
|
||||
@@ -132,7 +158,7 @@
|
||||
"Syncthing is restarting.": "Syncthing se restartuje.",
|
||||
"Syncthing is upgrading.": "Syncthing se aktualizuje.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing se zdá být nefunkční, nebo je problém s připojením k Internetu. Opakuji...",
|
||||
"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 má nejspíše problém s provedením vašeho požadavku. Pokud problém přetrvává, obnovte stránku v prohlížeči nebo restartujte Syncthing.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Souhrnné statistiky jsou veřejně dostupné na {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurace byla uložena, ale není aktivována. Pro aktivaci nové konfigurace je třeba restartovat Syncthing.",
|
||||
"The device ID cannot be blank.": "ID přístroje nemůže být prázdné.",
|
||||
@@ -145,22 +171,31 @@
|
||||
"The folder ID must be unique.": "ID adresáře musí být unikátní.",
|
||||
"The folder path cannot be blank.": "Cesta k adresáři nemůže být prázdná.",
|
||||
"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 following items could not be synchronized.": "Následující položky nemohly být synchronizovány.",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Nové pokusy o synchronizaci budou probíhat automaticky a položky budou synchronizovány jakmile bude chyba odstraněna.",
|
||||
"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í",
|
||||
"Updated": "Aktualizováno",
|
||||
"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,30 +1,39 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "Eine negative Anzahl von Tagen ergibt keinen Sinn.",
|
||||
"A new major version may not be compatible with previous versions.": "Die neue Hauptversion ist evtl. nicht mit vorherigen Versionen kompatibel.",
|
||||
"API Key": "API-Schlüssel",
|
||||
"About": "Über Syncthing",
|
||||
"Actions": "Aktionen",
|
||||
"Add": "Hinzufügen",
|
||||
"Add Device": "Gerät hinzufügen",
|
||||
"Add Folder": "Verzeichnis hinzufügen",
|
||||
"Add new folder?": "Neues Verzeichnis hinzufügen?",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adressen",
|
||||
"Advanced": "Erweitert",
|
||||
"Advanced Configuration": "Erweiterte Konfiguration",
|
||||
"All Data": "Alle Daten",
|
||||
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
|
||||
"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": "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",
|
||||
"Automatic upgrades": "automatische Updates",
|
||||
"Be careful!": "Vorsicht!",
|
||||
"Bugs": "Fehler",
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Changelog": "Versionsinfo",
|
||||
"Changelog": "Änderungsprotokoll",
|
||||
"Clean out after": "Aufräumen nach",
|
||||
"Close": "Schließen",
|
||||
"Command": "Command",
|
||||
"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 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 die folgenden Unterstützer:",
|
||||
"Delete": "Löschen",
|
||||
"Deleted": "gelöscht",
|
||||
"Device ID": "Geräte ID",
|
||||
"Device Identification": "Gerät Identifikation",
|
||||
"Device Name": "Gerätename",
|
||||
@@ -38,39 +47,49 @@
|
||||
"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": "External File Versioning",
|
||||
"External File Versioning": "Externe Dateiversionierung",
|
||||
"Failed Items": "Fehlgeschlagene Dateien",
|
||||
"File Pull Order": "Dateiübertragungsreihenfolge",
|
||||
"File Versioning": "Dateiversionierung",
|
||||
"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.": "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.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
|
||||
"Folder": "Verzeichnis",
|
||||
"Folder ID": "Verzeichnis ID",
|
||||
"Folder Master": "Keine Veränderungen zulassen",
|
||||
"Folder Master": "Master Verzeichnis - schreibgeschützt",
|
||||
"Folder Path": "Verzeichnispfad",
|
||||
"Folders": "Verzeichnisse",
|
||||
"GUI": "GUI",
|
||||
"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": "Globale Gerätesuche",
|
||||
"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) (KB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Eine falsche Konfiguration kann den Verzeichnisinhalt beschädigen und Syncthing in einen unausführbaren Zustand versetzen.",
|
||||
"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ößte zuerst",
|
||||
"Last File Received": "Letzte empfangene Datei ",
|
||||
"Last seen": "Zuletzt online",
|
||||
"Later": "Später",
|
||||
"Local Discovery": "Lokale Auffindung",
|
||||
"Local Discovery": "Lokale Gerätesuche",
|
||||
"Local State": "Lokaler Status",
|
||||
"Local State (Total)": "Lokaler Status (total)",
|
||||
"Major Upgrade": "Hauptversionsupgrade",
|
||||
"Maximum Age": "Höchstalter",
|
||||
"Metadata Only": "Nur Metadaten",
|
||||
"Move to top of queue": "An den Anfang der Warteschlange setzen",
|
||||
@@ -78,31 +97,37 @@
|
||||
"Never": "Nie",
|
||||
"New Device": "Neues Gerät",
|
||||
"New Folder": "Neues Verzeichnis",
|
||||
"Newest First": "Neueste zuerst",
|
||||
"No": "Nein",
|
||||
"No File Versioning": "Keine Dateiversionierung",
|
||||
"Notice": "Hinweis",
|
||||
"OK": "OK",
|
||||
"Off": "Aus",
|
||||
"Out Of Sync": "Nicht synchronisiert",
|
||||
"Oldest First": "Älteste zuerst",
|
||||
"Options": "Optionen",
|
||||
"Out of Sync": "Nicht synchronisiert",
|
||||
"Out of Sync Items": "Nicht synchronisierte Objekte",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ausgehendes Datenratelimit (KiB/s)",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limit Datenrate (ausgehend) (KB/s)",
|
||||
"Override Changes": "Änderungen überschreiben",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem die Versionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
|
||||
"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 +139,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",
|
||||
@@ -132,38 +158,47 @@
|
||||
"Syncthing is restarting.": "Syncthing wird neu gestartet",
|
||||
"Syncthing is upgrading.": "Syncthing wird aktualisiert",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "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.": "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.": "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.": "Der erste Kommandozeilenparameter ist der Verzeichnis-Pfad und der zweite Parameter ist der relative Pfad in diesem Verzeichnis.",
|
||||
"The folder ID cannot be blank.": "Die Verzeichnis ID darf nicht leer sein.",
|
||||
"The folder ID must be 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 following items could not be synchronized.": "Die folgenden Dateien konnten nicht synchronisiert werden.",
|
||||
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
|
||||
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Dauer in Tagen für welche die Dateien aufgehoben werden sollen. 0 bedeutet für immer.",
|
||||
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
|
||||
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Sie werden automatisch heruntergeladen und werden synchronisiert, wenn der Fehler behoben wurde.",
|
||||
"This is a major version upgrade.": "Dies ist eine neue Hauptversion.",
|
||||
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
|
||||
"Unknown": "Unbekannt",
|
||||
"Unshared": "Ungeteilt",
|
||||
"Unused": "Ungenutzt",
|
||||
"Up to Date": "Aktuell",
|
||||
"Updated": "aktualisiert",
|
||||
"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,30 +1,39 @@
|
||||
{
|
||||
"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": "Ενέργειες",
|
||||
"Add": "Προσθήκη",
|
||||
"Add Device": "Προσθήκη συσκευής",
|
||||
"Add Folder": "Προσθήκη φακέλου",
|
||||
"Add new folder?": "Προσθήκη νέου φακέλου;",
|
||||
"Address": "Διεύθυνση",
|
||||
"Addresses": "Διευθύνσεις",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"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": "Αυτόματη αναβάθμιση",
|
||||
"Be careful!": "Be careful!",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
|
||||
"Changelog": "Πληροφορίες εκδόσεων",
|
||||
"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": "Διαγραφή",
|
||||
"Deleted": "Διαγραμμένα",
|
||||
"Device ID": "Ταυτότητα συσκευής",
|
||||
"Device Identification": "Ταυτότητα συσκευής",
|
||||
"Device Name": "Όνομα συσκευής",
|
||||
@@ -43,34 +52,44 @@
|
||||
"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": "Εξωτερική τήρηση εκδόσεων",
|
||||
"Failed Items": "Failed Items",
|
||||
"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.": "Τα αρχεία μετακινούνται στον φάκελο .stversions, όταν αντικαθίστανται ή διαγράφονται από το 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": "Folder",
|
||||
"Folder ID": "Ταυτότητα φακέλου",
|
||||
"Folder Master": "Να μην επιτρέπονται αλλαγές",
|
||||
"Folder Path": "Μονοπάτι φακέλου",
|
||||
"Folders": "Φάκελοι",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Κωδικός για την πρόσβαση στη διεπαφή",
|
||||
"GUI Authentication User": "Χρηστώνυμο για την πρόσβαση στη διεπαφή",
|
||||
"GUI Listen Addresses": "Διευθύνσεις από τις οποίες θα είναι προσβάσιμη η διεπαφή",
|
||||
"Generate": "Δημιουργία",
|
||||
"Global Discovery": "Να μπορεί να ανευρεθεί καθολικά (από παντού)",
|
||||
"Global Discovery": "Καθολική ανεύρεση",
|
||||
"Global Discovery Server": "Διακομιστής καθολικής ανεύρεσης κόμβου",
|
||||
"Global State": "Καθολική κατάσταση",
|
||||
"Help": "Βοήθεια",
|
||||
"Ignore": "Αγνόησε",
|
||||
"Ignore Patterns": "Πρότυπο για αγνόηση",
|
||||
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
|
||||
"Incoming Rate Limit (KiB/s)": "Περιορισμός ταχύτητας λήψης (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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": "Τοπική κατάσταση",
|
||||
"Local State (Total)": "Τοπική κατάσταση (συνολικά)",
|
||||
"Major Upgrade": "Σημαντική αναβάθμιση",
|
||||
"Maximum Age": "Μέγιστη ηλικία",
|
||||
"Metadata Only": "Μόνο μεταδεδομένα",
|
||||
"Move to top of queue": "Μεταφορά στην αρχή της λίστας",
|
||||
@@ -78,22 +97,28 @@
|
||||
"Never": "Ποτέ",
|
||||
"New Device": "Νέα συσκευή",
|
||||
"New Folder": "Νέος φάκελος",
|
||||
"Newest First": "Το νεότερο πρώτα",
|
||||
"No": "Όχι",
|
||||
"No File Versioning": "Να μην τηρούνται εκδόσεις",
|
||||
"Notice": "Σημείωση",
|
||||
"OK": "OK",
|
||||
"Off": "Απενεργοποιημένο",
|
||||
"Out Of Sync": "Μη συγχρονισμένα",
|
||||
"Oldest First": "Το παλιότερο πρώτα",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "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 +137,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 +145,7 @@
|
||||
"Shutdown Complete": "Πλήρης απενεργοποίηση",
|
||||
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
|
||||
"Single level wildcard (matches within a directory only)": "Τελεστής μπαλαντέρ (*) για ένα επίπεδο (χρησιμοποιείται για έναν φάκελο μόνο)",
|
||||
"Smallest First": "Το μικρότερο πρώτα",
|
||||
"Source Code": "Πηγαίος κώδικας",
|
||||
"Staggered File Versioning": "Να τηρούνται κλιμακούμενες εκδόσεις",
|
||||
"Start Browser": "Εκκίνηση προγράμματος περιήγησης",
|
||||
@@ -132,32 +158,41 @@
|
||||
"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.": "Η ταυτότητα του φακέλου πρέπει να είναι μοναδική.",
|
||||
"The folder path cannot be blank.": "Το μονοπάτι του φακέλου δεν μπορεί να είναι κενό.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Θα χρησιμοποιούνται τα εξής διαστήματα: Την πρώτη ώρα θα τηρείται μια έκδοση κάθε 30 δευτερόλεπτα. Την πρώτη ημέρα, μια έκδοση κάθε μια ώρα. Τις πρώτες 30 ημέρες, μία έκδοση κάθε ημέρα. Από εκεί και έπειτα μέχρι τη μέγιστη ηλικία, θα τηρείται μια έκδοση κάθε εβδομάδα.",
|
||||
"The following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"The maximum age must be a number and cannot be blank.": "Η μέγιστη ηλικία πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Η μέγιστη ηλικία παλιότερων εκδόσεων (σε ημέρες, αν δώσεις 0 οι παλιότερες εκδόσεις θα διατηρούνται για πάντα).",
|
||||
"The number of days must be a number and cannot be blank.": "Ο αριθμός ημερών πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Ο αριθμός ημερών για τήρηση αρχείων στον Κάδο. Αριθμός μηδέν σημαίνει τήρηση για πάντα.",
|
||||
"The number of old versions to keep, per file.": "Πόσες παλιότερες εκδόσεις θα διατηρούνται, ανά αρχείο.",
|
||||
"The number of versions must be a number and cannot be blank.": "Ο αριθμός εκδόσεων πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
|
||||
"The path cannot be blank.": "Το μονοπάτι δεν μπορεί να είναι κενό.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Ο χρόνος επανελέγχου για αλλαγές είναι σε δευτερόλεπτα (δηλ. θετικός αριθμός).",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "Αυτή είναι μιας σημαντική αναβάθμιση.",
|
||||
"Trash Can File Versioning": "Ο Κάδος μπορεί να τηρεί εκδόσεις",
|
||||
"Unknown": "Άγνωστο",
|
||||
"Unshared": "Δε μοιράζεται",
|
||||
"Unused": "Δε χρησιμοποιείται",
|
||||
"Up to Date": "Ενημερωμένος",
|
||||
"Updated": "Ενημερωμένο",
|
||||
"Upgrade": "Αναβάθμιση",
|
||||
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
|
||||
"Upgrading": "Αναβάθμιση",
|
||||
"Upload Rate": "Ταχύτητα ανεβάσματος",
|
||||
"Uptime": "Χρόνος απρόσκοπτης λειτουργίας",
|
||||
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
|
||||
"Version": "Έκδοση",
|
||||
"Versions Path": "Φάκελος τήρησης εκδόσεων",
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
{
|
||||
"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",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Address": "Address",
|
||||
"Addresses": "Addresses",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"All Data": "All Data",
|
||||
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
|
||||
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
|
||||
"Automatic upgrades": "Automatic upgrades",
|
||||
"Be careful!": "Be careful!",
|
||||
"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",
|
||||
@@ -25,6 +33,7 @@
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Delete": "Delete",
|
||||
"Deleted": "Deleted",
|
||||
"Device ID": "Device ID",
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
@@ -44,14 +53,19 @@
|
||||
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"Failed Items": "Failed Items",
|
||||
"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": "Folder",
|
||||
"Folder ID": "Folder ID",
|
||||
"Folder Master": "Folder Master",
|
||||
"Folder Path": "Folder Path",
|
||||
"Folders": "Folders",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI Authentication Password",
|
||||
"GUI Authentication User": "GUI Authentication User",
|
||||
"GUI Listen Addresses": "GUI Listen Addresses",
|
||||
@@ -59,18 +73,23 @@
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
@@ -78,22 +97,28 @@
|
||||
"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",
|
||||
"Out Of Sync": "Out of Sync",
|
||||
"Oldest First": "Oldest First",
|
||||
"Options": "Options",
|
||||
"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 +145,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",
|
||||
@@ -145,19 +171,28 @@
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"Trash Can File Versioning": "Rubbish Bin File Versioning",
|
||||
"Unknown": "Unknown",
|
||||
"Unshared": "Unshared",
|
||||
"Unused": "Unused",
|
||||
"Up to Date": "Up to Date",
|
||||
"Updated": "Updated",
|
||||
"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,21 +1,29 @@
|
||||
{
|
||||
"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",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Address": "Address",
|
||||
"Addresses": "Addresses",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"All Data": "All Data",
|
||||
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
|
||||
"Alphabetic": "Alphabetic",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
|
||||
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
|
||||
"Automatic upgrades": "Automatic upgrades",
|
||||
"Be careful!": "Be careful!",
|
||||
"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",
|
||||
@@ -25,6 +33,7 @@
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Delete": "Delete",
|
||||
"Deleted": "Deleted",
|
||||
"Device ID": "Device ID",
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
@@ -44,14 +53,19 @@
|
||||
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
|
||||
"Error": "Error",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"Failed Items": "Failed Items",
|
||||
"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": "Folder",
|
||||
"Folder ID": "Folder ID",
|
||||
"Folder Master": "Folder Master",
|
||||
"Folder Path": "Folder Path",
|
||||
"Folders": "Folders",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI Authentication Password",
|
||||
"GUI Authentication User": "GUI Authentication User",
|
||||
"GUI Listen Addresses": "GUI Listen Addresses",
|
||||
@@ -59,18 +73,23 @@
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
@@ -78,22 +97,28 @@
|
||||
"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",
|
||||
"Out Of Sync": "Out Of Sync",
|
||||
"Oldest First": "Oldest First",
|
||||
"Options": "Options",
|
||||
"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 Utilization",
|
||||
"Random": "Random",
|
||||
"Release Notes": "Release Notes",
|
||||
"Rescan": "Rescan",
|
||||
"Rescan All": "Rescan All",
|
||||
"Rescan Interval": "Rescan Interval",
|
||||
@@ -120,6 +145,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",
|
||||
@@ -145,16 +171,24 @@
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "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",
|
||||
"Updated": "Updated",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
||||
"Upgrading": "Upgrading",
|
||||
"Upload Rate": "Upload Rate",
|
||||
|
||||
207
gui/assets/lang/lang-es-ES.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "Un número negativo de días no tiene sentido.",
|
||||
"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": "Acciones",
|
||||
"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",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"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",
|
||||
"Be careful!": "Be careful!",
|
||||
"Bugs": "Errores (bugs)",
|
||||
"CPU Utilization": "Uso de CPU",
|
||||
"Changelog": "Informe de cambios",
|
||||
"Clean out after": "Limpiar tras",
|
||||
"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",
|
||||
"Deleted": "Borrado",
|
||||
"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",
|
||||
"Failed Items": "Failed Items",
|
||||
"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.": "Los archivos serán movidos a la carpeta .stversions cuando sean reemplazados o borrados por 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": "Folder",
|
||||
"Folder ID": "ID de carpeta",
|
||||
"Folder Master": "Carpeta principal",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
"Folders": "Carpetas",
|
||||
"GUI": "GUI",
|
||||
"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": "Ayuda",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrones a ignorar",
|
||||
"Ignore Permissions": "Permisos a ignorar",
|
||||
"Incoming Rate Limit (KiB/s)": "Límite de descarga (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Estado Local (Total)",
|
||||
"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",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.": "El número de días debe ser un número y no puede estar en blanco.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "El número de días para mantener los archivos en la papelera. Cero significa \"para siempre\".",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "Hay una actualización importante.",
|
||||
"Trash Can File Versioning": "Versionado de archivos de la papelera",
|
||||
"Unknown": "Desconocido",
|
||||
"Unshared": "No compartido",
|
||||
"Unused": "No usado",
|
||||
"Up to Date": "Actualizado",
|
||||
"Updated": "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,21 +1,29 @@
|
||||
{
|
||||
"A negative number of days doesn't make sense.": "Un número negativo no tiene sentido",
|
||||
"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",
|
||||
"Add new folder?": "¿Agregar nuevo repositorio?",
|
||||
"Address": "Dirección",
|
||||
"Addresses": "Direcciones",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"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.",
|
||||
"Automatic upgrades": "Actualizaciones automáticas",
|
||||
"Be careful!": "Be careful!",
|
||||
"Bugs": "Errores",
|
||||
"CPU Utilization": "Uso de CPU",
|
||||
"Changelog": "Registro de cambios",
|
||||
"Clean out after": "Limpiar después",
|
||||
"Close": "Cerrar",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
|
||||
@@ -25,6 +33,7 @@
|
||||
"Copied from original": "Copiado del original",
|
||||
"Copyright © 2015 the following Contributors:": "Derechos de autor © 2015 los siguientes colaboradores:",
|
||||
"Delete": "Suprimir",
|
||||
"Deleted": "Suprimido",
|
||||
"Device ID": "ID del dispositivo",
|
||||
"Device Identification": "Identificación del dispositivo",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
@@ -44,14 +53,19 @@
|
||||
"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",
|
||||
"Failed Items": "Failed Items",
|
||||
"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": "Folder",
|
||||
"Folder ID": "ID del repositorio",
|
||||
"Folder Master": "Repositorio maestro",
|
||||
"Folder Path": "Ruta del repositorio",
|
||||
"Folders": "Repositorios",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Contraseña de autenticación de la GUI",
|
||||
"GUI Authentication User": "Usuario de la GUI",
|
||||
"GUI Listen Addresses": "Direcciones de escucha para la GUI.",
|
||||
@@ -59,18 +73,23 @@
|
||||
"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",
|
||||
"Incoming Rate Limit (KiB/s)": "Límite de velocidad de entrada (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Estado local (total)",
|
||||
"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 +97,28 @@
|
||||
"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",
|
||||
"Out Of Sync": "Fuera de sincronización",
|
||||
"Oldest First": "Antiguo primero",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"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 +145,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",
|
||||
@@ -145,19 +171,28 @@
|
||||
"The folder ID must be unique.": "La ID del repositorio debe ser única.",
|
||||
"The folder path cannot be blank.": "La ruta del repositorio no puede estar vacía.",
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.": "El número de días debe ser un número y no puede estar vacío.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "El tiempo máximo para mantener un archivo en el cubo de basura (en días, establece en 0 para mantener versiones para siempre).",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "Esta es una actualización de version mayor.",
|
||||
"Trash Can File Versioning": "Versiones como cubo de basura",
|
||||
"Unknown": "Desconocido",
|
||||
"Unshared": "No compartido",
|
||||
"Unused": "No utilizado",
|
||||
"Up to Date": "Actualizado",
|
||||
"Updated": "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",
|
||||
|
||||
207
gui/assets/lang/lang-fi.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"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",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"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",
|
||||
"Be careful!": "Be careful!",
|
||||
"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",
|
||||
"Deleted": "Deleted",
|
||||
"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",
|
||||
"Failed Items": "Failed Items",
|
||||
"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": "Folder",
|
||||
"Folder ID": "Kansion ID",
|
||||
"Folder Master": "Hallitsijakansio",
|
||||
"Folder Path": "Kansion polku",
|
||||
"Folders": "Kansiot",
|
||||
"GUI": "GUI",
|
||||
"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)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"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",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"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 following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"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.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This is a major version upgrade.": "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",
|
||||
"Updated": "Updated",
|
||||
"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}}\"."
|
||||
}
|
||||