Compare commits

...

54 Commits

Author SHA1 Message Date
Jakob Borg
a9f0659f2f lib/fs: Handle deduplicated files on NTFS (fixes #1845)
These files always have the symlink bit set, because they are reparse
points. Nonetheless they are not symlinks, and Lstat reports a size for
them. We use this fact to disambiguate, and hope fervently that nothing
else matches this description so it comes back to bite us...

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4622
2017-12-29 21:23:06 +00:00
Lars K.W. Gohlke
9988044bbe lib/model: Cleanup conditions
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4621
2017-12-29 13:14:39 +00:00
Jakob Borg
c24bf7ea55 vendor: Update everything
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4620
2017-12-29 11:38:00 +00:00
Jakob Borg
1296a22069 vendor: Update golang.org/x/sys/... (fixes #4615)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4619
2017-12-29 09:25:24 +00:00
Audrius Butkevicius
72172d853c vendor: Move back to upstream KCP (fixes #4407)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4614
2017-12-27 11:33:12 +00:00
Jakob Borg
5a05d9b867 gui, man: Update docs & translations 2017-12-27 07:45:19 +01:00
Simon Frei
841205dbfe lib/model: Check for invalid filenames after ignore patterns
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4607
LGTM: calmh, AudriusButkevicius
2017-12-25 17:54:34 +00:00
Audrius Butkevicius
c58b383b6d gui: Add debug tab to settings (ref #2644)
Just because there are a ton of people struggling to set env vars.
Perhaps this should live in advanced settings, and perhaps we should have a button to view the log.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4604
LGTM: calmh, imsodin
2017-12-24 22:26:05 +00:00
Audrius Butkevicius
2547a29dd7 lib/connections: Don't close nil connections (fixes #4605) 2017-12-18 14:40:51 +00:00
Simon Frei
8fa2b7765a gui, lib/model: Display list of files needed by remote (fixes #4369)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4559
LGTM: AudriusButkevicius, calmh
2017-12-15 20:01:56 +00:00
Jakob Borg
c7522063b3 lib/model: Don't leak fd when truncate fails (fixes #4593)
Also attempt to handle this nicer by ignoring the truncate failure when
it doesn't matter, and recover by deleting the temp file when it does.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4594
2017-12-14 10:42:40 +00:00
Jakob Borg
d1d967f0cf lib/db: Keep folder meta data persistently in db (fixes #4400)
This keeps the data we need about sequence numbers and object counts
persistently in the database. The sizeTracker is expanded into a
metadataTracker than handled multiple folders, and the Counts struct is
made protobuf serializable. It gains a Sequence field to assist in
tracking that as well, and a collection of Counts become a CountsSet
(for serialization purposes).

The initial database scan is also a consistency check of the global
entries. This shouldn't strictly be necessary. Nonetheless I added a
created timestamp to the metadata and set a variable to compare against
that. When the time since the metadata creation is old enough, we drop
the metadata and rebuild from scratch like we used to, while also
consistency checking.

A new environment variable STCHECKDBEVERY can override this interval,
and for example be set to zero to force the check immediately.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4547
LGTM: imsodin
2017-12-14 09:51:17 +00:00
Jakob Borg
8c91ced784 cmd/syncthing: Clean up deadlock envvars
So STDEADLOCK seems to do the same thing as STDEADLOCKTIMEOUT, except in
the other package. Consolidate?

STDEADLOCKTHRESHOLD is actually called STLOCKTHRESHOLD, correct the help
text.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4598
2017-12-13 19:40:12 +00:00
Jakob Borg
a4147d9019 cmd/syncthing: Fix /rest/system/browse for folder path completion (fixes #4590)
Two issues since the filesystem migration;

- filepath.Base() doesn't do what the code expected when the path ends
  in a slash, and we make sure the path ends in a slash.

- the return should be fully qualified, not just the last component.

With this change it works as before, and passes the new test for it.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4591
LGTM: imsodin
2017-12-13 09:34:47 +00:00
Jakob Borg
673bd5fcc0 gui, man: Update docs & translations 2017-12-13 07:45:17 +01:00
Jakob Borg
24c721cb5d vendor: Update github.com/minio/sha256-simd (fixes #4585)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4592
2017-12-12 10:31:27 +00:00
Jakob Borg
230fb083fc lib/model: Remove dead code (*sharedPullerState).sourceFile 2017-12-12 11:30:47 +01:00
Tommy Thorn
509ae5e2d9 goals: Typo
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4588
2017-12-12 08:12:30 +00:00
Jakob Borg
136b3f25f6 cmd/stsigtool: Silence spurious Go 1.10 test/vet complaint 2017-12-10 19:42:17 +01:00
Jakob Borg
57eb1710e9 vendor: Update github.com/zillode/notify 2017-12-10 19:42:17 +01:00
Pawel Palenica
ece1defb2f lib/dialer: Register dialer for socks URL scheme (fixes #4515)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4579
2017-12-08 12:04:43 +00:00
Pier Paolo Ramon
8fd2937a58 readme: Formatting
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4578
2017-12-07 09:43:00 +00:00
Simon Frei
cce634f340 lib/model: Improve scan scheduling and dir del during pull (fixes #4475 #4476)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4493
2017-12-07 08:42:03 +00:00
Jakob Borg
47429d01e8 lib/config, lib/model: Tweaks to the auto accept feature
Fix the folder restart behavior (ignore Label), improve the API for that
(imho).

Also removes the tab switch animation in the settings modal, because
annoying.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4577
2017-12-07 08:33:32 +00:00
Audrius Butkevicius
445c4edeca gui, lib/config, lib/model: Support auto-accepting folders (fixes #2299)
Also introduces a new Waiter interface for config changes and segments the
configuration GUI.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4551
2017-12-07 07:08:24 +00:00
Simon Frei
c005b8dcb0 lib/fs: Prolong test timeout on darwin, hopefully fixing flakyness
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4576
2017-12-06 22:07:08 +00:00
Audrius Butkevicius
b9ed6c4c2c vendor: Update pfilter and go-stun (fixes #4561)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4575
2017-12-06 21:28:36 +00:00
Jakob Borg
3153e36a3d gui, man: Update docs & translations 2017-12-06 07:45:18 +01:00
Audrius Butkevicius
60bceb0f09 vendor: Update pfilter (ref #4561)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4572
2017-12-06 06:19:49 +00:00
Jakob Borg
257c3f5e82 script: Retire unused changelog script 2017-12-04 23:00:40 +01:00
Jakob Borg
7d0723da68 authors: Retire unused NICKS file 2017-12-04 23:00:40 +01:00
Pawel Palenica
205426a9c6 gui: Add confirmation on removing devices and folders (fixes #4543)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4563
LGTM: calmh
2017-12-02 11:28:06 +00:00
Jakob Borg
bd12e38b56 gui, man: Update docs & translations 2017-11-29 07:45:17 +01:00
Audrius Butkevicius
95a65bf0d0 lib/config: Support symlinked root (fixes #4542, fixes #4353)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4545
LGTM: imsodin, calmh
2017-11-26 07:51:22 +00:00
Jakob Borg
429b3a0429 lib/osutil, lib/scanner: Run symlink test on Windows when possible
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4548
2017-11-25 21:49:53 +00:00
Jakob Borg
cc14563b62 Merge branch 'release'
* release:
  vendor: Update pfilter (fixes #4537)
  lib/connections: Actually fix LAN detection, for real (ref #4534)
2017-11-23 08:32:54 +01:00
Thomas Hipp
b2af8f135b lib/events: Fix unmarshaling of EventType
skip-check: authors

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4540
2017-11-22 23:25:55 +00:00
Audrius Butkevicius
67c39b2512 vendor: Update pfilter (fixes #4537)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4539
2017-11-22 21:16:49 +00:00
Simon Frei
ce29d3a574 all: Various debug logging improvements
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4529
2017-11-22 08:05:27 +00:00
Jakob Borg
6daa766fde lib/connections: Actually fix LAN detection, for real (ref #4534) 2017-11-22 09:01:21 +01:00
Jakob Borg
ed95e80088 Merge branch 'release'
* release:
  lib/connections: Fix local address priority
  lib/connections: Actually make connection attempts for lower priority addresses as well
2017-11-22 08:11:03 +01:00
Audrius Butkevicius
4922b46fbd lib/connections: Fix local address priority
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4534
LGTM: imsodin, calmh
2017-11-22 07:05:49 +00:00
Jakob Borg
b99e92bad7 gui, man: Update docs & translations 2017-11-22 07:45:21 +01:00
xjtdy888
a17d953334 lib/connections: Actually make connection attempts for lower priority addresses as well
Skip-check: authors

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4535
2017-11-21 14:58:18 +00:00
Jakob Borg
7817d092cb Merge branch 'release'
* release:
  lib/connections: Trust the model to tell us if we are connected
  build: More signatures, more better (ref #3420)
  lib/model: Trigger a pull when ignore patterns change
2017-11-21 08:44:14 +01:00
Audrius Butkevicius
44a542391e lib/connections: Trust the model to tell us if we are connected
This should address issue as described in https://forum.syncthing.net/t/stun-nig-party-with-paused-devices/10942/13
Essentially the model and the connection service goes out of sync in terms of thinking if we are connected or not.
Resort to model as being the ultimate source of truth.

I can't immediately pin down how this happens, yet some ideas.

ConfigSaved happens in separate routine, so it's possbile that we have some sort of device removed yet connection comes in parallel kind of thing.
However, in this case the connection exists in the model, and does not exist in the connection service and the only way for the connection to be removed
in the connection service is device removal from the config.

Given the subject, this might also be related to the device being paused.

Also, adds more info to the logs

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4533
2017-11-21 07:25:38 +00:00
Jakob Borg
e589e6c19d test: Forgot a resume 2017-11-21 08:21:25 +01:00
Jakob Borg
4a58196959 build: More signatures, more better (ref #3420) 2017-11-20 17:42:59 +01:00
Jakob Borg
0901350087 lib/model: Trigger a pull when ignore patterns change
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4532
2017-11-20 16:29:36 +00:00
Jakob Borg
8078babf0a Merge branch 'release'
* release:
  build: Windows code signing (ref #3420)
  lib/connections: Fix race condition in parallel dial, minor cleanups (fixes #4526)
2017-11-20 11:57:29 +01:00
Jakob Borg
7279644372 build: Windows code signing (ref #3420) 2017-11-20 08:25:23 +01:00
Jakob Borg
cd29e3c524 script: Better change log, based only on issue titles, understanding labels 2017-11-19 21:19:33 +01:00
Jakob Borg
72d645865e lib/connections: Fix race condition in parallel dial, minor cleanups (fixes #4526)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4527
2017-11-19 17:38:13 +00:00
Dmitry Saveliev
9471b9f6af lib/versioner: Purge the empty directories in .stversions (fixes #4406)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4514
LGTM: AudriusButkevicius, imsodin
2017-11-18 15:56:53 +00:00
1451 changed files with 350955 additions and 515693 deletions

View File

@@ -1 +0,0 @@
NICKS

View File

@@ -3,7 +3,9 @@
#
# Name Name Name (nickname) <email1@example.com> <email2@example.com>
#
# The NICKS list is auto generated from this file.
# After changing this list, run "go run script/authors.go" to sort and update
# the GUI HTML.
#
Aaron Bieber (qbit) <qbit@deftly.net>
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com>
@@ -41,6 +43,7 @@ Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
Denis A. (dva) <denisva@gmail.com>
Dennis Wilson (snnd) <dw@risu.io>
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
Dominik Heidler (asdil12) <dominik@heidler.eu>
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
Emil Hessman (ceh) <emil@hessman.se>
@@ -89,6 +92,7 @@ Michael Tilli (pyfisch) <pyfisch@gmail.com>
Nate Morrison (nrm21) <natemorrison@gmail.com>
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
Peter Hoeg (peterhoeg) <peter@speartail.com>
Philippe Schommers (filoozoom) <philippe@schommers.be>
Phill Luby (pluby) <phill.luby@newredo.com>

View File

@@ -36,7 +36,7 @@ or modification by unauthorized parties.
Syncthing should be approachable, understandable and inclusive.
> Complex concepts and maths form the base of Synchting's functionality.
> Complex concepts and maths form the base of Syncthing's functionality.
> This should nonetheless be abstracted or hidden to a degree where
> Syncthing is usable by the general public.

151
NICKS
View File

@@ -1,151 +0,0 @@
# This file maps email addresses used in commits to nicks used the changelog.
# It is auto generated from the AUTHORS file by script/authors.go.
0x010C <antoine.lamielle@0x010c.fr>
0x010C <gh@0x010c.fr>
acogdev <jake@acogdev.com>
adelq <aqalieh95@gmail.com>
adelq <adelq@users.noreply.github.com>
alessandro.g89 <alessandro.g89@gmail.com>
alex2108 <register-github@alex-graf.de>
andersonvom <andersonvom@gmail.com>
andrew-d <andrew@du.nham.ca>
asdil12 <dominik@heidler.eu>
AudriusButkevicius <audrius.butkevicius@gmail.com>
aviau <alexandre@alexandreviau.net>
aviau <aviau@debian.org>
bencurthoys <ben@bencurthoys.com>
benshep <bjashepherd@gmail.com>
bigbear2nd <bigbear2nd@gmail.com>
brbecker <brbecker@gmail.com>
brendanlong <self@brendanlong.com>
brgmnn <dan.arne.bergmann@gmail.com>
brgmnn <brgmnn@users.noreply.github.com>
bsidhom <bsidhom@gmail.com>
buinsky <vix_booja@tut.by>
burkemw3 <mburke@amplify.com>
burkemw3 <burkemw3@gmail.com>
calmh <jakob@nym.se>
calmh <jakob@kastelo.net>
canton7 <antony.male@gmail.com>
Cathryne <cathryne.linenweaver@gmail.com>
Cathryne <Cathryne@users.noreply.github.com>
cdata <chris@scriptolo.gy>
cdhowie <me@chrishowie.com>
ceh <emil@hessman.se>
cqcallaw <enlightened.despot@gmail.com>
damajor <damajor@gmail.com>
dinosore <dinosore@dbrsoftware.co.uk>
dtchanpura <dtchanpura@gmail.com>
dtchanpura <dcprime314@gmail.com>
dva <denisva@gmail.com>
dzarda <dzardacz@gmail.com>
eipiminus1 <eipiminusone+github@gmail.com>
eipiminus1 <eipiminus1@users.noreply.github.com>
elopio <yo@elopio.net>
facastagnini <federico.castagnini@gmail.com>
filoozoom <philippe@schommers.be>
frioux <frew@afoolishmanifesto.com>
frioux <frioux@gmail.com>
fti7 <frank@isemann.name>
gillisig <gilli@vx.is>
hadogenes <szafar@linux.pl>
imsodin <freisim93@gmail.com>
ironmig <kma1660@gmail.com>
jarlebring <jarlebring@gmail.com>
jayachithra <s.k.jayachithra@gmail.com>
jedie <github.com@jensdiemer.de>
jedie <git@jensdiemer.de>
jgke <jgke@jgke.fi>
jmdaweb <jmdaweb@hotmail.com>
jmdaweb <jmdaweb@users.noreply.github.com>
jpjp <jamespatterson@operamail.com>
jpjp <jpjp@users.noreply.github.com>
kamadak <kamada@nanohz.org>
KayoticSully <kayoticsully@gmail.com>
kc1212 <kc04bc@gmx.com>
kc1212 <kc1212@users.noreply.github.com>
kilburn <kilburn@la3.org>
kluppy <kluppy@going2blue.com>
kozec <kozec@kozec.com>
kralo <max.schulze@online.de>
kralo <kralo@users.noreply.github.com>
krozycki <rozycki.karol@gmail.com>
Kudalufi <kurt@va1der.ca>
Kudalufi <kurt.fitzner@gmail.com>
kwhite17 <kevinwhite1710@gmail.com>
letiemble <laurent.etiemble@gmail.com>
letiemble <laurent.etiemble@monobjc.net>
liusy182 <liusy182@gmail.com>
liusy182 <liusy182@hotmail.com>
lkwg82 <lkwg82@gmx.de>
LordLandon <lordlandon@gmail.com>
majedev <majed.alhajry@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
marclaporte <marc@marclaporte.com>
marclaporte <marc@laporte.name>
mateon1 <matin1111@wp.pl>
mogwa1 <devriesb@gmail.com>
moshen <moshen.colin@gmail.com>
Moter8 <moter8@gmail.com>
mpx <mark@kyne.com.au>
mvdan <mvdan@mvdan.cc>
nelsonkhan <nelsonkhan@gmail.com>
Niller303 <nielsproest@hotmail.com>
Niller303 <seje.niels@hotmail.com>
norgeous <daniel@harte.me>
norgeous <daniel@danielharte.co.uk>
norgeous <norgeous@users.noreply.github.com>
nov1n <robert@carosi.nl>
nrm21 <natemorrison@gmail.com>
Nutomic <me@nutomic.com>
pascalj <github@pascalj.com>
pascalj <mail@pascal-jungblut.com>
peterhoeg <peter@speartail.com>
philips <brandon@ifup.org>
piobpl <piotrb10@gmail.com>
plouj <ploujj@gmail.com>
pluby <phill.luby@newredo.com>
ProactiveServices <aD@simplypeachy.co.uk>
ProactiveServices <simplypeachy@users.noreply.github.com>
ProactiveServices <ProactiveServices@users.noreply.github.com>
pyfisch <pyfisch@gmail.com>
qbit <qbit@deftly.net>
ralder <ralder@yandex.ru>
rasa <ross@smithii.com>
Rewt0r <rewt0r@gmx.com>
Rewt0r <Rewt0r@users.noreply.github.com>
rumpelsepp <stefan@sevenbyte.org>
rumpelsepp <rumpelsepp@sevenbyte.org>
sacheendra <sacheendra.t@gmail.com>
scienmind <scintertech@cryptolab.net>
sciurius <jvromans@squirrel.nl>
seehuhn <voss@seehuhn.de>
Smiley73 <heiko@zuerker.org>
snnd <dw@risu.io>
snugghash <suhas.gundimeda@gmail.com>
snugghash <snugghash@gmail.com>
Stefan-Code <stefan.github@gmail.com>
Stefan-Code <Stefan.github@gmail.com>
timabell <tim@timwise.co.uk>
timhowes <timhowes@berkeley.edu>
tnn2 <tnn@nygren.pp.se>
tobiastom <t.tom@succont.de>
tojrobinson <tully@tojr.org>
tpng <benny.tpng@gmail.com>
tylerbrazier <tyler@tylerbrazier.com>
Unrud <unrud@openaliasbox.org>
Unrud <Unrud@users.noreply.github.com>
uok <ueomkail@gmail.com>
uok <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>
Vilbrekin <vilbrekin@gmail.com>
wkennington <william@wkennington.com>
WSGCSysadmin <e.meitner@willystreet.coop>
wweich <wweich@users.noreply.github.com>
wweich <wweich@gmx.de>
wweich <wulf@weich-kr.de>
xduugu <cedric@gmx.ca>
zaynetro <romanznet@gmail.com>
Zillode <zillode@zillode.be>
zukoo <fxgsell@gmail.com>

View File

@@ -20,36 +20,36 @@ commentary, see the full [Goals document][13].
Syncthing should be:
1. Safe From Data Loss
1. **Safe From Data Loss**
Protecting the user's data is paramount. We take every reasonable
precaution to avoid corrupting the user's files.
2. Secure Against Attackers
2. **Secure Against Attackers**
Again, protecting the user's data is paramount. Regardless of our other
goals we must never allow the user's data to be susceptible to
eavesdropping or modification by unauthorized parties.
3. Easy to Use
3. **Easy to Use**
Syncthing should be approachable, understandable and inclusive.
4. Automatic
4. **Automatic**
User interaction should be required only when absolutely necessary.
5. Universally Available
5. **Universally Available**
Syncthing should run on every common computer. We are mindful that the
latest technology is not always available to any given individual.
6. For Individuals
6. **For Individuals**
Syncthing is primarily about empowering the individual user with safe,
secure and easy to use file synchronization.
7. Everything Else
7. **Everything Else**
There are many things we care about that don't make it on to the list. It
is fine to optimize for these values, as long as they are not in conflict
@@ -111,3 +111,4 @@ All code is licensed under the [MPLv2 License][7].
[13]: https://github.com/syncthing/syncthing/blob/master/GOALS.md
[14]: assets/logo-text-128.png
[15]: https://syncthing.net/

View File

@@ -24,7 +24,7 @@ func main() {
flag.Parse()
if flag.NArg() < 1 {
log.Println(`Usage:
log.Print(`Usage:
stsigtool <command>
Where command is one of:
@@ -40,6 +40,7 @@ Where command is one of:
verify <signaturefile> <datafile> <pubkeyfile>
- verify a signature, using the specified public key file
`)
}

View File

@@ -13,6 +13,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
@@ -83,7 +84,8 @@ type modelIntf interface {
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
Completion(device protocol.DeviceID, folder string) model.FolderCompletion
Override(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
NeedSize(folder string) db.Counts
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
@@ -112,12 +114,12 @@ type configIntf interface {
GUI() config.GUIConfiguration
RawCopy() config.Configuration
Options() config.OptionsConfiguration
Replace(cfg config.Configuration) error
Replace(cfg config.Configuration) (config.Waiter, error)
Subscribe(c config.Committer)
Folders() map[string]config.FolderConfiguration
Devices() map[protocol.DeviceID]config.DeviceConfiguration
SetDevice(config.DeviceConfiguration) error
SetDevices([]config.DeviceConfiguration) error
SetDevice(config.DeviceConfiguration) (config.Waiter, error)
SetDevices([]config.DeviceConfiguration) (config.Waiter, error)
Save() error
ListenAddresses() []string
RequiresRestart() bool
@@ -254,6 +256,7 @@ func (s *apiService) Serve() {
getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
getRestMux.HandleFunc("/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
getRestMux.HandleFunc("/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
@@ -661,6 +664,7 @@ func (s *apiService) getDBCompletion(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string]interface{}{
"completion": comp.CompletionPct,
"needBytes": comp.NeedBytes,
"needItems": comp.NeedItems,
"globalBytes": comp.GlobalBytes,
"needDeletes": comp.NeedDeletes,
})
@@ -718,11 +722,7 @@ func (s *apiService) postDBOverride(w http.ResponseWriter, r *http.Request) {
go s.model.Override(folder)
}
func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
func getPagingParams(qs url.Values) (int, int) {
page, err := strconv.Atoi(qs.Get("page"))
if err != nil || page < 1 {
page = 1
@@ -731,20 +731,52 @@ func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
if err != nil || perpage < 1 {
perpage = 1 << 16
}
return page, perpage
}
progress, queued, rest, total := s.model.NeedFolderFiles(folder, page, perpage)
func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
page, perpage := getPagingParams(qs)
progress, queued, rest := s.model.NeedFolderFiles(folder, page, perpage)
// Convert the struct to a more loose structure, and inject the size.
sendJSON(w, map[string]interface{}{
"progress": s.toNeedSlice(progress),
"queued": s.toNeedSlice(queued),
"rest": s.toNeedSlice(rest),
"total": total,
"progress": toNeedSlice(progress),
"queued": toNeedSlice(queued),
"rest": toNeedSlice(rest),
"page": page,
"perpage": perpage,
})
}
func (s *apiService) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
device := qs.Get("device")
deviceID, err := protocol.DeviceIDFromString(device)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
page, perpage := getPagingParams(qs)
if files, err := s.model.RemoteNeedFolderFiles(deviceID, folder, page, perpage); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
sendJSON(w, map[string]interface{}{
"files": toNeedSlice(files),
"page": page,
"perpage": perpage,
})
}
}
func (s *apiService) getSystemConnections(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.model.ConnectionStats())
}
@@ -809,7 +841,7 @@ func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
// Activate and save
if err := s.cfg.Replace(to); err != nil {
if _, err := s.cfg.Replace(to); err != nil {
l.Warnln("Replacing config:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -1201,7 +1233,7 @@ func (s *apiService) makeDevicePauseHandler(paused bool) http.HandlerFunc {
cfgs = append(cfgs, cfg)
}
if err := s.cfg.SetDevices(cfgs); err != nil {
if _, err := s.cfg.SetDevices(cfgs); err != nil {
http.Error(w, err.Error(), 500)
}
}
@@ -1280,18 +1312,21 @@ func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
current := qs.Get("current")
// Default value or in case of error unmarshalling ends up being basic fs.
var fsType fs.FilesystemType
fsType.UnmarshalText([]byte(qs.Get("filesystem")))
sendJSON(w, browseFiles(current, fsType))
}
func browseFiles(current string, fsType fs.FilesystemType) []string {
if current == "" {
filesystem := fs.NewFilesystem(fsType, "")
if roots, err := filesystem.Roots(); err == nil {
sendJSON(w, roots)
} else {
http.Error(w, err.Error(), 500)
return roots
}
return
return nil
}
search, _ := fs.ExpandTilde(current)
pathSeparator := string(fs.PathSeparator)
@@ -1300,7 +1335,13 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
search = search + pathSeparator
}
searchDir := filepath.Dir(search)
searchFile := filepath.Base(search)
// The searchFile should be the last component of search, or empty if it
// ends with a path separator
var searchFile string
if !strings.HasSuffix(search, pathSeparator) {
searchFile = filepath.Base(search)
}
fs := fs.NewFilesystem(fsType, searchDir)
@@ -1310,11 +1351,10 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
for _, subdirectory := range subdirectories {
info, err := fs.Stat(subdirectory)
if err == nil && info.IsDir() {
ret = append(ret, subdirectory+pathSeparator)
ret = append(ret, filepath.Join(searchDir, subdirectory)+pathSeparator)
}
}
sendJSON(w, ret)
return ret
}
func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
@@ -1343,7 +1383,7 @@ func (s *apiService) getHeapProf(w http.ResponseWriter, r *http.Request) {
pprof.WriteHeapProfile(w)
}
func (s *apiService) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
func toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
res := make([]jsonDBFileInfo, len(fs))
for i, f := range fs {
res[i] = jsonDBFileInfo(f)
@@ -1365,6 +1405,7 @@ func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
"invalid": f.Invalid,
"noPermissions": f.NoPermissions,
"modified": protocol.FileInfo(f).ModTime(),
"modifiedBy": f.ModifiedBy.String(),
"sequence": f.Sequence,
"numBlocks": len(f.Blocks),
"version": jsonVersionVector(f.Version),
@@ -1376,13 +1417,14 @@ type jsonDBFileInfo db.FileInfoTruncated
func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"type": f.Type,
"type": f.Type.String(),
"size": f.Size,
"permissions": fmt.Sprintf("%#o", f.Permissions),
"deleted": f.Deleted,
"invalid": f.Invalid,
"noPermissions": f.NoPermissions,
"modified": db.FileInfoTruncated(f).ModTime(),
"modifiedBy": f.ModifiedBy.String(),
"sequence": f.Sequence,
})
}

View File

@@ -16,6 +16,8 @@ import (
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
@@ -24,6 +26,7 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
@@ -957,3 +960,61 @@ func TestEventMasks(t *testing.T) {
t.Errorf("should have returned a valid, non-default event sub")
}
}
func TestBrowse(t *testing.T) {
pathSep := string(os.PathSeparator)
tmpDir, err := ioutil.TempDir("", "syncthing")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
if err := os.Mkdir(filepath.Join(tmpDir, "dir"), 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
// We expect completion to return the full path to the completed
// directory, with an ending slash.
dirPath := filepath.Join(tmpDir, "dir") + pathSep
cases := []struct {
current string
returns []string
}{
// The direcotory without slash is completed to one with slash.
{tmpDir, []string{tmpDir + pathSep}},
// With slash it's completed to its contents.
// Dirs are given pathSeps.
// Files are not returned.
{tmpDir + pathSep, []string{dirPath}},
// Globbing is automatic based on prefix.
{tmpDir + pathSep + "d", []string{dirPath}},
{tmpDir + pathSep + "di", []string{dirPath}},
{tmpDir + pathSep + "dir", []string{dirPath}},
{tmpDir + pathSep + "f", nil},
{tmpDir + pathSep + "q", nil},
}
for _, tc := range cases {
ret := browseFiles(tc.current, fs.FilesystemTypeBasic)
if !equalStrings(ret, tc.returns) {
t.Errorf("browseFiles(%q) => %q, expected %q", tc.current, ret, tc.returns)
}
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

View File

@@ -179,14 +179,11 @@ are mostly useful for developers. Use with care.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
STDEADLOCK Used for debugging internal deadlocks. Use only under
direction of a developer.
STDEADLOCKTIMEOUT Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
STDEADLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
STNORESTART Equivalent to the -no-restart argument. Disable the
Syncthing monitor process which handles restarts for some
@@ -200,6 +197,11 @@ are mostly useful for developers. Use with care.
"minio" for the github.com/minio/sha256-simd implementation,
and blank (the default) for auto detection.
STDBCHECKEVERY Set to a time interval to override the default database
check interval of 30 days (720h). The interval understands
"h", "m" and "s" abbreviations for hours minutes and seconds.
Valid values are like "720h", "30s", etc.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.
@@ -1080,14 +1082,7 @@ func defaultConfig(cfgFile string) *config.Wrapper {
if !noDefaultFolder {
l.Infoln("Default folder created and/or linked to new config")
defaultFolder = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, locations[locDefFolder])
defaultFolder.Label = "Default Folder"
defaultFolder.RescanIntervalS = 60
defaultFolder.FSWatcherDelayS = 10
defaultFolder.MinDiskFree = config.Size{Value: 1, Unit: "%"}
defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}}
defaultFolder.AutoNormalize = true
defaultFolder.MaxConflicts = -1
defaultFolder = config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations[locDefFolder])
} else {
l.Infoln("We will skip creation of a default folder on first start since the proper envvar is set")
}
@@ -1331,7 +1326,7 @@ func setPauseState(cfg *config.Wrapper, paused bool) {
for i := range raw.Folders {
raw.Folders[i].Paused = paused
}
if err := cfg.Replace(raw); err != nil {
if _, err := cfg.Replace(raw); err != nil {
l.Fatalln("Cannot adjust paused state:", err)
}
}

View File

@@ -34,8 +34,8 @@ func (c *mockedConfig) Options() config.OptionsConfiguration {
return config.OptionsConfiguration{}
}
func (c *mockedConfig) Replace(cfg config.Configuration) error {
return nil
func (c *mockedConfig) Replace(cfg config.Configuration) (config.Waiter, error) {
return nil, nil
}
func (c *mockedConfig) Subscribe(cm config.Committer) {}
@@ -48,12 +48,12 @@ func (c *mockedConfig) Devices() map[protocol.DeviceID]config.DeviceConfiguratio
return nil
}
func (c *mockedConfig) SetDevice(config.DeviceConfiguration) error {
return nil
func (c *mockedConfig) SetDevice(config.DeviceConfiguration) (config.Waiter, error) {
return nil, nil
}
func (c *mockedConfig) SetDevices([]config.DeviceConfiguration) error {
return nil
func (c *mockedConfig) SetDevices([]config.DeviceConfiguration) (config.Waiter, error) {
return nil, nil
}
func (c *mockedConfig) Save() error {

View File

@@ -28,8 +28,12 @@ func (m *mockedModel) Completion(device protocol.DeviceID, folder string) model.
func (m *mockedModel) Override(folder string) {}
func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int) {
return nil, nil, nil, 0
func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
return nil, nil, nil
}
func (m *mockedModel) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
return nil, nil
}
func (m *mockedModel) NeedSize(folder string) db.Counts {

View File

@@ -211,6 +211,7 @@ func (c *folderSummaryService) sendSummary(folder string) {
"device": devCfg.DeviceID.String(),
"completion": comp.CompletionPct,
"needBytes": comp.NeedBytes,
"needItems": comp.NeedItems,
"globalBytes": comp.GlobalBytes,
})
}

View File

@@ -51,10 +51,10 @@ func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf,
var totBytes, maxBytes int64
for folderID := range cfg.Folders() {
global := m.GlobalSize(folderID)
totFiles += global.Files
totFiles += int(global.Files)
totBytes += global.Bytes
if global.Files > maxFiles {
maxFiles = global.Files
if int(global.Files) > maxFiles {
maxFiles = int(global.Files)
}
if global.Bytes > maxBytes {
maxBytes = global.Bytes

View File

@@ -367,3 +367,7 @@ ul.three-columns li, ul.two-columns li {
width: 100%;
}
}
.tab-content {
padding-top: 10px;
}

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Анонимен доклад",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Форматът на анонимния доклад е променен. Желаете ли да преминете към новия формат?",
"Any devices configured on an introducer device will be added to this device as well.": "Устройства настроени да представят други устройства също ще бъдат добавени към това устройство.",
"Are you sure you want to remove device {%name%}?": "Сигурни ли сте, че искате да премахнете устройство {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Сигурни ли сте, че искате да премахнете папка {{label}}?",
"Auto Accept": "Автоматично приемане",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматичното обновяване вече предлага избор между стабилни версии и кандидат версии.",
"Automatic upgrades": "Автоматично обновяване",
"Automatically create or share folders that this device advertises at the default path.": "Автоматично създаване или споделяне на папки, които това устройство предлага в пътя по подразбиране.",
"Be careful!": "Внимание!",
"Bugs": "Бъгове",
"CPU Utilization": "Използван процесор",
@@ -41,18 +45,21 @@
"Configured": "Настроен",
"Connection Error": "Грешка при свързването",
"Connection Type": "Вид връзка",
"Connections": "Връзки",
"Copied from elsewhere": "Копиране от някъде другаде",
"Copied from original": "Копиран от оригинала",
"Copyright © 2014-2016 the following Contributors:": "Всички правата запазени © 2014-2016 Сътрудници:",
"Copyright © 2014-2017 the following Contributors:": "Всички правата запазени © 2014-2017. Сътрудници:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Създаване на шаблони за игнориране, презаписване на съществуващ файл в {{path}}.",
"Danger!": "Опасност!",
"Default Folder Path": "Път до папка по подразбиране",
"Deleted": "Изтрито",
"Device": "Устройство",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) на {{address}} желае да се свърже. Добави ново устройство?",
"Device ID": "Идентификатор на устройство",
"Device Identification": "Идентификатор на устройство",
"Device Name": "Име на устройството",
"Device that last modified the item": "Устройство, което последно промени обекта",
"Devices": "Устройства",
"Disabled": "Деактивирано",
"Disconnected": "Не е свързано",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Адрес на слушане на GUI-то",
"GUI Listen Addresses": "Адрес за свързване с потребителския интерфейс",
"GUI Theme": "Тема за потребителския интефейс",
"General": "Общи",
"Generate": "Генерирай",
"Global Changes": "Глобални промени",
"Global Discovery": "Глобално откриване",
@@ -123,6 +131,7 @@
"Latest Change": "Последна промяна",
"Learn more": "Научете повече",
"Listeners": "Синхронизиращи устройства",
"Loading data...": "Зарежадне на информация...",
"Local Discovery": "Локално откриване",
"Local State": "Локално състояние",
"Local State (Total)": "Локално състояние (общо)",
@@ -131,6 +140,8 @@
"Maximum Age": "Максимална възраст",
"Metadata Only": "Само мета информация",
"Minimum Free Disk Space": "Минимално свободно дисково пространство",
"Mod. Device": "Променящо устройство",
"Mod. Time": "Дата на промяна",
"Move to top of queue": "Премести в началото на опашката",
"Multi level wildcard (matches multiple directory levels)": "Маска на много нива (покрива папки с много нива)",
"Never": "никога",
@@ -139,6 +150,7 @@
"Newest First": "Първо най-новите",
"No": "Не",
"No File Versioning": "Без версии",
"No files will be deleted as a result of this operation.": "Няма да бъдат изтрити файлове като резултат от тази операция.",
"No upgrades": "Няма обновления",
"Normal": "Нормален",
"Notice": "Известие",
@@ -153,6 +165,7 @@
"Override Changes": "Наложи локалните промени",
"Path": "Път",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Път до папката на това устройство. Ако не съществува ще бъде създадена. Символът тилда (~) може да бъде използван като заместител на",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Пътя, където нови автоматично приети папки ще бъдат създадени, както и пътят, който потребителският интерфейс ще предлага при добавяне на нови папки. ТСимволът тилда (~) ще се превърне в {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Пътят, където версиите да бъдат складирани(оставете празно за папката .stversions).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Пътят, където версиите да бъдат складирани(остави празно за папката .stversions).",
"Pause": "Пауза",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Кандидат версиите съдържат най-новата функционалност и поправки. Те са близки до традиционните дву-седмични Synchthing обновления.",
"Remote Devices": "Чужди устройства",
"Remove": "Премахни",
"Remove Device": "Премахване на устройство",
"Remove Folder": "Премахване на папка",
"Required identifier for the folder. Must be the same on all cluster devices.": "Задължителен идентификатор за тази папка. Трябва да бъде един и същ на всички устройства.",
"Rescan": "Сканирай",
"Rescan All": "Обнови всички",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Спирането завършено",
"Simple File Versioning": "Опростени версии",
"Single level wildcard (matches within a directory only)": "Маска на едно ниво (покрива само в папка)",
"Size": "Размер",
"Smallest First": "Първо най-малките",
"Source Code": "Сорс код",
"Stable releases and release candidates": "Стабилни версии и кандидати за стабилни версии",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Това е нова основна версия.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Тази настройка контролира нужното свободното място на основния (пр. този с базата данни) диск.",
"Time": "Време",
"Time the item was last modified": "Часът на последна промяна на обенкта",
"Trash Can File Versioning": "Само на файловете в кошчето",
"Type": "Тип",
"Unavailable": "Не е на разположение",
"Unavailable/Disabled by administrator or maintainer": "Не е на разположение/Деактивриан от администраторът или поддръжника",
"Undecided (will prompt)": "Неизбрано (ще попита)",
"Unknown": "Неясно",
"Unshared": "Несподелена",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Informe d'ús anònim",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"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.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "L'actualització automàtica ara ofereix l'elecció entre les versions estables i les versions candidates.",
"Automatic upgrades": "Actualitzacions automàtiques",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Tin precaució!",
"Bugs": "Errors (Bugs)",
"CPU Utilization": "Utilització de la CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurat",
"Connection Error": "Error de connexió",
"Connection Type": "Tipus de connexió",
"Connections": "Connections",
"Copied from elsewhere": "Copiat de qualsevol lloc",
"Copied from original": "Copiat de l'original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 els següents Col·laboradors:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 els següents Col·laboradors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creant patrons a ignorar, sobreescriguent un fitxer que ja existeix a {{path}}.",
"Danger!": "Perill!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Esborrat",
"Device": "Dispositiu",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositiu \"{{name}}\" ({{device}} a l'adreça {{address}}) vol connectar. Afegir nou dispositiu?",
"Device ID": "ID del dispositiu",
"Device Identification": "Identificació del dispositiu",
"Device Name": "Nom del dispositiu",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositius",
"Disabled": "Disabled",
"Disconnected": "Desconnectat",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Adreça d'Escolta de l'Interfície Gràfica d'Usuari (GUI).",
"GUI Listen Addresses": "Direcció d'escolta de l'Interfície Gràfica d'Usuari (GUI)",
"GUI Theme": "Tema de l'Interfície Gràfica d'Usuari (GUI)",
"General": "General",
"Generate": "Generar",
"Global Changes": "Canvis Globals",
"Global Discovery": "Descobriment global",
@@ -123,6 +131,7 @@
"Latest Change": "Últim Canvi",
"Learn more": "Saber més",
"Listeners": "Escoltants",
"Loading data...": "Loading data...",
"Local Discovery": "Descobriment local",
"Local State": "Estat local",
"Local State (Total)": "Estat Local (Total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Edat màxima",
"Metadata Only": "Sols metadades",
"Minimum Free Disk Space": "Espai minim de disc lliure",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"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",
@@ -139,6 +150,7 @@
"Newest First": "El més nou primer",
"No": "No",
"No File Versioning": "Sense versionat de fitxer",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Sense actualitzacions",
"Normal": "Normal",
"Notice": "Avís",
@@ -153,6 +165,7 @@
"Override Changes": "Sobreescriure els canvis",
"Path": "Ruta",
"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 new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "La ruta on deuen guardar-se les versions (deixar buit per al directori per defecte .stversions en la carpeta compartida).",
"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).",
"Pause": "Pausa",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Les versions candidates (Release Candidates) contenen les darreres característiques i arreglos. Són paregudes a les versions tradicionals bi-semanals de Syncthing. ",
"Remote Devices": "Dispositius Remots",
"Remove": "Eliminar",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador necessari per la carpeta. Deu ser el mateix en tots els dispositius del cluster.",
"Rescan": "Tornar a buscar",
"Rescan All": "Tornar a buscar tot",
@@ -210,6 +225,7 @@
"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)",
"Size": "Size",
"Smallest First": "El més xicotet primer",
"Source Code": "Codi font",
"Stable releases and release candidates": "Versions estables i versions candidates",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Aquesta és una actualització important de la versió.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Aquest ajust controla l'espai lliure requerit en el disc inicial (per exemple, la base de dades de l'index).",
"Time": "Temps",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Versionat d'arxius de la paperera",
"Type": "Tipus",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Desconegut",
"Unshared": "No compartit",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formát anonymního hlášení o používání byl změněn. Chcete přejít na nový formát?",
"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.",
"Are you sure you want to remove device {%name%}?": "Skutečně chcete odebrat zařízení {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Skutečně chcete odebrat adresář {{label}}?",
"Auto Accept": "Přijmout automaticky",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatická aktualizace nyní nabízí volbu mezi stabilními vydáními a kandidáty na vydání.",
"Automatic upgrades": "Automatické aktualizace",
"Automatically create or share folders that this device advertises at the default path.": "Automaticky vytvářet nebo sdílet adresáře, které toto zařízení odesílá ve výchozí cestě.",
"Be careful!": "Pozor!",
"Bugs": "Chyby",
"CPU Utilization": "Využití CPU",
@@ -41,18 +45,21 @@
"Configured": "Nastaveno",
"Connection Error": "Chyba připojení",
"Connection Type": "Typ připojení",
"Connections": "Připojení",
"Copied from elsewhere": "Zkopírováno odjinud",
"Copied from original": "Zkopírováno z originálu",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 následující přispěvatelé:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 následující přispěvatelé:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Vytváření ignorovaných vzorů, přepisování existujícího souboru v {{path}}.",
"Danger!": "Pozor!",
"Default Folder Path": "Výchozí cesta k adresáři",
"Deleted": "Smazáno",
"Device": "Zařízení",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Zařízení \"{{name}}\" ({{device}} na {{address}}) se chce připojit. Přidat nové zařízení?",
"Device ID": "ID přístroje",
"Device Identification": "Identifikace přístroje",
"Device Name": "Jméno přístroje",
"Device that last modified the item": "Poslední přístroj, který modifikoval položku",
"Devices": "Přístroje",
"Disabled": "Vypnuto",
"Disconnected": "Odpojen",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Adresa naslouchání GUI",
"GUI Listen Addresses": "Adresa naslouchání GUI",
"GUI Theme": "Grafické téma",
"General": "Obecné",
"Generate": "Generovat",
"Global Changes": "Globální změny",
"Global Discovery": "Globální oznamování",
@@ -123,6 +131,7 @@
"Latest Change": "Poslední změna",
"Learn more": "Zjistěte více",
"Listeners": "Naslouchající",
"Loading data...": "Nahrávání dat...",
"Local Discovery": "Místní oznamování",
"Local State": "Místní status",
"Local State (Total)": "Místní status (Celkem)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maximální časový limit",
"Metadata Only": "Pouze metadata",
"Minimum Free Disk Space": "Minimální velikost volného místa na disku",
"Mod. Device": "Přístroj, který provedl modifikaci",
"Mod. Time": "Čas modifikace",
"Move to top of queue": "Přesunout na začátek fronty",
"Multi level wildcard (matches multiple directory levels)": "Víceúrovňový zástupný znak (shoda skrz více úrovní složek)",
"Never": "Nikdy",
@@ -139,6 +150,7 @@
"Newest First": "Od nejnovějšího",
"No": "Ne",
"No File Versioning": "Bez verzování souborů",
"No files will be deleted as a result of this operation.": "Tato operace nesmaže žádné soubory.",
"No upgrades": "Žádné aktualizace",
"Normal": "Normální",
"Notice": "Oznámení",
@@ -153,6 +165,7 @@
"Override Changes": "Přepsat změny",
"Path": "Cesta",
"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 new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Cesta pro ukládání nových autom. přijatých adresářů a také výchozí cesta při přidávání nových adresářů v GUI. Vlnka (~) se rozvine na {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Cesta pro ukládání verzí (ponechte prázdné pro výchozí adresář .stversions ve sdíleném adresáři).",
"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í složku .stversions v adresáři).",
"Pause": "Pozastavit",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidáti na vydání obsahují nejnovější změny a opravy. Podobají se tradičním dvoutýdenním vydáním Syncthing.",
"Remote Devices": "Vzdálená zařízení",
"Remove": "Odstranit",
"Remove Device": "Odebrat zařízení",
"Remove Folder": "Odebrat adresář",
"Required identifier for the folder. Must be the same on all cluster devices.": "Požadovaný identifikátor adresáře. Musí být stejný na všech zařízeních.",
"Rescan": "Opakovat skenování",
"Rescan All": "Opakovat skenování všech",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Vypnutí dokončeno",
"Simple File Versioning": "Jednoduché verzování souborů",
"Single level wildcard (matches within a directory only)": "Jednoúrovňový zástupný znak (shody pouze uvnitř složky)",
"Size": "Velikost",
"Smallest First": "Od nejmenšího",
"Source Code": "Zdrojový kód",
"Stable releases and release candidates": "Stabilní vydání a kandidáti na vydání",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Toto je důležitá aktualizace.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Toto nastavení ovládá velikost volného prostoru na hlavním disku (ten, na kterém je databáze indexu).",
"Time": "Čas",
"Time the item was last modified": "Čas poslední modifikace položky",
"Trash Can File Versioning": "Verzování souborů v koši",
"Type": "Typ",
"Unavailable": "Nedostupné",
"Unavailable/Disabled by administrator or maintainer": "Nedostupné; Zakázáno administrátorem",
"Undecided (will prompt)": "Nerozhodnuto (zeptá se)",
"Unknown": "Neznámý",
"Unshared": "Nesdílený",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonym brugerstatistik",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Alle enheder som er konfigueret som en introducerende enhed, vil også blive tilføjet til denne enhed.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Den automatiske opdatering tilbyder nu valget mellem stabile - og udgivelses kandidater.",
"Automatic upgrades": "Automatisk opdatering",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Vær forsigtig!",
"Bugs": "Fejl",
"CPU Utilization": "CPU-forbrug",
@@ -41,18 +45,21 @@
"Configured": "Konfigureret",
"Connection Error": "Tilslutnings fejl",
"Connection Type": "Tilslutningstype",
"Connections": "Connections",
"Copied from elsewhere": "Kopieret fra et andet sted",
"Copied from original": "Kopieret fra originalen",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 de følgende bidragsydere:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 de følgende bidragsydere:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Fare!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Slettet",
"Device": "Enhed",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enheden\"{{name}}\" ({{device}} på {{address}}) vil gerne forbinde. Tilføj denne enhed ? ",
"Device ID": "Enheds-ID",
"Device Identification": "Enhedsidentifikation",
"Device Name": "Enhedsnavn",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Enheder",
"Disabled": "Disabled",
"Disconnected": "Ikke tilsluttet",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI-lytteadresse",
"GUI Theme": "GUI tema",
"General": "General",
"Generate": "Opret",
"Global Changes": "Globale ændringer",
"Global Discovery": "Globalt opslag",
@@ -123,6 +131,7 @@
"Latest Change": "Sidste ændring",
"Learn more": "Lær mere",
"Listeners": "Lyttere",
"Loading data...": "Loading data...",
"Local Discovery": "Lokal opslag",
"Local State": "Lokal tilstand",
"Local State (Total)": "Lokal tilstand (total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maks alder",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Mindst ledig diskplads",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Flyt til toppen af køen",
"Multi level wildcard (matches multiple directory levels)": "Flerniveau wildcard (matcher flere biblioteksniveauer)",
"Never": "Aldrig",
@@ -139,6 +150,7 @@
"Newest First": "Nyeste først",
"No": "Nej",
"No File Versioning": "Ingen filversion",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Ingen opgraderinger",
"Normal": "Normal",
"Notice": "OBS",
@@ -153,6 +165,7 @@
"Override Changes": "Overskriv ændringer",
"Path": "Sti",
"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": "Sti til den lokale mappe. Vil blive oprettet hvis den ikke findes. Tilde tegnet (~) kan bruges som en forkortelse for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Sti hvor versioner skal gemmes ( lad dette felt være tom for at bruge .stversions mappen i den delte mappe)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sti hvor versioner skal gemmes (efterlad tom for default .stversions mappe)",
"Pause": "Pause",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Udgivelseskandidater indeholder alle de nyeste funktioner og rettelser. De er ens med de traditionelle 2 ugers Syncthing udgivelser.",
"Remote Devices": "Fjernenheder ",
"Remove": "Fjern",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Nødvendig identifikation af mappen. Dette skal være det samme på alle enheder.",
"Rescan": "Skan igen",
"Rescan All": "Skan alt igen",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Nedlukning fuldført",
"Simple File Versioning": "Simpel fil versioner",
"Single level wildcard (matches within a directory only)": "Enkeltnivau wildcard (matcher kun inden for en mapp)",
"Size": "Size",
"Smallest First": "Mindste først",
"Source Code": "Kildekode",
"Stable releases and release candidates": "Stabile udgivelser og udgivelseskandidater ",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Dette er en ny version",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Tid",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Skraldespand fil versioner",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Ukendt",
"Unshared": "Ikke delt",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Das Format der anonymen Nutzungsberichte wurde geändert. Möchten Sie auf das neue Format umsteigen?",
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteilergerät eingetragen sind, werden auch bei diesem Gerät hinzugefügt.",
"Are you sure you want to remove device {%name%}?": "Sind Sie sicher, dass sie das Gerät {{name}} entfernen möchten?",
"Are you sure you want to remove folder {%label%}?": "Sind Sie sicher, dass sie den Ordner {{label}} entfernen möchten?",
"Auto Accept": "Automatisches Annehmen",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Die automatische Aktualisierung bietet jetzt die Wahl zwischen stabilen Veröffentlichungen und Veröffentlichungskandidaten.",
"Automatic upgrades": "Automatische Updates aktivieren",
"Automatic upgrades": "Automatische Aktualisierungen aktivieren",
"Automatically create or share folders that this device advertises at the default path.": "Automatisches erstellen oder teilen von Ordnern, die von dem Gerät über den Standard Pfad angeboten werden.",
"Be careful!": "Vorsicht!",
"Bugs": "Fehler",
"CPU Utilization": "Prozessorauslastung",
@@ -41,18 +45,21 @@
"Configured": "Konfiguriert",
"Connection Error": "Verbindungsfehler",
"Connection Type": "Verbindungstyp",
"Connections": "Verbindungen",
"Copied from elsewhere": "Von anderer Quelle kopiert",
"Copied from original": "Vom Original kopiert",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 der folgenden Unterstützer:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 der folgenden Unterstützer:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Erstelle Ignoriermuster, welche die existierende Datei {{path}} überschreiben.",
"Danger!": "Achtung!",
"Default Folder Path": "Standardmäßiger Ordnerpfad ",
"Deleted": "Gelöscht",
"Device": "Gerät",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät \"{{name}}\" ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Device ID": "Gerätekennung",
"Device Identification": "Geräteidentifikation",
"Device Name": "Gerätename",
"Device that last modified the item": "Gerät, das das Element zuletzt geändert hat",
"Devices": "Geräte",
"Disabled": "Deaktiviert",
"Disconnected": "Getrennt",
@@ -95,10 +102,11 @@
"Folders": "Ordner",
"GUI": "GUI",
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
"GUI Authentication User": "Nutzername für Zugang zur Benutzeroberfläche",
"GUI Authentication User": "Benutzername für Zugang zur Benutzeroberfläche",
"GUI Listen Address": "Addresse der Benutzeroberfläche",
"GUI Listen Addresses": "Adressen der Benutzeroberfläche",
"GUI Theme": "GUI Design",
"General": "Allgemein",
"Generate": "Generieren",
"Global Changes": "Globale Änderungen",
"Global Discovery": "Globale Gerätesuche",
@@ -123,14 +131,17 @@
"Latest Change": "Letzte Änderung",
"Learn more": "Mehr erfahren",
"Listeners": "Zuhörer",
"Loading data...": "Daten werden geladen...",
"Local Discovery": "Lokale Gerätesuche",
"Local State": "Lokaler Status",
"Local State (Total)": "Lokaler Status (Gesamt)",
"Major Upgrade": "Hauptversionsupdate",
"Major Upgrade": "Hauptversionsaktualisierung",
"Master": "Master",
"Maximum Age": "Höchstalter",
"Metadata Only": "Nur Metadaten",
"Minimum Free Disk Space": "Minimal freier Festplattenspeicher",
"Mod. Device": "Änd.-gerät",
"Mod. Time": "Änd.-zeit",
"Move to top of queue": "An den Anfang der Warteschlange setzen",
"Multi level wildcard (matches multiple directory levels)": "Verschachteltes Maskenzeichen (wird für verschachtelte Ordner verwendet)",
"Never": "Nie",
@@ -139,7 +150,8 @@
"Newest First": "Neueste zuerst",
"No": "Nein",
"No File Versioning": "Keine Dateiversionierung",
"No upgrades": "Keine Updates",
"No files will be deleted as a result of this operation.": "Durch diesen Vorgang werden keine Dateien gelöscht.",
"No upgrades": "Keine Aktualisierungen",
"Normal": "Normal",
"Notice": "Hinweis",
"OK": "OK",
@@ -153,27 +165,30 @@
"Override Changes": "Änderungen überschreiben",
"Path": "Pfad",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Pfad, an dem die automatisch angenommenen Ordner erstellt werden, ebenso wie der standardmäßig vorgeschlagene Pfad beim Hinzufügen neuer Ordner über die UI. Tilde-Buchstaben (~) entspricht {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pfad in dem Versionen gespeichert werden sollen (leer lassen, wenn der Standard .stversions Ordner für den geteilten Ordner verwendet werden soll).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem Versionen gespeichert werden sollen (leer lassen, wenn der Standard .stversions Ordner für den geteilten Ordner verwendet werden soll).",
"Pause": "Pause",
"Pause All": "Alles pausieren",
"Paused": "Pausiert",
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie ein neues Hauptversionsupdate installieren.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bitte setze einen Benutzer und ein Passwort für das GUI in den Einstellungen.",
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungshinweise bevor Sie eine Hauptversionsaktualisierung installieren.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bitte lege einen Benutzer und ein Passwort für die Benutzeroberfläche in den Einstellungen fest.",
"Please wait": "Bitte warten",
"Prefix indicating that the file can be deleted if preventing directory removal": "Präfix, das anzeigt, dass die Datei gelöscht werden kann, wenn sie die Entfernung des Ordners verhindert",
"Prefix indicating that the pattern should be matched without case sensitivity": "Präfix, das anzeigt, dass das Muster ohne Beachtung der Groß-/Kleinschreibung übereinstimmen soll",
"Preview": "Vorschau",
"Preview Usage Report": "Vorschau des Nutzungsberichts",
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"RAM Utilization": "RAM Auslastung",
"RAM Utilization": "RAM-Auslastung",
"Random": "Zufall",
"Recent Changes": "Letzte Änderungen",
"Reduced by ignore patterns": "Durch Ignoriermuster reduziert",
"Release Notes": "Veröffentlichungsnotizen",
"Release Notes": "Veröffentlichungshinweise",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Veröffentlichungskandidaten enthalten die neuesten Funktionen und Verbesserungen. Sie gleichen den üblichen zweiwöchentlichen Syncthing-Veröffentlichungen.",
"Remote Devices": "Fern-Geräte",
"Remove": "Entfernen",
"Remove Device": "Gerät entfernen",
"Remove Folder": "Ordner entfernen",
"Required identifier for the folder. Must be the same on all cluster devices.": "Erforderlicher Bezeichner für den Ordner. Muss auf allen Verbund-Geräten gleich sein.",
"Rescan": "Neu scannen",
"Rescan All": "Alle neu scannen",
@@ -188,8 +203,8 @@
"Scan Time Remaining": "Zeit für Scan verbleibend",
"Scanning": "Scannen",
"See external versioner help for supported templated command line parameters.": "Siehe externe Versionshilfe für unterstützte Befehlszeilenparameter.",
"See external versioning help for supported templated command line parameters.": "Siehe externe Versionshilfe für unterstützte Befehlszeilenparameter.",
"Select a version": "Version auswählen",
"See external versioning help for supported templated command line parameters.": "Siehe Hilfe zur externen Versionierung für unterstützte Befehlszeilenparameter.",
"Select a version": "Wählen Sie eine Version",
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du diesen Ordner teilen willst.",
"Select the folders to share with this device.": "Wähle die Ordner aus, die Du mit diesem Gerät teilen möchtest",
"Send & Receive": "Senden & empfangen",
@@ -203,13 +218,14 @@
"Shared With": "Geteilt mit",
"Show ID": "Eigene Kennung",
"Show QR": "Zeige QR Code",
"Show diff with previous version": "Zeige den Unterschied zur vorigen Version",
"Show diff with previous version": "Unterschied zur vorherigen Version 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ätekennung im Verbund-Status angezeigt. Wird als optionaler Standardname an andere Geräte bekannt gegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird auf diesem Gerät als Gerätename angezeigt und an die anderen Geräte im Geräte-Verbund weitergegeben. Wenn kein Gerätename anegegeben wird, wird der Name des entfernten Gerätes genommen.",
"Shutdown": "Herunterfahren",
"Shutdown Complete": "Vollständig Heruntergefahren",
"Simple File Versioning": "Einfache Dateiversionierung",
"Single level wildcard (matches within a directory only)": "Einzelnes Maskenzeichen (wird für einen einzelnen Ordner verwendet)",
"Size": "Größe",
"Smallest First": "Kleinstes zuerst",
"Source Code": "Quellcode",
"Stable releases and release candidates": "Stabile Veröffentlichungen und Veröffentlichungskandidaten",
@@ -254,24 +270,27 @@
"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 Device": "Dieses Gerät",
"This can easily give hackers access to read and change any files on your computer.": "Dies kann dazu führen, dass Unberechtigte relativ einfach auf Ihre Dateien zugreifen und diese ändern können.",
"This is a major version upgrade.": "Dies ist ein neues Hauptversionsupdate.",
"This is a major version upgrade.": "Dies ist eine Hauptversionsaktualisierung.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Diese Einstellung regelt den freien Speicherplatz, der für den Systemordner (d.h. Indexdatenbank) erforderlich ist.",
"Time": "Zeit",
"Time the item was last modified": "Zeit der letzten Änderung des Elements",
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
"Type": "Typ",
"Undecided (will prompt)": "Unentschieden (wird nachgefragt werden)",
"Unavailable": " Nicht verfügbar",
"Unavailable/Disabled by administrator or maintainer": "Nicht verfügbar / Deaktiviert von Administrator oder Eigentümer",
"Undecided (will prompt)": "Unentschlossen (wird nachgefragt)",
"Unknown": "Unbekannt",
"Unshared": "Ungeteilt",
"Unused": "Ungenutzt",
"Up to Date": "Aktuell",
"Updated": "Aktualisiert",
"Upgrade": "Update",
"Upgrade To {%version%}": "Update auf {{version}}",
"Upgrade": "Aktualisierung",
"Upgrade To {%version%}": "Aktualisierung auf {{version}}",
"Upgrading": "Wird aktualisiert",
"Upload Rate": "Upload",
"Uptime": "Betriebszeit",
"Usage reporting is always enabled for candidate releases.": "Nutzungsbericht ist für Veröffentlichungskandidaten immer aktiviert.",
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche verwenden",
"Version": "Version",
"Versions Path": "Versionierungspfad",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Dateiversionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die angegebene Höchstzahl an Dateien erreicht ist.",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Ανώνυμα στοιχεία χρήσης",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Η μορφή της αναφοράς ανώνυμων στοιχείων χρήσης έχει αλλάξει. Επιθυμείτε να μεταβείτε στη νέα μορφή;",
"Any devices configured on an introducer device will be added to this device as well.": "Αν δηλωθεί σαν «βασικός κόμβος», τότε όλες οι συσκευές που είναι δηλωμένες εκεί θα υπάρχουν και στον τοπικό κόμβο.",
"Are you sure you want to remove device {%name%}?": "Σίγουρα επιθυμείτε να αφαιρέσετε τη συσκευή {{name}};",
"Are you sure you want to remove folder {%label%}?": "Σίγουρα επιθυμείτε να αφαιρέσετε τον φάκελο {{label}};",
"Auto Accept": "Αυτόματη αποδοχή",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Για τις αυτόματες αναβαθμίσεις μπορείτε πλέον να επιλέξετε μεταξύ σταθερών εκδόσεων και υποψήφιων εκδόσεων.",
"Automatic upgrades": "Αυτόματη αναβάθμιση",
"Automatically create or share folders that this device advertises at the default path.": "Αυτόματη δημιουργία ή κοινή χρήση φακέλων τους οποίους ανακοινώνει αυτή η συσκευή στην προκαθορισμένη διαδρομή.",
"Be careful!": "Με προσοχή!",
"Bugs": "Bugs",
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
@@ -41,18 +45,21 @@
"Configured": "Βάσει ρύθμισης",
"Connection Error": "Σφάλμα σύνδεσης",
"Connection Type": "Τύπος Σύνδεσης",
"Connections": "Συνδέσεις",
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 για τους παρακάτω συνεισφέροντες:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 για τους παρακάτω συνεισφέροντες:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Δημιουργία προτύπων αγνόησης, αντικατάσταση του υπάρχοντος αρχείου στο {{path}}.",
"Danger!": "Προσοχή!",
"Default Folder Path": "Προκαθορισμένη διαδρομή φακέλων",
"Deleted": "Διαγραμμένα",
"Device": "Συσκευή",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Η συσκευή \"{{name}}\" ({{device}} στη διεύθυνση {{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής;",
"Device ID": "Ταυτότητα συσκευής",
"Device Identification": "Ταυτότητα συσκευής",
"Device Name": "Όνομα συσκευής",
"Device that last modified the item": "Συσκευή από την οποία πραγματοποιήθηκε η τελευταία τροποποίηση του στοιχείου",
"Devices": "Συσκευές",
"Disabled": "Απενεργοποιημένη",
"Disconnected": "Αποσυνδεδεμένη",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Διεύθυνση ακρόασης γραφικού περιβάλλοντος (GUI)",
"GUI Listen Addresses": "Διευθύνσεις από τις οποίες θα είναι προσβάσιμη η διεπαφή",
"GUI Theme": "Θέμα GUI",
"General": "Γενικά",
"Generate": "Δημιουργία",
"Global Changes": "Συνολικές αλλαγές",
"Global Discovery": "Καθολική ανεύρεση",
@@ -123,6 +131,7 @@
"Latest Change": "Τελευταία αλλαγή",
"Learn more": "Μάθετε περισσότερα",
"Listeners": "Ακροατές",
"Loading data...": "Φόρτωση δεδομένων...",
"Local Discovery": "Τοπική ανεύρεση",
"Local State": "Τοπική κατάσταση",
"Local State (Total)": "Τοπική κατάσταση (συνολικά)",
@@ -131,6 +140,8 @@
"Maximum Age": "Μέγιστη ηλικία",
"Metadata Only": "Μόνο μεταδεδομένα",
"Minimum Free Disk Space": "Ελάχιστος ελεύθερος αποθηκευτικός χώρος",
"Mod. Device": "Συσκευή τροποποίησης",
"Mod. Time": "Ώρα τροποποίησης",
"Move to top of queue": "Μεταφορά στην αρχή της λίστας",
"Multi level wildcard (matches multiple directory levels)": "Τελεστής μπαλαντέρ (*) για πολλά επίπεδα (χρησιμοποιείται για εμφωλευμένους φακέλους)",
"Never": "Ποτέ",
@@ -139,6 +150,7 @@
"Newest First": "Το νεότερο πρώτα",
"No": "Όχι",
"No File Versioning": "Να μην τηρούνται εκδόσεις",
"No files will be deleted as a result of this operation.": "Δεν πρόκειται να διαγραφούν αρχεία με αυτή την ενέργεια.",
"No upgrades": "Απενεργοποιημένες",
"Normal": "Κανονικός",
"Notice": "Σημείωση",
@@ -153,6 +165,7 @@
"Override Changes": "Να αντικατασταθούν οι αλλαγές",
"Path": "Μονοπάτι",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Μονοπάτι του φακέλου σε αυτόν τον υπολογιστή. Αν δεν υπάρχει θα δημιουργηθεί. Η περισπωμένη (~) μπορεί να μπει σαν συντόμευση για το",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Διαδρομή όπου θα δημιουργούνται φάκελοι μετά από την αυτόματη αποδοχή, καθώς και προτεινόμενη διανομή κατά την προσθήκη φακέλων μέσω του UI. Ο χαρακτήρας της περισπωμένης (~) μετατρέπεται στο {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Ο κατάλογος στον οποίο θα αποθηκεύονται οι εκδόσεις των αρχείων (αν δεν οριστεί, θα αποθηκεύονται στον υποκατάλογο .stversions)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ο φάκελος στον οποίο θα αποθηκεύονται οι εκδόσεις των αρχείων (αν δεν οριστεί θα αποθηκεύονται στον υποφάκελο .stversions)",
"Pause": "Παύση",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Οι υποψήφιες εκδόσεις περιέχουν τις νεότερες λειτουργίες και επιδιορθώσεις σφαλμάτων, όπως και οι παραδοσιακές δισεβδομαδιαίες εκδόσεις του Syncthing.",
"Remote Devices": "Απομακρυσμένες συσκευές",
"Remove": "Αφαίρεση",
"Remove Device": "Αφαίρεση συσκευής",
"Remove Folder": "Αφαίρεση φακέλου",
"Required identifier for the folder. Must be the same on all cluster devices.": "Απαραίτητο αναγνωριστικό για τον φάκελο. Πρέπει να είναι το ίδιο σε όλες τις συσκευές με τις οποίες διαμοιράζεται ο φάκελος αυτός.",
"Rescan": "Έλεγξε για αλλαγές",
"Rescan All": "Έλεγξέ τα όλα για αλλαγές",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Πλήρης απενεργοποίηση",
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
"Single level wildcard (matches within a directory only)": "Τελεστής μπαλαντέρ (*) για ένα επίπεδο (χρησιμοποιείται για έναν φάκελο μόνο)",
"Size": "Μέγεθος",
"Smallest First": "Το μικρότερο πρώτα",
"Source Code": "Πηγαίος κώδικας",
"Stable releases and release candidates": "Σταθερές εκδόσεις και υποψήφιες εκδόσεις.",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Αυτή είναι μια σημαντική αναβάθμιση.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Αυτή η επιλογή καθορίζει τον ελεύθερο χώρο που θα παραμένει ελεύθερος στον δίσκο όπου βρίσκεται ο κατάλογος της εφαρμογής (και συνεπώς η βάση δεδομένων ευρετηρίων).",
"Time": "Χρόνος",
"Time the item was last modified": "Ώρα τελευταίας τροποποίησης του στοιχείου",
"Trash Can File Versioning": "Τήρηση εκδόσεων κάδου ανακύκλωσης",
"Type": "Τύπος",
"Unavailable": "Μη διαθέσιμο",
"Unavailable/Disabled by administrator or maintainer": "Μη διαθέσιμο/απενεργοποιημένο από τον διαχειριστή ή υπεύθυνο διανομής",
"Undecided (will prompt)": "Μη καθορισμένη (θα γίνει ερώτηση)",
"Unknown": "Άγνωστο",
"Unshared": "Δε μοιράζεται",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"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.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
"Automatic upgrades": "Automatic upgrades",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Be careful!",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilisation",
@@ -41,18 +45,21 @@
"Configured": "Configured",
"Connection Error": "Connection Error",
"Connection Type": "Connection Type",
"Connections": "Connections",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Danger!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Deleted",
"Device": "Device",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Device ID",
"Device Identification": "Device Identification",
"Device Name": "Device Name",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Devices",
"Disabled": "Disabled",
"Disconnected": "Disconnected",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI Listen Addresses",
"GUI Theme": "GUI Theme",
"General": "General",
"Generate": "Generate",
"Global Changes": "Global Changes",
"Global Discovery": "Global Discovery",
@@ -123,6 +131,7 @@
"Latest Change": "Latest Change",
"Learn more": "Learn more",
"Listeners": "Listeners",
"Loading data...": "Loading data...",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Local State (Total)": "Local State (Total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maximum Age",
"Metadata Only": "Metadata Only",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"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": "Never",
@@ -139,6 +150,7 @@
"Newest First": "Newest First",
"No": "No",
"No File Versioning": "No File Versioning",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "No upgrades",
"Normal": "Normal",
"Notice": "Notice",
@@ -153,6 +165,7 @@
"Override Changes": "Override Changes",
"Path": "Path",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
"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).",
"Pause": "Pause",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional fortnightly Syncthing releases.",
"Remote Devices": "Remote Devices",
"Remove": "Remove",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.",
"Rescan": "Rescan",
"Rescan All": "Rescan All",
@@ -210,6 +225,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)",
"Size": "Size",
"Smallest First": "Smallest First",
"Source Code": "Source Code",
"Stable releases and release candidates": "Stable releases and release candidates",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "This is a major version upgrade.",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Time",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Rubbish Bin File Versioning",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Unknown",
"Unshared": "Unshared",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"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.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
"Automatic upgrades": "Automatic upgrades",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Be careful!",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilization",
@@ -41,18 +45,22 @@
"Configured": "Configured",
"Connection Error": "Connection Error",
"Connection Type": "Connection Type",
"Connections": "Connections",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Danger!",
"Debugging Facilities": "Debugging Facilities",
"Default Folder Path": "Default Folder Path",
"Deleted": "Deleted",
"Device": "Device",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Device ID",
"Device Identification": "Device Identification",
"Device Name": "Device Name",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Devices",
"Disabled": "Disabled",
"Disconnected": "Disconnected",
@@ -99,6 +107,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI Listen Addresses",
"GUI Theme": "GUI Theme",
"General": "General",
"Generate": "Generate",
"Global Changes": "Global Changes",
"Global Discovery": "Global Discovery",
@@ -123,14 +132,21 @@
"Latest Change": "Latest Change",
"Learn more": "Learn more",
"Listeners": "Listeners",
"Loading data...": "Loading data...",
"Loading...": "Loading...",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Local State (Total)": "Local State (Total)",
"Log": "Log",
"Log tailing paused. Click here to continue.": "Log tailing paused. Click here to continue.",
"Logs": "Logs",
"Major Upgrade": "Major Upgrade",
"Master": "Master",
"Maximum Age": "Maximum Age",
"Metadata Only": "Metadata Only",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"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": "Never",
@@ -139,6 +155,7 @@
"Newest First": "Newest First",
"No": "No",
"No File Versioning": "No File Versioning",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "No upgrades",
"Normal": "Normal",
"Notice": "Notice",
@@ -153,6 +170,7 @@
"Override Changes": "Override Changes",
"Path": "Path",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
"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).",
"Pause": "Pause",
@@ -174,6 +192,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.",
"Remote Devices": "Remote Devices",
"Remove": "Remove",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.",
"Rescan": "Rescan",
"Rescan All": "Rescan All",
@@ -210,6 +230,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)",
"Size": "Size",
"Smallest First": "Smallest First",
"Source Code": "Source Code",
"Stable releases and release candidates": "Stable releases and release candidates",
@@ -257,8 +278,11 @@
"This is a major version upgrade.": "This is a major version upgrade.",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Time",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Trash Can File Versioning",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Unknown",
"Unshared": "Unshared",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonima Raporto de Uzado",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Ajna aparatoj agorditaj sur enkondukanto aparato estos ankaŭ aldonita al ĉi tiu aparato.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Aŭtomata ĝisdatigo nun proponas la elekton inter stabilaj eldonoj kaj kandidataj eldonoj.",
"Automatic upgrades": "Aŭtomataj ĝisdatigoj",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Atentu!",
"Bugs": "Bugoj",
"CPU Utilization": "Ĉefprocesoro Uzo",
@@ -41,18 +45,21 @@
"Configured": "Agordita",
"Connection Error": "Eraro de Konekto",
"Connection Type": "Tipo de Konekto",
"Connections": "Connections",
"Copied from elsewhere": "Kopiita el aliloke",
"Copied from original": "Kopiita el la originalo",
"Copyright © 2014-2016 the following Contributors:": "Kopirajto © 2014-2016 por la sekvantaj Kontribuantoj:",
"Copyright © 2014-2017 the following Contributors:": "Kopirajto © 2014-2017 por la sekvantaj Kontribuantoj:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Kreante ignori skemojn, anstataŭige ekzistantan dosieron ĉe {{path}}.",
"Danger!": "Danĝero!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Forigita",
"Device": "Aparato",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Aparato \"{{name}}\" ({{device}} ĉe {{address}}) volas konekti. Aldoni la novan aparaton?",
"Device ID": "Aparato ID",
"Device Identification": "Identigo de Aparato",
"Device Name": "Nomo de Aparato",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Aparatoj",
"Disabled": "Disabled",
"Disconnected": "Malkonektita",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "Aŭskultado de Adresoj en Grafika Interfaco",
"GUI Theme": "Etoso de Grafika Interfaco",
"General": "General",
"Generate": "Generi",
"Global Changes": "Kompletaj Ŝanĝoj",
"Global Discovery": "Kompleta Malkovro",
@@ -123,6 +131,7 @@
"Latest Change": "Lasta Ŝanĝo",
"Learn more": "Lerni pli",
"Listeners": "Aŭskultantoj",
"Loading data...": "Loading data...",
"Local Discovery": "Loka Malkovro",
"Local State": "Loka Stato",
"Local State (Total)": "Loka Stato (Tuta)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksimuma Aĝo",
"Metadata Only": "Nur Metadatumoj",
"Minimum Free Disk Space": "Minimuma Libera Diskospaco",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Movi supren en la atendovico",
"Multi level wildcard (matches multiple directory levels)": "Multi nivelo ĵokero (egalas multoblajn dosierujaj niveloj)",
"Never": "Neniam",
@@ -139,6 +150,7 @@
"Newest First": "Plejnova Unue",
"No": "Ne",
"No File Versioning": "Sen Dosiera Versionado",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Sen ĝisdatigoj",
"Normal": "Normala",
"Notice": "Avizo",
@@ -153,6 +165,7 @@
"Override Changes": "Transpasi Ŝanĝojn",
"Path": "Vojo",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Vojo de la dosierujo en la loka komputilo. Kreiĝos se ne ekzistas. La tilda signo (~) povas esti uzata kiel mallongigilo por",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Vojo kies versioj devus esti stokitaj (lasi malplena por la defaŭlta .stversiaj dosierujoj en la dividita dosierujo).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Vojo kies vesioj estas konservitaj (lasi malplena por la defaŭlta .stversiaj dosierujoj en la dosierujo).",
"Pause": "Paŭzu",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidataj eldonoj enhavas la lastajn trajtojn kaj korektojn. Ili estas similaj al la tradiciaj dusemajnaj Syncthing ĵetoj.",
"Remote Devices": "Foraj Aparatoj",
"Remove": "Forigu",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Nepra identigilo por la dosierujo. Devas esti la sama en ĉiuj aparatoj de la grupo.",
"Rescan": "Reskanu",
"Rescan All": "Reskanu Ĉion",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Sistemfermu tute",
"Simple File Versioning": "Simpla Versionado de Dosieroj",
"Single level wildcard (matches within a directory only)": "Ununura nivelo ĵokero (egalas nur ene de dosierujo)",
"Size": "Size",
"Smallest First": "Plej Malgranda Unue",
"Source Code": "Fontkodo",
"Stable releases and release candidates": "Stabilaj eldonoj kaj kandidataj eldonoj",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Ĉi tio estas ĉefversio ĝisdatigita.",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Tempo",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Rubujo ebligas Dosieran Versionadon",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Nekonata",
"Unshared": "Nekomunigita",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"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.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Ahora la actualización automática permite elegir entre versiones estables o versiones candidatas.",
"Automatic upgrades": "Actualizaciones automáticas",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "¡Ten cuidado!",
"Bugs": "Errores",
"CPU Utilization": "Uso de CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurado",
"Connection Error": "Error de conexión",
"Connection Type": "Tipo de conexión",
"Connections": "Connections",
"Copied from elsewhere": "Copiado de otro sitio",
"Copied from original": "Copiado del original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes Colaboradores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 Los siguientes colaboradores:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Crear patrones a ignorar, sobreescribiendo un fichero existente en {{path}}.",
"Danger!": "¡Peligro!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Eliminado",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "El dispositivo \"{{name}}\" ({{device}} en la dirección {{address}}) quiere conectarse. Añadir nuevo dispositivo?",
"Device ID": "ID del Dispositivo",
"Device Identification": "Identificación del Dispositivo",
"Device Name": "Nombre del Dispositivo",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositivos",
"Disabled": "Disabled",
"Disconnected": "Desconectado",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Dirección de Escucha del GUI.",
"GUI Listen Addresses": "Direcciones de escucha de la Interfaz Gráfica de Usuario (GUI)",
"GUI Theme": "Tema GUI",
"General": "General",
"Generate": "Generar",
"Global Changes": "Cambios globales",
"Global Discovery": "Descubrimiento global",
@@ -123,6 +131,7 @@
"Latest Change": "Último Cambio",
"Learn more": "Saber más",
"Listeners": "Oyentes",
"Loading data...": "Loading data...",
"Local Discovery": "Descubrimiento local",
"Local State": "Estado local",
"Local State (Total)": "Estado Local (Total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Edad máxima",
"Metadata Only": "Sólo metadatos",
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"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",
@@ -139,6 +150,7 @@
"Newest First": "El más nuevo primero",
"No": "No",
"No File Versioning": "Sin versionado de fichero",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Sin actualizaciones",
"Normal": "Normal",
"Notice": "Aviso",
@@ -153,6 +165,7 @@
"Override Changes": "Anular cambios",
"Path": "Parche",
"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 new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "La ruta donde las versiones deben ser almacenadas (dejar vacío para el directorio .stversions por defecto en la carpeta compartida).",
"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\").",
"Pause": "Pausar",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Las versiones candidatas contienen las últimas funcionalidades y correcciones. Son similares a las tradicionales versiones bisemanales de Syncthing.",
"Remote Devices": "Otros dispositivos",
"Remove": "Eliminar",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador requerido para la carpeta. Debe ser el mismo en todos los dispositivos del clúster.",
"Rescan": "Volver a analizar",
"Rescan All": "Volver a analizar Todo",
@@ -210,6 +225,7 @@
"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)",
"Size": "Size",
"Smallest First": "El más pequeño primero",
"Source Code": "Código fuente",
"Stable releases and release candidates": "Versiones estables y versiones candidatas",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Hay una actualización importante.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este ajuste controla el espacio libre necesario en el disco principal (por ejemplo, el índice de la base de datos).",
"Time": "Hora",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Versionado de archivos de la papelera",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Desconocido",
"Unshared": "No compartido",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"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.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Ahora la actualización automática permite elegir entre versiones estables o versiones candidatas.",
"Automatic upgrades": "Actualizaciones automáticas",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "¡Ten cuidado!",
"Bugs": "Errores",
"CPU Utilization": "Uso de CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurado",
"Connection Error": "Error de conexión",
"Connection Type": "Tipo de conexión",
"Connections": "Connections",
"Copied from elsewhere": "Copiado de otro sitio",
"Copied from original": "Copiado del original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes Colaboradores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 Los siguientes colaboradores:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Crear patrones a ignorar, sobreescribiendo un fichero existente en {{path}}.",
"Danger!": "¡Peligro!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Eliminado",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "El dispositivo \"{{name}}\" ({{device}} en la dirección {{address}}) quiere conectarse. Añadir nuevo dispositivo?",
"Device ID": "ID del Dispositivo",
"Device Identification": "Identificación del Dispositivo",
"Device Name": "Nombre del Dispositivo",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositivos",
"Disabled": "Disabled",
"Disconnected": "Desconectado",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Dirección de Escucha del GUI.",
"GUI Listen Addresses": "Direcciones de escucha de la Interfaz Gráfica de Usuario (GUI)",
"GUI Theme": "Tema GUI",
"General": "General",
"Generate": "Generar",
"Global Changes": "Cambios globales",
"Global Discovery": "Descubrimiento global",
@@ -123,6 +131,7 @@
"Latest Change": "Último Cambio",
"Learn more": "Saber más",
"Listeners": "Oyentes",
"Loading data...": "Loading data...",
"Local Discovery": "Descubrimiento local",
"Local State": "Estado local",
"Local State (Total)": "Estado Local (Total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Edad máxima",
"Metadata Only": "Sólo metadatos",
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"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",
@@ -139,6 +150,7 @@
"Newest First": "El más nuevo primero",
"No": "No",
"No File Versioning": "Sin versionado de fichero",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Sin actualizaciones",
"Normal": "Normal",
"Notice": "Aviso",
@@ -153,6 +165,7 @@
"Override Changes": "Anular cambios",
"Path": "Parche",
"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 new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "La ruta donde las versiones deben ser almacenadas (dejar vacío para el directorio .stversions por defecto en la carpeta compartida).",
"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\").",
"Pause": "Pausar",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Las versiones candidatas contienen las últimas funcionalidades y correcciones. Son similares a las tradicionales versiones bisemanales de Syncthing.",
"Remote Devices": "Otros dispositivos",
"Remove": "Eliminar",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador requerido para la carpeta. Debe ser el mismo en todos los dispositivos del clúster.",
"Rescan": "Volver a analizar",
"Rescan All": "Volver a analizar Todo",
@@ -210,6 +225,7 @@
"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)",
"Size": "Size",
"Smallest First": "El más pequeño primero",
"Source Code": "Código fuente",
"Stable releases and release candidates": "Versiones estables y versiones candidatas",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Hay una actualización importante.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este ajuste controla el espacio libre necesario en el disco principal (por ejemplo, el índice de la base de datos).",
"Time": "Hora",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Versionado de archivos de la papelera",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Desconocido",
"Unshared": "No compartido",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Izenik gabeko erabiltze erreportak",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Sarrarazle deitzen duzun tresna batean gehitua izanen den edozein tresna, zurean ere gehitua izanen da.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Eguneratze automatiko sistemak iraunkor bertsioen eta aitzineko bertsioen artean hautatzea proposatzen du",
"Automatic upgrades": "Eguneratze automatikoak",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Kasu emazu!",
"Bugs": "Akatsak",
"CPU Utilization": "Prozesadorearen erabiltzea",
@@ -41,18 +45,21 @@
"Configured": "Konfiguratua",
"Connection Error": "Konexio hutsa",
"Connection Type": "Konexio mota",
"Connections": "Connections",
"Copied from elsewhere": "Beste nunbaitik kopiatua",
"Copied from original": "Jatorrizkotik kopiatua",
"Copyright © 2014-2016 the following Contributors:": "Copyright 2014-2016, ekarle hauk:",
"Copyright © 2014-2017 the following Contributors:": "Copyright 2014-2017, ekarle hauk:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Baztertze modelo batzuen sortzea, dagoen fitxategiari ordaina ezartzea: {{path}}",
"Danger!": "Lanjera !",
"Default Folder Path": "Default Folder Path",
"Deleted": "Kendua",
"Device": "Tresna",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Tresna \"{{name}}\" ({{device}} {{address}} era) konektatu nahi du. Onhartzen duzu ?",
"Device ID": "Tresnaren ID-a",
"Device Identification": "Tresnaren identifikazioa",
"Device Name": "Tresnaren izena",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Tresnak",
"Disabled": "Disabled",
"Disconnected": "Deskonektatua",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Interfaze grafiko helbidea",
"GUI Listen Addresses": "Interfaze grafiko helbideak",
"GUI Theme": "Interfaze grafiko tema",
"General": "General",
"Generate": "Sortu",
"Global Changes": "Azken aldaketak",
"Global Discovery": "Aurkikuntza orokorra",
@@ -123,6 +131,7 @@
"Latest Change": "Azken aldaketa",
"Learn more": "Gehiago jakiteko",
"Listeners": "Entzungailuak",
"Loading data...": "Loading data...",
"Local Discovery": "Lekuko aurkikuntza",
"Local State": "Lekuko egoera",
"Local State (Total)": "Lekuko egoera (Osoa)",
@@ -131,6 +140,8 @@
"Maximum Age": "Adin gehiena",
"Metadata Only": "Metadatuak bakarrik",
"Minimum Free Disk Space": "Diskoan gutieneko leku libroa ",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Lerro bururat lekuz alda",
"Multi level wildcard (matches multiple directory levels)": "Hein anitzerako jokerra (errepertorio eta azpi errepertorioeri dagokiona)",
"Never": "Sekulan",
@@ -139,6 +150,7 @@
"Newest First": "Berrienak lehenik",
"No": "Ez",
"No File Versioning": "Fitxategi bersioen kontrolik ez",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Eguneratzerik ez",
"Normal": "Normala",
"Notice": "Jakinaraztea",
@@ -153,6 +165,7 @@
"Override Changes": "Aldaketak desegin",
"Path": "Bidexka",
"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": "Lekuko tresnaren partekatzeari buruzko bidea. Ez balitz, asmatu beharko da bat. Programarenari, baitezpadako bide bat sartzen ahal duzu (adibidez \"/home/ni/Sync/Etsenplua\") edo bestenaz bide errelatibo bat (adibidez \"..\\Partekatzeak\\Etsenplua\" - instalazio mugikor batentzat baliagarria). Tildea (~, edo ~+Espazioa Windows XP+Azerty-n) erabil litzateke laburbide gisa",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Kopiak kontserbatzeko bidea (hutsa utz ezazu, .steversioen ohizko bidearentzat, dosier partekatuan)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kopiak kontserbatzeko bidea (hutsa utz ezazu, .stversioen ohizko dosierarentzat, karpeta partekatuan)",
"Pause": "Pausa",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Azken zuzenketak eta funtzionalitateak edukitzen dituzte aitzin-bertsioek. Bi hilabete guziz egiten diren eguneratzeen berdinak dira.",
"Remote Devices": "Beste tresnak",
"Remove": "Kendu",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Partekatzearen erabilzaile izena. Dauden tresna guzietan berdin berdina izan behar du",
"Rescan": "Berriz eskaneatu",
"Rescan All": "Dena berriz eskaneatu",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Gelditua!",
"Simple File Versioning": "Bertsioen segitze sinplifikatua",
"Single level wildcard (matches within a directory only)": "Hein bakar bateko jokerra (karpetaren barnean bakarrik dagokiona)",
"Size": "Size",
"Smallest First": "Ttipienak lehenik",
"Source Code": "Iturri kodea",
"Stable releases and release candidates": "iraunkor eta aintzin-bertsioak",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Aktualizatze garrantzitsu bat da",
"This setting controls the free space required on the home (i.e., index database) disk.": "Behar den espazio kontrolatzen du egokitze honek, zure erabiltzale partekatzea geritzatzen duen diskoan (hori da, indexazio datu-basean)",
"Time": "Ordua",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Zakarrontzia",
"Type": "Mota",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Ezezaguna",
"Unshared": "Partekatua ez dena",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonyymi käyttöraportointi",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"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.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
"Automatic upgrades": "Automaattiset päivitykset",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Ole varovainen!",
"Bugs": "Bugit",
"CPU Utilization": "CPU:n käyttö",
@@ -41,18 +45,21 @@
"Configured": "Konfiguroitu",
"Connection Error": "Yhteysvirhe",
"Connection Type": "Yhteyden tyyppi",
"Connections": "Connections",
"Copied from elsewhere": "Kopioitu muualta",
"Copied from original": "Kopioitu alkuperäisestä lähteestä",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 seuraavat avustajat",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Vaara!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Poistettu",
"Device": "Laite",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Laite \"{{name}}\" {{device}} osoitteessa ({{address}}) haluaa yhdistää. Lisää uusi laite?",
"Device ID": "Laitteen ID",
"Device Identification": "Laitteen tunniste",
"Device Name": "Laitteen nimi",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Laitteet",
"Disabled": "Disabled",
"Disconnected": "Yhteys katkaistu",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI:n kuunteluosoitteet",
"GUI Theme": "GUI Theme",
"General": "General",
"Generate": "Generoi",
"Global Changes": "Global Changes",
"Global Discovery": "Globaali etsintä",
@@ -123,6 +131,7 @@
"Latest Change": "Viimeisin muutos",
"Learn more": "Learn more",
"Listeners": "Kuuntelijat",
"Loading data...": "Loading data...",
"Local Discovery": "Paikallinen etsintä",
"Local State": "Paikallinen tila",
"Local State (Total)": "Paikallinen tila (Yhteensä)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksimi-ikä",
"Metadata Only": "Vain metadata",
"Minimum Free Disk Space": "Vapaan levytilan vähimmäismäärä",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Siirrä jonon alkuun",
"Multi level wildcard (matches multiple directory levels)": "Monitasoinen jokerimerkki (vaikuttaa useassa kansiotasossa)",
"Never": "Ei koskaan",
@@ -139,6 +150,7 @@
"Newest First": "Uusin ensin",
"No": "Ei",
"No File Versioning": "Ei tiedostoversiointia",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Ei päivityksiä",
"Normal": "Normaali kansio",
"Notice": "Huomautus",
@@ -153,6 +165,7 @@
"Override Changes": "Ohita muutokset",
"Path": "Path",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Polku kansioon paikallisella tietokoneella. Kansio luodaan, ellei sitä ole olemassa. Tilde-merkkiä (~) voidaan käyttää oikotienä polulle",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
"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).",
"Pause": "Keskeytä",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.",
"Remote Devices": "Laitteet",
"Remove": "Poista",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Pakollinen tunniste kansiolle, jonka täytyy olla sama kaikilla laitteilla.",
"Rescan": "Skannaa uudelleen",
"Rescan All": "Skannaa kaikki uudelleen",
@@ -210,6 +225,7 @@
"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ä)",
"Size": "Size",
"Smallest First": "Pienin ensin",
"Source Code": "Lähdekoodi",
"Stable releases and release candidates": "Vakaat julkaisut ja julkaisuehdokkaat",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Tämä on pääversion päivitys.",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Aika",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Roskakorin tiedostoversiointi",
"Type": "Tyyppi",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Tuntematon",
"Unshared": "Jakamaton",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Lui permettre d'ajouter et enlever des membres à toutes mes listes de membres de partages dont il fait partie (ceci permet de créer toutes les liaisons point à point possibles en complétant mes listes par les siennes, meilleur débit de réception par cumul des débits d'envoi, indépendance vis à vis de l'introducteur, etc).",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Le système de mise à jour automatique propose le choix entre versions stables et versions préliminaires.",
"Automatic upgrades": "Mises à jour automatiques",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Faites attention !",
"Bugs": "Bugs",
"CPU Utilization": "Utilisation du CPU",
@@ -41,18 +45,21 @@
"Configured": "Configuré",
"Connection Error": "Erreur de connexion",
"Connection Type": "Type de connexion",
"Connections": "Connections",
"Copied from elsewhere": "Copié d'ailleurs",
"Copied from original": "Copié depuis l'original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016, les contributeurs suivants:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017, les contributeurs suivants :",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Création de masques d'exclusion, remplacement du fichier existant : {{path}}.",
"Danger!": "Attention !",
"Default Folder Path": "Default Folder Path",
"Deleted": "Supprimé",
"Device": "Appareil",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "L'appareil \"{{name}}\" ({{device}} à l'IP {{address}}) souhaite se connecter. L'acceptez-vous ?",
"Device ID": "ID de l'appareil",
"Device Identification": "Identifiant de l'appareil",
"Device Name": "Nom de l'appareil",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Appareil",
"Disabled": "Disabled",
"Disconnected": "Déconnecté",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "Adresses de l'interface (GUI)",
"GUI Theme": "Thème graphique",
"General": "General",
"Generate": "Générer",
"Global Changes": "Derniers changements",
"Global Discovery": "Découverte globale",
@@ -123,6 +131,7 @@
"Latest Change": "Dernier changement",
"Learn more": "En savoir plus",
"Listeners": "Systèmes à l'écoute",
"Loading data...": "Loading data...",
"Local Discovery": "Découverte locale",
"Local State": "État local",
"Local State (Total)": "État local (Total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Ancienneté maximum",
"Metadata Only": "Métadonnées uniquement",
"Minimum Free Disk Space": "Espace disque libre minimum",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Déplacer en haut de la file",
"Multi level wildcard (matches multiple directory levels)": "Joker multi niveaux (correspond aux répertoires et sous-répertoires)",
"Never": "Jamais",
@@ -139,6 +150,7 @@
"Newest First": "Les plus récents en premier",
"No": "Non",
"No File Versioning": "Pas de préservation",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Pas de mises à jour",
"Normal": "Normal",
"Notice": "Notification",
@@ -153,6 +165,7 @@
"Override Changes": "Écraser les changements",
"Path": "Chemin",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire à partager dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire partagé).\nChemin relatif ou absolu (recommandé), mais dans un répertoire non synchronisé (par masque ou hors du chemin du partage).\nSur la même partition ou système de fichiers (recommandé).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire partagé).\nChemin relatif ou absolu (recommandé), mais dans un répertoire non synchronisé (par masque ou hors du chemin du partage).\nSur la même partition ou système de fichiers (recommandé).",
"Pause": "Pause",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Les versions préliminaires contiennent les dernières fonctionnalités et derniers correctifs. Elles sont identiques aux traditionnelles mises à jour bimensuelles.",
"Remote Devices": "Autres appareils",
"Remove": "Enlever",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur tous les appareils concernés.",
"Rescan": "Réanalyser",
"Rescan All": "Tout réanalyser",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Arrêté !",
"Simple File Versioning": "Suivi simplifié des versions",
"Single level wildcard (matches within a directory only)": "Joker à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Size": "Size",
"Smallest First": "Les plus petits d'abord",
"Source Code": "Code source",
"Stable releases and release candidates": "Versions stables et préliminaires",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Il s'agit d'une mise à jour majeure.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Ce réglage contrôle l'espace disque requis dans le disque qui abrite votre répertoire utilisateur (pour la base de données d'indexation).",
"Time": "Heure",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Style poubelle",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Inconnu",
"Unshared": "Non partagé",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Le format du rapport anonyme d'utilisation a changé. Voulez-vous passer au nouveau format ?",
"Any devices configured on an introducer device will be added to this device as well.": "Lui permettre d'ajouter et enlever des membres à toutes mes listes de membres des partages dont il fait (ou fera !) partie (ceci permet de créer toutes les liaisons point à point possibles en complétant mes listes par les siennes, meilleur débit de réception par cumul des débits d'envoi, indépendance vis à vis de l'introducteur, etc).",
"Are you sure you want to remove device {%name%}?": "Êtes-vous sûr de vouloir supprimer l'appareil {{name}} ?",
"Are you sure you want to remove folder {%label%}?": "Êtes-vous sûr de vouloir supprimer le partage {{label}} ?",
"Auto Accept": "Accepter automatiquement",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Le système de mise à jour automatique propose le choix entre versions stables et versions préliminaires.",
"Automatic upgrades": "Mises à jour automatiques",
"Automatically create or share folders that this device advertises at the default path.": "Créer ou partager automatiquement dans le chemin par défaut les partages que cet appareil annonce.",
"Be careful!": "Faites attention !",
"Bugs": "Bugs",
"CPU Utilization": "Utilisation du CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurée",
"Connection Error": "Erreur de connexion",
"Connection Type": "Type de connexion",
"Connections": "Connexions",
"Copied from elsewhere": "Copié d'ailleurs",
"Copied from original": "Copié depuis l'original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016, les contributeurs sont:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017, les contributeurs sont:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Création de masques d'exclusion, remplacement du fichier existant : {{path}}.",
"Danger!": "Attention !",
"Default Folder Path": "Chemin par défaut des partages",
"Deleted": "Supprimé",
"Device": "Appareil",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{device}}), appareil actuellement à {{address}}, demande à se connecter.\nAcceptez-vous de l'ajouter à votre liste d'appareils connus ?",
"Device ID": "ID de l'appareil",
"Device Identification": "Identifiant de l'appareil",
"Device Name": "Nom convivial local de l'appareil",
"Device that last modified the item": "Dernier appareil modificateur",
"Devices": "Appareils",
"Disabled": "Désactivé",
"Disconnected": "Déconnecté",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Adresse d'écoute du GUI",
"GUI Listen Addresses": "Adresses de l'interface (GUI)",
"GUI Theme": "Thème graphique",
"General": "Général",
"Generate": "Générer",
"Global Changes": "Derniers changements",
"Global Discovery": "Découverte globale",
@@ -123,6 +131,7 @@
"Latest Change": "Dernier changement",
"Learn more": "En savoir plus",
"Listeners": "Systèmes en écoute",
"Loading data...": "Chargement des données...",
"Local Discovery": "Découverte locale",
"Local State": "État local",
"Local State (Total)": "État local (Total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Ancienneté maximum",
"Metadata Only": "Métadonnées uniquement",
"Minimum Free Disk Space": "Espace disque libre minimum",
"Mod. Device": "Appareil modificateur",
"Mod. Time": "Date de modification",
"Move to top of queue": "Déplacer en haut de la file",
"Multi level wildcard (matches multiple directory levels)": "Joker multi niveaux (correspond aux répertoires et sous-répertoires)",
"Never": "Jamais",
@@ -139,6 +150,7 @@
"Newest First": "Les plus récents en premier",
"No": "Non",
"No File Versioning": "Pas de préservation",
"No files will be deleted as a result of this operation.": "Aucun fichier ne sera supprimé à la suite de cette opération.",
"No upgrades": "Pas de mises à jour",
"Normal": "Normal",
"Notice": "Notification",
@@ -153,6 +165,7 @@
"Override Changes": "Écraser les changements",
"Path": "Chemin",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire à partager dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin suggéré lors de l'enregistrement des nouveaux partages via cette interface graphique. Le caractère tilde (~) est un raccourci pour {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire partagé).\nChemin relatif ou absolu (recommandé), mais dans un répertoire non synchronisé (par masque ou hors du chemin du partage).\nSur la même partition ou système de fichiers (recommandé).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire partagé).\nChemin relatif ou absolu (recommandé), mais dans un répertoire non synchronisé (par masque ou hors du chemin du partage).\nSur la même partition ou système de fichiers (recommandé).",
"Pause": "Pause",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Les versions préliminaires contiennent les dernières fonctionnalités et derniers correctifs. Elles sont identiques aux traditionnelles mises à jour bimensuelles.",
"Remote Devices": "Autres appareils",
"Remove": "Supprimer",
"Remove Device": "Supprimer l'appareil",
"Remove Folder": "Supprimer le partage",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur tous les appareils concernés (généré aléatoirement, mais modifiable à la création).",
"Rescan": "Réanalyser",
"Rescan All": "Tout réanalyser",
@@ -189,9 +204,9 @@
"Scanning": "Analyse en cours",
"See external versioner help for supported templated command line parameters.": "Voir l'aide sur la préservation externe des fichiers pour les paramètres supportés en lignes de commande dans les modèles.",
"See external versioning help for supported templated command line parameters.": "Consulter l'aide à la gestion externe des versions pour voir les paramètres de ligne de commande supportés.",
"Select a version": "Sélectionnez une version",
"Select a version": "Choisissez une version",
"Select the devices to share this folder with.": "Synchroniser avec :",
"Select the folders to share with this device.": "Sélectionner les partages auxquels cet appareil doit participer :",
"Select the folders to share with this device.": "Choisir les partages auxquels cet appareil doit participer :",
"Send & Receive": "Envoi & réception",
"Send Only": "Envoi (lecture seule)",
"Settings": "Configuration",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Arrêté !",
"Simple File Versioning": "Suivi simplifié des versions",
"Single level wildcard (matches within a directory only)": "Joker à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Size": "Taille",
"Smallest First": "Les plus petits en premier",
"Source Code": "Code source",
"Stable releases and release candidates": "Versions stables et préliminaires",
@@ -257,9 +273,12 @@
"This is a major version upgrade.": "Il s'agit d'une mise à jour majeure.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Ce réglage contrôle l'espace disque requis dans le disque qui abrite votre répertoire utilisateur (pour la base de données d'indexation).",
"Time": "Heure",
"Time the item was last modified": "Dernière modification de l'élément",
"Trash Can File Versioning": "Style poubelle",
"Type": "Type",
"Undecided (will prompt)": "Choix différé (Une question sera affichée)",
"Unavailable": "Indisponible",
"Unavailable/Disabled by administrator or maintainer": "Indisponible/Désactivé par l'administrateur ou le mainteneur",
"Undecided (will prompt)": "Choix différé (Une question sera affichée plus tard)",
"Unknown": "Inconnu",
"Unshared": "Non partagé",
"Unused": "Non utilisé",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonym brûkensrapportaazje",
"Anonymous usage report format has changed. Would you like to move to the new format?": "It formaat fan de rapportaazje fan anonime gebrûksynformaasje is feroare. Wolle jo op dit nije formaat oerstappe?",
"Any devices configured on an introducer device will be added to this device as well.": "Alle apparaten die op in 'yntrodusearjend apparaat' ynstelt binne, wurde ek op dit apparaat taheakke.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatyske fernijing biedt no de kar tusken stabyle ferzjes en ferzje kandidaten",
"Automatic upgrades": "Automatyske fernijings",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Tink derom!",
"Bugs": "Brekkings",
"CPU Utilization": "CPU-brûken",
@@ -41,18 +45,21 @@
"Configured": "Konfigureart",
"Connection Error": "Ferbiningsflater",
"Connection Type": "Ferbiningstype",
"Connections": "Connections",
"Copied from elsewhere": "Oernommen fan earne oars",
"Copied from original": "Oernommen fan orizjineel",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 de folgende bydragers:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 de folgende Bydragers:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Meitsje negear-patroanen dy in besteande triem oerskriuwe yn {{path}}.",
"Danger!": "Gefaar!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Fuortsmiten",
"Device": "Apparaat",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Apparaat \"{{name}}\" {{device}} op ({{address}}) wol ferbining meitsje. Nij apparaat taheakje?",
"Device ID": "Apparaat-ID",
"Device Identification": "Apparaatidentifikaasje",
"Device Name": "Apparaatnamme",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Apparaten",
"Disabled": "Utskeakele",
"Disconnected": "Ferbining ferbrutsen",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI-harkadres",
"GUI Listen Addresses": "Harkadres foar GUI",
"GUI Theme": "Ynterfaasjetema",
"General": "General",
"Generate": "Generearje",
"Global Changes": "Wrâldwide Feroarings",
"Global Discovery": "Wrâldwide ûntdekking",
@@ -123,6 +131,7 @@
"Latest Change": "Meast Resinte Feroarings",
"Learn more": "Mear witte",
"Listeners": "Harkers",
"Loading data...": "Loading data...",
"Local Discovery": "Lokale ûntdekking",
"Local State": "Lokale tastân",
"Local State (Total)": "Lokale tastân (Folledich)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksimale âldens",
"Metadata Only": "Allinnich metadata",
"Minimum Free Disk Space": "Minimale frije skiifromte",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Fersette nei boppe oan de rige",
"Multi level wildcard (matches multiple directory levels)": "Multi-nivo jokerteken (wildcard) (fergelykt mei meardere map-nivo's)",
"Never": "Nea",
@@ -139,6 +150,7 @@
"Newest First": "Nijste earst",
"No": "Nee",
"No File Versioning": "Gjin triemferzjebehear",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Gjin fernijings",
"Normal": "Normaal",
"Notice": "Notysje",
@@ -153,6 +165,7 @@
"Override Changes": "Feroarings oerskriuwe",
"Path": "Paad",
"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": "Paad nei de map op de lokale kompjûter. Wurd oanmakke as dizze net bestiet. It tilde teken (~) kin brûkt wurde as fluchkeppeling foar",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Paad dêr't de ferzjes bewarre wurde moatte (leech litte foar de standert .stversions-map yn de map).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Paad dêr't de ferzjes bewarre wurde moatte (leech litte foar de standert .stversions-map yn de map).",
"Pause": "Skoftsje",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Ferzje kandidaten hawwe de lêste mooglikheden en ferbetterings. Se binne allyksa de tradisjonele twa-wyklikse Syncthing ferzjes.",
"Remote Devices": "Apparaten op Ofstân",
"Remove": "Fuortsmite",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Ferplicht ID foar de map. Moat op alle bondelapparaten itselde wêze.",
"Rescan": "Sken opnij",
"Rescan All": "Sken alles opnij",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Ofsluten klear",
"Simple File Versioning": "Ienfâldich triemferzjebehear",
"Single level wildcard (matches within a directory only)": "Inkel-nivo jokerteken (wildcard) (fergeliket allinnich binnen in map)",
"Size": "Size",
"Smallest First": "Lytste earst",
"Source Code": "Boarnekoade",
"Stable releases and release candidates": "Stabyle ferzjes en ferzje kanditaten",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Dit is in wichtige ferzjefernijing.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Dizze ynstelling bepaalt de frije romte dy't noadich is op de home-skiif (fan de yndeks-databank).",
"Time": "Tiid",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Jiskefet-triemferzjebehear",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Noch net beslist (wurd noch frege)",
"Unknown": "Unbekend",
"Unshared": "Net dielt",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Névtelen felhasználási adatok küldése",
"Anonymous usage report format has changed. Would you like to move to the new format?": "A névtelen használati jelentés formátuma megváltozott. Szeretnél áttérni az új formátumra?",
"Any devices configured on an introducer device will be added to this device as well.": "A bevezető eszközön beállított minden eszköz hozzá lesz adva ehhez az eszközhöz is.",
"Are you sure you want to remove device {%name%}?": "Biztos, hogy el akarod távolítani az eszközt: {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Biztos, hogy el akarod távolítani a mappát: {{label}}?",
"Auto Accept": "Automatikus elfogadás",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Az automatikus frissítés most lehetőséget kínál a stabil és az előzetes kiadások közötti választásra.",
"Automatic upgrades": "Automatikus frissítések",
"Automatically create or share folders that this device advertises at the default path.": "Az eszköz alapértelmezett útvonalon hirdetett mappáinak automatikus létrehozása vagy megosztása",
"Be careful!": "Óvatosan!",
"Bugs": "Hibák",
"CPU Utilization": "Processzorhasználat",
@@ -41,18 +45,21 @@
"Configured": "Beállított",
"Connection Error": "Kapcsolódási hiba",
"Connection Type": "Kapcsolattípus",
"Connections": "Kapcsolatok",
"Copied from elsewhere": "Máshonnan másolva",
"Copied from original": "Eredetiről másolva",
"Copyright © 2014-2016 the following Contributors:": "Szerzői jog © 2014-2016 az alábbi közreműködők:",
"Copyright © 2014-2017 the following Contributors:": "Szerzői jog © 2014-2017 az alábbi közreműködők:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Figyelmen kívül hagyás minták létrehozása, egy létező fájl felülírása itt: {{path}}.",
"Danger!": "Veszély!",
"Default Folder Path": "Alapértelmezett mappa útvonala",
"Deleted": "Törölve",
"Device": "Eszköz",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" eszköz ({{device}} @ {{address}}) szeretne csatlakozni. Hozzáadható az új eszköz?",
"Device ID": "Eszközazonosító",
"Device Identification": "Eszközazonosító",
"Device Name": "Eszköz neve",
"Device that last modified the item": "Az eszköz, amely utoljára módosította az elemet",
"Devices": "Eszközök",
"Disabled": "Letiltva",
"Disconnected": "Kapcsolat bontva",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Grafikus felület címe",
"GUI Listen Addresses": "Grafikus felület címe",
"GUI Theme": "Grafikus felület témája",
"General": "Általános",
"Generate": "Generálás",
"Global Changes": "Globális módosítások",
"Global Discovery": "Globális felfedezés",
@@ -123,6 +131,7 @@
"Latest Change": "Utolsó módosítás",
"Learn more": "Tudj meg többet",
"Listeners": "Kapcsolatok",
"Loading data...": "Adatok betöltése...",
"Local Discovery": "Helyi felfedezés",
"Local State": "Helyi állapot",
"Local State (Total)": "Helyi állapot (teljes)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maximális kor",
"Metadata Only": "Csak metaadatok",
"Minimum Free Disk Space": "Minimális szabad lemezterület",
"Mod. Device": "Módosító eszköz",
"Mod. Time": "Módosítási idő",
"Move to top of queue": "Sor elejére mozgatás",
"Multi level wildcard (matches multiple directory levels)": "Több szintű helyettesítő karakter (több könyvtár szintre érvényesül)",
"Never": "Soha",
@@ -139,6 +150,7 @@
"Newest First": "Újabb először",
"No": "Nem",
"No File Versioning": "Nincs fájlverzió-követés",
"No files will be deleted as a result of this operation.": "A művelet eredményeként egyetlen fájl sem lesz törölve.",
"No upgrades": "Nincsenek frissítések",
"Normal": "Normál",
"Notice": "Megjegyzés",
@@ -153,6 +165,7 @@
"Override Changes": "Változtatások felülbírálása",
"Path": "Útvonal",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "A mappa elérési útvonala az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Az útvonal, ahol létrejönnek az automatikusan elfogadott mappák, valamint a grafikus felületen létrehozott mappák esetén alapértelmezetten javasolt útvonal. A hullámvonal karakter (~) erre egészül ki: {{tilde}}",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "A verziók tárolására szolgáló elérési út (hagyd üresen a megasztott mappa alapértelmezett .stversions könyvtárának használatához)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "A verziók tárolására szolgáló elérési út (üresen hagyva az alapértelmezett .stversions mappa)",
"Pause": "Szünet",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Az előzetes kiadások tartalmazzák a legújabb fejlesztéseket és javításokat. Ezek hasonlóak a hagyományos, kétheti Syncthing kiadásokhoz.",
"Remote Devices": "Távoli eszközök",
"Remove": "Eltávolítás",
"Remove Device": "Eszköz eltávolítás",
"Remove Folder": "Mappa eltávolítás",
"Required identifier for the folder. Must be the same on all cluster devices.": "A mappa szükséges azonosítója. Minden fürtözött eszközön azonosnak kell lennie.",
"Rescan": "Átnézés",
"Rescan All": "Összes átnézése",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Leállítás kész",
"Simple File Versioning": "Egyszerű fájlverzió-követés",
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
"Size": "Méret",
"Smallest First": "Kisebb először",
"Source Code": "Forráskód",
"Stable releases and release candidates": "Stabil és előzetes kiadások ",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Ez egy főverzió-frissítés.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Ez e beállítás szabályozza a szükséges szabad helyet a fő (pl: index, adatbázis) lemezen.",
"Time": "Idő",
"Time the item was last modified": "Az idő, amikor utoljára módosítva lett az elem",
"Trash Can File Versioning": "Szemetes fájlverzió-követés",
"Type": "Típus",
"Unavailable": "Nem elérhető",
"Unavailable/Disabled by administrator or maintainer": "Nem elérhető/letiltva egy adminisztrátor vagy karbantartó által",
"Undecided (will prompt)": "Bizonytalan (kérdezni fogja)",
"Unknown": "Ismeretlen",
"Unshared": "Nincs megosztva",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Il formato delle statistiche anonime di utilizzo è cambiato. Vuoi passare al nuovo formato?",
"Any devices configured on an introducer device will be added to this device as well.": "Qualsiasi dispositivo configurato in un introduttore verrà aggiunto anche a questo dispositivo.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Aggiornamenti automatici offrono la scelta tra rilasci stabili e candidati di rilascio.",
"Automatic upgrades": "Aggiornamenti automatici",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Fai attenzione!",
"Bugs": "Bug",
"CPU Utilization": "Utilizzo CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurato",
"Connection Error": "Errore di Connessione",
"Connection Type": "Tipo di Connessione",
"Connections": "Connections",
"Copied from elsewhere": "Copiato da qualche altra parte",
"Copied from original": "Copiato dall'originale",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 i seguenti Collaboratori:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 i seguenti Collaboratori:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creazione di schemi di esclusione, sovrascrivendo un file esistente in {{path}}.",
"Danger!": "Pericolo!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Cancellato",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Il dispositivo \"{{name}}\" ({{device}} - {{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
"Device ID": "ID Dispositivo",
"Device Identification": "Identificazione Dispositivo",
"Device Name": "Nome Dispositivo",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositivi",
"Disabled": "Disabilitato",
"Disconnected": "Disconnesso",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Indirizzo dell'Interfaccia Grafica",
"GUI Listen Addresses": "Indirizzi dell'Interfaccia Grafica",
"GUI Theme": "Tema GUI",
"General": "General",
"Generate": "Genera",
"Global Changes": "Modifiche Globali",
"Global Discovery": "Individuazione Globale",
@@ -123,6 +131,7 @@
"Latest Change": "Ultima Modifica",
"Learn more": "Impara di piu",
"Listeners": "In Ascolto",
"Loading data...": "Loading data...",
"Local Discovery": "Individuazione Locale",
"Local State": "Stato Locale",
"Local State (Total)": "Stato Locale (Totale)",
@@ -131,6 +140,8 @@
"Maximum Age": "Durata Massima",
"Metadata Only": "Solo i Metadati",
"Minimum Free Disk Space": "Minimo Spazio Libero su Disco",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Posiziona in cima alla coda",
"Multi level wildcard (matches multiple directory levels)": "Metacarattere multi-livello (per corrispondenze in più livelli di cartelle)",
"Never": "Mai",
@@ -139,6 +150,7 @@
"Newest First": "Prima il più recente",
"No": "No",
"No File Versioning": "Nessun Controllo Versione",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Senza aggiornamenti",
"Normal": "Normale",
"Notice": "Avviso",
@@ -153,6 +165,7 @@
"Override Changes": "Ignora le Modifiche",
"Path": "Percorso",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Percorso della cartella nel computer locale. Verrà creata se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Percorso di salvataggio delle versioni (lasciare vuoto per utilizzare la cartella predefinita .stversions in questa cartella).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Percorso di salvataggio delle versioni (lasciare vuoto per utilizzare la cartella predefinita .stversions in questa cartella).",
"Pause": "Pausa",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Candidati di rilascio contengono le ultime funzionalita e aggiustamenti. Sono simili ai rilasci bisettimanali di Syncthing.",
"Remote Devices": "Dispositivi Remoti",
"Remove": "Rimuovi",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificatore obbligatorio della cartella. Deve essere lo stesso su tutti i dispositivi del cluster.",
"Rescan": "Riscansiona",
"Rescan All": "Riscansiona Tutto",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Arresto Eseguito",
"Simple File Versioning": "Controllo Versione Semplice",
"Single level wildcard (matches within a directory only)": "Metacarattere di singolo livello (per corrispondenze solo all'interno di una cartella)",
"Size": "Size",
"Smallest First": "Prima il più piccolo",
"Source Code": "Codice Sorgente",
"Stable releases and release candidates": "Rilasci stabili e candidati di rilascio",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Questo è un aggiornamento di versione principale",
"This setting controls the free space required on the home (i.e., index database) disk.": "Questa impostazione controlla lo spazio libero richiesto sul disco home (cioè, database di indice).",
"Time": "Tempo",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Controllo Versione con Cestino",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Non deciso (verrà richiesto)",
"Unknown": "Sconosciuto",
"Unshared": "Non Condiviso",

View File

@@ -21,13 +21,17 @@
"Allow Anonymous Usage Reporting?": "匿名で使用状況をレポートすることを許可しますか?",
"Allowed Networks": "許可されているネットワーク",
"Alphabetic": "アルファベット順",
"An external command handles the versioning. It has to remove the file from the shared folder.": "外部コマンドバージョン管理を任せます。ここで指定するコマンドは、共有フォルダーからファイルを削除するものでなくてはなりません。",
"An external command handles the versioning. It has to remove the file from the shared folder.": "外部コマンドバージョン管理を行います。ここで指定するコマンドは、共有フォルダーからファイルを削除するものでなくてはなりません。",
"An external command handles the versioning. It has to remove the file from the synced folder.": "外部コマンドにバージョンを管理させます。ここで指定するコマンドは、同期フォルダーからファイルを削除するものでなくてはなりません。",
"Anonymous Usage Reporting": "匿名での使用状況レポート",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名での使用状況レポートのフォーマットが変わりました。新形式でのレポートに移行しますか?",
"Any devices configured on an introducer device will be added to this device as well.": "紹介者デバイス上で設定されたデバイスは、このデバイス上にも追加されます。",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自動アップグレードは、安定版とリリース候補版のいずれかを選べるようになりました。",
"Automatic upgrades": "自動アップグレード",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "注意!",
"Bugs": "バグ",
"CPU Utilization": "CPU使用率",
@@ -41,20 +45,23 @@
"Configured": "設定値",
"Connection Error": "接続エラー",
"Connection Type": "接続種別",
"Connections": "Connections",
"Copied from elsewhere": "別ファイルからコピー済",
"Copied from original": "元ファイルからコピー済",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "無視パターンを作成中。既存のファイルが {{path}} にある場合は上書きされます。",
"Danger!": "危険!",
"Default Folder Path": "Default Folder Path",
"Deleted": "削除",
"Device": "デバイス",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "デバイス「{{name}}」 ({{address}} の {{device}}) が接続を求めています。新しいデバイスとして追加しますか?",
"Device ID": "デバイスID",
"Device Identification": "デバイスID",
"Device Name": "デバイス名",
"Device that last modified the item": "Device that last modified the item",
"Devices": "デバイス",
"Disabled": "Disabled",
"Disabled": "無効",
"Disconnected": "切断中",
"Discovered": "探索結果",
"Discovery": "探索サーバー",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI待ち受けアドレス",
"GUI Listen Addresses": "GUI待ち受けアドレス",
"GUI Theme": "GUIテーマ",
"General": "General",
"Generate": "生成",
"Global Changes": "全変更点",
"Global Discovery": "グローバル探索",
@@ -123,6 +131,7 @@
"Latest Change": "最終変更内容",
"Learn more": "詳細を確認する",
"Listeners": "待ち受けポート",
"Loading data...": "Loading data...",
"Local Discovery": "LAN内で探索",
"Local State": "ローカル状態",
"Local State (Total)": "ローカル状態 (合計)",
@@ -131,6 +140,8 @@
"Maximum Age": "最大保存日数",
"Metadata Only": "メタデータのみ",
"Minimum Free Disk Space": "同期を停止する最小空きディスク容量",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "最優先にする",
"Multi level wildcard (matches multiple directory levels)": "多階層ワイルドカード (複数のディレクトリ階層にマッチします)",
"Never": "記録なし",
@@ -139,6 +150,7 @@
"Newest First": "新しい順",
"No": "いいえ",
"No File Versioning": "バージョン管理をしない",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "アップグレードしない",
"Normal": "通常",
"Notice": "通知",
@@ -152,7 +164,8 @@
"Outgoing Rate Limit (KiB/s)": "上り帯域制限 (KiB/s)",
"Override Changes": "他のデバイスの変更を上書きする",
"Path": "パス",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "ローカルコンピュータ上のフォルダーパス。フォルダーが存在しない場合は作成されます。チルダ (~) でのフォルダーを短縮入力できます:",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "ローカルコンピュータ上のフォルダーパス。フォルダーが存在しない場合は作成されます。チルダ (~) で以下のフォルダーを短縮入力できます:",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "古いバージョンを保存するパス (空欄の場合、デフォルトで共有フォルダー内の .stversions ディレクトリ)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "古いバージョンを保存するパス (空欄の場合、デフォルトでフォルダー内の .stversions フォルダー)",
"Pause": "一時停止",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "リリース候補版には最新の機能と修正が含まれます。これは従来の隔週リリースに近いものです。",
"Remote Devices": "接続先デバイス",
"Remove": "除去",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "フォルダーの識別子で、必須です。このフォルダーを共有する全てのデバイス上で同一でなくてはなりません。",
"Rescan": "再スキャン",
"Rescan All": "すべて再スキャン",
@@ -188,8 +203,8 @@
"Scan Time Remaining": "スキャン残り時間",
"Scanning": "スキャン中",
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select a version": "Select a version",
"See external versioning help for supported templated command line parameters.": "使用可能なコマンドラインパラメータについてはヘルプの外部バージョン管理の項目を参照してください。",
"Select a version": "バージョンを選択してください",
"Select the devices to share this folder with.": "このフォルダーを共有するデバイスを選択してください。",
"Select the folders to share with this device.": "このデバイスと共有するフォルダーを選択してください。",
"Send & Receive": "送受信",
@@ -203,13 +218,14 @@
"Shared With": "共有中のデバイス",
"Show ID": "IDを表示",
"Show QR": "QRコードを表示",
"Show diff with previous version": "Show diff with previous version",
"Show diff with previous version": "前バージョンとの差分を表示",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "ステータス画面でデバイスIDの代わりに表示されます。他のデバイスに対してもデフォルトの名前として通知されます。",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "ステータス画面でデバイスIDの代わりに表示されます。空欄にすると相手側デバイスが通知してきた名前で更新されます。",
"Shutdown": "シャットダウン",
"Shutdown Complete": "シャットダウン完了",
"Simple File Versioning": "単純バージョン管理",
"Single level wildcard (matches within a directory only)": "ワイルドカード (単一のディレクトリ内だけでマッチします)",
"Size": "Size",
"Smallest First": "小さい順",
"Source Code": "ソースコード",
"Stable releases and release candidates": "安定版とリリース候補版",
@@ -229,7 +245,7 @@
"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を再起動してください。",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthingの管理画面が、パスワードなしで外部からアクセスできるように設定されています。",
"The aggregated statistics are publicly available at the URL below.": "集計結果はのURLで公開されています。",
"The aggregated statistics are publicly available at the URL below.": "集計結果は以下のURLで公開されています。",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "設定が保存されましたが、まだ有効になっていません。新しい設定を有効にするにはSyncthingを再起動してください。",
"The device ID cannot be blank.": "デバイスIDを入力してください。",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ここに入力するデバイスIDは、接続したい相手側デバイスの [メニュー]→[IDを表示] で確認できます。スペースとハイフンは入力しなくてもかまいません。",
@@ -257,9 +273,12 @@
"This is a major version upgrade.": "メジャーアップグレードです。",
"This setting controls the free space required on the home (i.e., index database) disk.": "この設定は、ホームディスク (インデックスデータベースがあるディスク) で必要な空き容量を管理します。",
"Time": "日時",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "ゴミ箱によるバージョン管理",
"Type": "タイプ",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "未決定(再確認する)",
"Unknown": "不明",
"Unshared": "非共有",
"Unused": "未使用",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "익명 사용 보고서",
"Anonymous usage report format has changed. Would you like to move to the new format?": "익명 사용 리포트의 형식이 변경되었습니다. 새 형식으로 이동 하시겠습니까?",
"Any devices configured on an introducer device will be added to this device as well.": "유도 장치에 추가된 기기들은 이 기기에도 동시에 추가됩니다.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "자동 업데이트를 이제 안정 버전과 출시 후보 사이에 선택 할 수 있게 됩니다.",
"Automatic upgrades": "자동 업데이트",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "주의!",
"Bugs": "버그",
"CPU Utilization": "CPU 사용률",
@@ -41,18 +45,21 @@
"Configured": "설정됨",
"Connection Error": "연결 에러",
"Connection Type": "연결 종류",
"Connections": "Connections",
"Copied from elsewhere": "다른 곳에서 복사됨",
"Copied from original": "원본에서 복사됨",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "무시 패턴 만들기, {{path}}에 존재하는 파일을 덮어쓰기 합니다",
"Danger!": "경고!",
"Default Folder Path": "Default Folder Path",
"Deleted": "삭제됨",
"Device": "기기",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
"Device ID": "기기 ID",
"Device Identification": "기기 식별자",
"Device Name": "기기 이름",
"Device that last modified the item": "Device that last modified the item",
"Devices": "기기",
"Disabled": "비활성화",
"Disconnected": "연결 끊김",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI 주소",
"GUI Listen Addresses": "GUI 주소",
"GUI Theme": "GUI 테마",
"General": "General",
"Generate": "생성",
"Global Changes": "전체 변경 사항",
"Global Discovery": "글로벌 탐색",
@@ -123,6 +131,7 @@
"Latest Change": "최신 변경",
"Learn more": "더 알아보기",
"Listeners": "수신자",
"Loading data...": "Loading data...",
"Local Discovery": "로컬 노드 검색",
"Local State": "로컬 상태",
"Local State (Total)": "로컬 상태 (합계)",
@@ -131,6 +140,8 @@
"Maximum Age": "최대 보존 기간",
"Metadata Only": "메타데이터만",
"Minimum Free Disk Space": "최소 여유 디스크 용량",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "대기열 상단으로 이동",
"Multi level wildcard (matches multiple directory levels)": "다중 레벨 와일드 카드 (여러 단계의 디렉토리와 일치하는 경우)",
"Never": "사용 안 함",
@@ -139,6 +150,7 @@
"Newest First": "새로운 파일순",
"No": "아니오",
"No File Versioning": "파일 버전 관리 안 함",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "업데이트 안함",
"Normal": "일반",
"Notice": "공지",
@@ -153,6 +165,7 @@
"Override Changes": "덮어쓰기",
"Path": "경로",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "로컬 컴퓨터에 있는 폴더의 경로를 지정합니다. 존재하지 않는 폴더일 경우 자동으로 생성됩니다. 물결 기호 (~)는 아래와 같은 폴더를 나타냅니다.",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "버전을 보관할 경로 (비워둘 시 공유된 폴더 안의 기본값 .stversions 폴더로 지정됨)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "버전을 보관할 경로 (비워둘 시 기본값 .stversions 폴더로 지정됨)",
"Pause": "일시 중지",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "출시 후보는 최신 기능과 버그 픽스를 포함 하고 있습니다. 이 버전은 예전 방식인 2주 주기 Syncthing 출시와 비슷합니다.",
"Remote Devices": "원격 기기",
"Remove": "삭제",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "폴더 식별자가 필요합니다. 모든 장치에서 동일해야 합니다.",
"Rescan": "재탐색",
"Rescan All": "전체 재탐색",
@@ -210,6 +225,7 @@
"Shutdown Complete": "종료 완료",
"Simple File Versioning": "간단한 파일 버전 관리",
"Single level wildcard (matches within a directory only)": "단일 레벨 와일드카드 (하나의 디렉토리만 일치하는 경우)",
"Size": "Size",
"Smallest First": "작은 파일순",
"Source Code": "소스 코드",
"Stable releases and release candidates": "안정 버전과 출시 후보",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "이 업데이트는 메이저 버전입니다.",
"This setting controls the free space required on the home (i.e., index database) disk.": "이 설정은 홈 디스크에 필요한 여유 공간을 제어합니다. (즉, 인덱스 데이터베이스)",
"Time": "시간",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "휴지통을 통한 파일 버전 관리",
"Type": "종류",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "알 수 없음",
"Unshared": "공유되지 않음",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anoniminė naudojimo ataskaita",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anoniminės naudojimo ataskaitos formatas pasikeitė. Ar norėtumėte pereiti prie naujojo formato?",
"Any devices configured on an introducer device will be added to this device as well.": "Visi supažindintojo įrenginiai bus pridėti prie jūsų įrenginių sąrašo.",
"Are you sure you want to remove device {%name%}?": "Ar tikrai norite pašalinti įrenginį {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Ar tikrai norite pašalinti aplanką {{label}}?",
"Auto Accept": "Automatiškai priimti",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatiniai atnaujinimai dabar siūlo pasirinkimą tarp stabilių versijų ir kandidatinių versijų.",
"Automatic upgrades": "Automatiniai atnaujinimai",
"Automatically create or share folders that this device advertises at the default path.": "Automatiškai sukurti ar dalintis aplankais, kuriuos šis įrenginys skelbia numatytajame kelyje.",
"Be careful!": "Būkite atsargūs!",
"Bugs": "Klaidos",
"CPU Utilization": "Procesoriaus panaudojimas",
@@ -41,18 +45,21 @@
"Configured": "Sukonfigūruotas",
"Connection Error": "Susijungimo klaida",
"Connection Type": "Ryšio tipas",
"Connections": "Ryšiai",
"Copied from elsewhere": "Nukopijuota iš kitur",
"Copied from original": "Nukopijuota iš originalo",
"Copyright © 2014-2016 the following Contributors:": "Autorių teisės © 2014-2016 šių bendraautorių:",
"Copyright © 2014-2017 the following Contributors:": "Autorių teisės © 2014-2017 šių bendraautorių:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Kuriami nepaisomi šablonai, perrašomas esamas failas, esantis {{path}}.",
"Danger!": "Pavojus!",
"Default Folder Path": "Numatytojo aplanko kelias",
"Deleted": "Ištrinta",
"Device": "Įrenginys",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Įrenginys \"{{name}}\" ({{device}} {{address}}) nori prisijungti. Pridėti naują įrenginį?",
"Device ID": "Įrenginio ID",
"Device Identification": "Įrenginio identifikacija",
"Device Name": "Įrenginio pavadinimas",
"Device that last modified the item": "Įrenginys, kuris paskutinis modifikavo elementą",
"Devices": "Įrenginiai",
"Disabled": "Išjungta",
"Disconnected": "Atsijungęs",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Valdymo skydelio adresas",
"GUI Listen Addresses": "Valdymo skydelio adresas",
"GUI Theme": "Valdymo skydelio tema",
"General": "Bendra",
"Generate": "Sukurti",
"Global Changes": "Visuotiniai pakeitimai",
"Global Discovery": "Visuotinis matomumas",
@@ -123,6 +131,7 @@
"Latest Change": "Paskutinis pakeitimas",
"Learn more": "Sužinoti daugiau",
"Listeners": "Klausytojai",
"Loading data...": "Įkeliami duomenys...",
"Local Discovery": "Vietinis matomumas",
"Local State": "Vietinė būsena",
"Local State (Total)": "Vietinė būsena (Bendrai)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksimalus amžius",
"Metadata Only": "Metaduomenims",
"Minimum Free Disk Space": "Minimum laisvos vietos diske",
"Mod. Device": "Mod. įrenginys",
"Mod. Time": "Mod. laikas",
"Move to top of queue": "Perkelti į eilės priekį",
"Multi level wildcard (matches multiple directory levels)": "Keleto lygių pakaitos simbolis (atitinka keletą katalogų lygių)",
"Never": "Niekada",
@@ -139,6 +150,7 @@
"Newest First": "Naujausi pirmiau",
"No": "Ne",
"No File Versioning": "Nėra versijų valdymo",
"No files will be deleted as a result of this operation.": "Šios operacijos rezultate nebus pašalinti jokie failai.",
"No upgrades": "Be atnaujinimų",
"Normal": "Normalus",
"Notice": "Įspėjimas",
@@ -153,6 +165,7 @@
"Override Changes": "Perrašyti pakeitimus",
"Path": "Kelias",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Kelias iki aplanko šiame kompiuteryje. Bus sukurtas, jei neegzistuoja. Tildės simbolis (~) gali būti naudojamas kaip trumpinys",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Kelias, kuriame bus sukuriami nauji automatiškai priimami aplankai, o taip pat numatytasis siūlomas kelias, kuomet per naudotojo sąsaja pridedami nauji aplankai. Tildės simbolis (~) išskleidžia į {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Kelias, kur bus saugomos versijos (palikite tuščią numatytajam .stversions katalogui bendrinamame aplanke).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kelias, kur bus saugomos versijos (palikite tuščią numatytam .stversions aplankui).",
"Pause": "Pristabdyti",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidatinėse versijose yra naujausios ypatybės ir pataisymai. Šios versijos yra panašios į tradicines, du kartus per mėnesį išleidžiamas Syncthing versijas.",
"Remote Devices": "Nuotoliniai įrenginiai",
"Remove": "Pašalinti",
"Remove Device": "Šalinti įrenginį",
"Remove Folder": "Šalinti aplanką",
"Required identifier for the folder. Must be the same on all cluster devices.": "Reikalaujamas aplanko identifikatorius. Privalo būti toks pats visuose įrenginiuose.",
"Rescan": "Nuskaityti iš naujo",
"Rescan All": "Nuskaityti visus aplankus",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Sėkmingai išjungta",
"Simple File Versioning": "Supaprastintas versijų valdymas",
"Single level wildcard (matches within a directory only)": "Vieno lygio pakaitos simbolis (atitinka tik vieną katalogo lygį)",
"Size": "Dydis",
"Smallest First": "Mažiausi pirmiau",
"Source Code": "Išeities kodas",
"Stable releases and release candidates": "Stabilios versijos ir kandidatinės versijos",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Tai yra stambus atnaujinimas.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Šis nustatymas valdo laisvą vietą, kuri yra reikalinga namų (duomenų bazės) diske.",
"Time": "Laikas",
"Time the item was last modified": "Laikas, kai elementas buvo paskutinį kartą modifikuotas",
"Trash Can File Versioning": "Šiukšliadėžės versijų valdymas",
"Type": "Tipas",
"Unavailable": "Neprieinama",
"Unavailable/Disabled by administrator or maintainer": "Neprieinama/Išjungta administratoriaus ar prižiūrėtojo",
"Undecided (will prompt)": "Nenuspręsta (bus klausiama)",
"Unknown": "Nežinoma",
"Unshared": "Nesidalinama",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonym innsamling av brukerdata",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Det anonyme bruksrapportformatet har endret seg. Ønsker du å gå over til det nye formatet?",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter satt opp på en introduksjonsenhet vil også bli lagt til denne enheten.",
"Are you sure you want to remove device {%name%}?": "Er du sikker på at du ønsker å fjerne enheten {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Er du sikker på at du ønsker å fjerne mappen {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatisk oppgradering lar deg nå få valget mellom ferdige utgaver og utgivelseskandidater.",
"Automatic upgrades": "Automatiske oppdateringer",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Vær forsiktig!",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnyttelse",
@@ -41,18 +45,21 @@
"Configured": "Oppsatt",
"Connection Error": "Tilkoblingsfeil",
"Connection Type": "Tilkoblingstype",
"Connections": "Connections",
"Copied from elsewhere": "Kopiert fra et annet sted",
"Copied from original": "Kopiert fra original",
"Copyright © 2014-2016 the following Contributors:": "Opphavsrett © 2014-2016 for følgende bidragsytere:",
"Copyright © 2014-2017 the following Contributors:": "Opphavsrett © 2014-2017 for følgende bidragsytere:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Oppretter ignoreringsmønster, overskriver eksisterende fil i {{path}}.",
"Danger!": "Fare!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Slettet",
"Device": "Enhet",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) ønsker å koble til. Legge til ny enhet?",
"Device ID": "Enhets-ID",
"Device Identification": "Enhetskjennemerke",
"Device Name": "Navn på enhet",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Enheter",
"Disabled": "Avskrudd",
"Disconnected": "Frakoblet",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Lytteadresse for grafisk brukergrensesnitt",
"GUI Listen Addresses": "GUI-lytteadresse",
"GUI Theme": "GUI-tema",
"General": "General",
"Generate": "Generer",
"Global Changes": "Globale endringer",
"Global Discovery": "Globalt oppslag",
@@ -123,6 +131,7 @@
"Latest Change": "Sist endret",
"Learn more": "Lær mer",
"Listeners": "Lyttere",
"Loading data...": "Loading data...",
"Local Discovery": "Lokalt oppslag",
"Local State": "Lokal tilstand",
"Local State (Total)": "Lokal tilstand (total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksimal levetid",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Nødvendig ledig diskplass",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Flytt fremst i køen",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
@@ -139,6 +150,7 @@
"Newest First": "Den nyeste først",
"No": "Nei",
"No File Versioning": "Ingen versjonskontroll",
"No files will be deleted as a result of this operation.": "Ingen filer vil bli slettet som følge av denne operasjonen.",
"No upgrades": "Ingen oppgraderinger",
"Normal": "Normal",
"Notice": "Merknader",
@@ -153,6 +165,7 @@
"Override Changes": "Overstyr endringer",
"Path": "Sti",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Plasseringen av mappen på datamaskinen. Denne vil bli opprettet dersom den ikke finnes. Krøllstrektegnet (~) kan brukes som forkortelse for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Plasseringen for lagrede versjoner (la denne være tom for å bruke den forvalgte .stversions-mappa i den delte mappa).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringen for lagrede versjoner (la denne være tom for å bruke standard .stversions-mappen i mappen).",
"Pause": "Oppholde",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Utgivelseskandidater inneholder de seneste problemfiksene og funksjonene. De ligner på de tradisjonelle Syncthing-utgivelsene som kom hver andre uke.",
"Remote Devices": "Andre enheter",
"Remove": "Fjern",
"Remove Device": "Fjern enhet",
"Remove Folder": "Fjern mappe",
"Required identifier for the folder. Must be the same on all cluster devices.": "Påkrevd identifikator for mappa. Denne må være lik på alle enheter i samme klynge.",
"Rescan": "Gjennomsøk på nytt",
"Rescan All": "Gjennomsøk alt på nytt",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Avslutning fullført",
"Simple File Versioning": "Enkel versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søker kun i en mappe)",
"Size": "Size",
"Smallest First": "Den minste først",
"Source Code": "Kildekode",
"Stable releases and release candidates": "Ferdige utgaver og utgivelseskandidater",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Dette er en storoppgradering",
"This setting controls the free space required on the home (i.e., index database) disk.": "Denne innstillingen kontrollerer ledig diskplass krevd på hjemme- (f.eks. indekseringsdatabase-) disken.",
"Time": "Klokkeslett",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Papirkurv versjonskontroll",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Ikke bestemt (vil spørre)",
"Unknown": "Ukjent",
"Unshared": "Ikke delt",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonieme gebruikersstatistieken",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Het formaat voor anonieme gebruikersrapporten is gewijzigd. Wil je naar het nieuwe formaat overschakelen?",
"Any devices configured on an introducer device will be added to this device as well.": "Apparaten die geconfigureerd worden op een introductieapparaat zullen ook aan dit apparaat worden toegevoegd.",
"Are you sure you want to remove device {%name%}?": "Weet je zeker dat je apparaat {{name}} wil verwijderen?",
"Are you sure you want to remove folder {%label%}?": "Weet je zeker dat je map {{label}} wil verwijderen?",
"Auto Accept": "Automatisch aanvaarden",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatisch bijwerken biedt nu de keuze tussen stabiele uitgaven en uitgavekandidaten.",
"Automatic upgrades": "Automatische upgrades",
"Automatically create or share folders that this device advertises at the default path.": "Automatisch mappen die dit apparaat aankondigt aanmaken of delen in de standaardlocatie.",
"Be careful!": "Wees voorzichtig!",
"Bugs": "Bugs",
"CPU Utilization": "CPU-gebruik",
@@ -41,18 +45,21 @@
"Configured": "Geconfigureerd",
"Connection Error": "Verbindingsfout",
"Connection Type": "Soort verbinding",
"Connections": "Verbindingen",
"Copied from elsewhere": "Gekopieerd vanaf elders",
"Copied from original": "Gekopieerd van het origineel",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 voor de volgende bijdragers:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 voor de volgende bijdragers:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Negeerpatronen worden aangemaakt, bestaand bestand wordt overschreven op {{path}}.",
"Danger!": "Let op!",
"Default Folder Path": "Standaardmaplocatie",
"Deleted": "Verwijderd",
"Device": "Apparaat",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Apparaat \"{{name}}\" ({{device}} at {{address}}) wil verbinden. Wil je dit toestaan?",
"Device ID": "Apparaat-ID",
"Device Identification": "Apparaat-identificatie",
"Device Name": "Naam apparaat",
"Device that last modified the item": "Apparaat dat het item laatst gewijzigd heeft",
"Devices": "Apparaten",
"Disabled": "Uitgeschakeld",
"Disconnected": "Niet verbonden",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI-luisteradres",
"GUI Listen Addresses": "GUI-adres",
"GUI Theme": "GUI-thema",
"General": "Algemeen",
"Generate": "Genereer",
"Global Changes": "Algemene veranderingen",
"Global Discovery": "Globaal zoeken",
@@ -123,6 +131,7 @@
"Latest Change": "Meest recente wijziging",
"Learn more": "Lees meer",
"Listeners": "Luisteraars",
"Loading data...": "Gegevens laden...",
"Local Discovery": "Lokaal zoeken",
"Local State": "Lokale status",
"Local State (Total)": "Lokale status (totaal)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maximum leeftijd",
"Metadata Only": "Alleen metadata",
"Minimum Free Disk Space": "Minimale vrije schijfruimte",
"Mod. Device": "Wijzigend apparaat",
"Mod. Time": "Tijdstip van wijziging",
"Move to top of queue": "Verplaats naar het begin van de wachtrij",
"Multi level wildcard (matches multiple directory levels)": "Wildcard op meerdere niveaus (toepasbaar op meerdere mapniveaus)",
"Never": "Nooit",
@@ -139,6 +150,7 @@
"Newest First": "Nieuwste eerst",
"No": "Nee",
"No File Versioning": "Geen versiebeheer",
"No files will be deleted as a result of this operation.": "Deze handeling zal geen bestanden verwijderen.",
"No upgrades": "Geen upgrades",
"Normal": "Normaal",
"Notice": "Mededeling",
@@ -153,6 +165,7 @@
"Override Changes": "Veranderingen overschrijven",
"Path": "Pad",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Locatie van de map op de lokale computer. Als deze niet bestaat zal de map aangemaakt worden. De tilde (~) kan gebruikt worden als snelkoppeling voor ",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Locatie waar nieuwe automatisch aanvaarde mappen aangemaakt zullen worden, evenals de standaard voorgestelde locatie bij toevoegen van nieuwe mappen in de gebruikersinterface. Een tilde (~) zet uit tot {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pad waar de verschillende versies opgeslagen moeten worden (laat leeg voor de standaardmap '.stversion' in de gedeelde map).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Betandspad waar de versies opgeslagen moeten worden (leeg laten voor de standaard .stversions subfolder).",
"Pause": "Pauze",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Uitgavekandidaten bevatten de laatste nieuwe functionaliteit en oplossingen voor problemen. Ze lijken op de traditionele, tweemaal per week, Syncthing-uitgaven.",
"Remote Devices": "Externe apparaten",
"Remove": "Verwijderen",
"Remove Device": "Apparaat verwijderen",
"Remove Folder": "Map verwijderen",
"Required identifier for the folder. Must be the same on all cluster devices.": "De identifier voor de map is verplicht. Dit moet hetzelfde zijn op alle apparaten in het cluster. ",
"Rescan": "Opnieuw scannen",
"Rescan All": "Scan alles opnieuw",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Afsluiten voltooid",
"Simple File Versioning": "Eenvoudig versiebeheer",
"Single level wildcard (matches within a directory only)": "Wildcard op enkelvoudig niveau (alleen toepasbaar binnen een map)",
"Size": "Grootte",
"Smallest First": "Kleinste eerst",
"Source Code": "Broncode",
"Stable releases and release candidates": "Stabiele versies en release candidates",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Dit is een grote update.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Deze instelling beheert de benodigde vrije ruimte op de home (index database) schijf.",
"Time": "Tijd",
"Time the item was last modified": "Tijdstip waarop het item laatst gewijzigd is",
"Trash Can File Versioning": "Versiebeheer bestanden prullenbak",
"Type": "Type",
"Unavailable": "Niet beschikbaar",
"Unavailable/Disabled by administrator or maintainer": "Niet beschikbaar of uitgeschakeld door administrator of beheerder",
"Undecided (will prompt)": "Onbeslist (bevestiging vragen)",
"Unknown": "Onbekend",
"Unshared": "Niet gedeeld",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonymisert bruksrapportering",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Einingar konfigurert på ei introduksjonseining vil òg verta lagt til denne eininga.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
"Automatic upgrades": "Automatiske oppdateringar",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Ver varsam!",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnytting",
@@ -41,18 +45,21 @@
"Configured": "Konfigurert",
"Connection Error": "Tilkoplingsfeil",
"Connection Type": "Tilkoplingstype",
"Connections": "Connections",
"Copied from elsewhere": "Kopiert frå ein annan stad",
"Copied from original": "Kopiert frå originalen",
"Copyright © 2014-2016 the following Contributors:": "Opphavsrett © 2014-2016 for følgjande bidragsyterar:",
"Copyright © 2014-2017 the following Contributors:": "Opphavsrett © 2014-2017 for følgjande bidragsyterar:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Fare!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Sletta",
"Device": "Eining",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Eininga «{{name}}» {{device}} ({{address}}) vil kopla seg til. Vil du leggja ho til?",
"Device ID": "Eining ID",
"Device Identification": "Einingskjennemerke",
"Device Name": "Namn På Eining",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Einingar",
"Disabled": "Disabled",
"Disconnected": "Fråkopla",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI Lytteadresse",
"GUI Theme": "GUI Theme",
"General": "General",
"Generate": "Generer",
"Global Changes": "Global Changes",
"Global Discovery": "Global søking",
@@ -123,6 +131,7 @@
"Latest Change": "Siste endringar",
"Learn more": "Learn more",
"Listeners": "Lyttarar",
"Loading data...": "Loading data...",
"Local Discovery": "Lokal oppdaging",
"Local State": "Lokal Tilstand",
"Local State (Total)": "Lokal tilstand (total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksimal Levetid",
"Metadata Only": "Berre metadata",
"Minimum Free Disk Space": "Naudsynt ledig diskplass",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Flytt øvst i køen",
"Multi level wildcard (matches multiple directory levels)": "Fleirnivå-jokerteikn (søkjer på fleire mappenivå)",
"Never": "Aldri",
@@ -139,6 +150,7 @@
"Newest First": "Nyaste fyrst",
"No": "Nei",
"No File Versioning": "Inga filutgåvehandtering",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "No upgrades",
"Normal": "Normal",
"Notice": "Merknad",
@@ -153,6 +165,7 @@
"Override Changes": "Overstyr endringar",
"Path": "Sti",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Plasseringa av mappa på datamaskinen. Vert oppretta om ho ikkje finst. Krøllstrekteiknet (~) kan brukast som forkorting for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringa for lagra versjonar (la denne vera tom for å bruka standardmappa .stversions i mappa).",
"Pause": "Stans",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.",
"Remote Devices": "Eksterne einingar",
"Remove": "Fjern",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Påkravd identifikator for katalogen. Denne må vera lik på alle einingane i same klynge.",
"Rescan": "Skann På Ny",
"Rescan All": "Skann alle på nytt",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Slått av",
"Simple File Versioning": "Enkel filutgåvehandtering",
"Single level wildcard (matches within a directory only)": "Enkeltnivå-jokerteikn (søkjer berre i éi mappe)",
"Size": "Size",
"Smallest First": "Minste fyrst",
"Source Code": "Kildekode",
"Stable releases and release candidates": "Stable releases and release candidates",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Dette er ei hovudoppgradering",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Time",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Papirkorg-filutgåvehandtering",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Ukjent",
"Unshared": "Ikkje delt",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonimowe statystyki użycia",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Format anonimowego raportu zużycia uległ zmianie.\nCzy chcesz przejść na nowy format?",
"Any devices configured on an introducer device will be added to this device as well.": "Wszystkie urządzenia skonfigurowane na urządzeniu wprowadzającym zostaną dodane także do tego urządzenia.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatyczne aktualizacje pozwalają teraz wybrać pomiędzy wydaniami stabilnymi a wersjami kandydującymi.",
"Automatic upgrades": "Automatyczne aktualizacje",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Uważaj!",
"Bugs": "Błędy",
"CPU Utilization": "Użycie CPU",
@@ -41,18 +45,21 @@
"Configured": "Skonfigurowane",
"Connection Error": "Błąd połączenia",
"Connection Type": "Rodzaj połączenia",
"Connections": "Connections",
"Copied from elsewhere": "Skopiowane z innego miejsca ",
"Copied from original": "Skopiowane z oryginału",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016: ",
"Copyright © 2014-2017 the following Contributors:": "Prawa autorskie © 2014-2017 dla następujących autorów:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Ustawienie wzorów ignorowania, nadpisze istniejący plik w {{path}}.",
"Danger!": "Niebezpieczne!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Usunięto",
"Device": "Urządzenie",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie \"{{name}}\" {{device}} ({{address}}) chce się połączyć. Dodać nowe urządzenie?",
"Device ID": "ID urządzenia",
"Device Identification": "Identyfikator urządzenia",
"Device Name": "Nazwa urządzenia",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Urządzenia",
"Disabled": "Wyłączone",
"Disconnected": "Rozłączony",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Adres nasłuchu GUI",
"GUI Listen Addresses": "Adres nasłuchiwania",
"GUI Theme": "Motyw GUI",
"General": "General",
"Generate": "Generuj",
"Global Changes": "Zmiany globalne",
"Global Discovery": "Globalne odnajdywanie",
@@ -123,6 +131,7 @@
"Latest Change": "Ostatnia zmiana",
"Learn more": "Zobacz więcej",
"Listeners": "Nasłuchujący",
"Loading data...": "Loading data...",
"Local Discovery": "Lokalne odnajdywanie",
"Local State": "Status lokalny",
"Local State (Total)": "Status lokalny (suma)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maksymalny wiek",
"Metadata Only": "Tylko metadane",
"Minimum Free Disk Space": "Minimum wolnego miejsca na dysku",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Przenieś na początek kolejki",
"Multi level wildcard (matches multiple directory levels)": "Wieloznaczność na poziomie katalogów i plików (uwzględnia nazwy folderów i plików)",
"Never": "Nigdy",
@@ -139,6 +150,7 @@
"Newest First": "Najnowsze na początku",
"No": "Nie",
"No File Versioning": "Bez wersjonowania pliku",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Brak aktualizacji",
"Normal": "Zwykły",
"Notice": "Wskazówka",
@@ -153,6 +165,7 @@
"Override Changes": "Nadpisz zmiany",
"Path": "Ścieżka",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Ścieżka gdzie wersje będą przechowywane (pozostaw puste dla domyślnego folderu .stversions we współ. folderze).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ścieżka gdzie będą przechowywane wersje (pozostaw puste dla domyślnego folderu .stversions)",
"Pause": "Zatrzymaj",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Wydania kandydujące zawierają najnowsze funkcje oraz poprawki błędów. Są one podobne do tradycyjnych co dwutygodniowych wydań Syncthing.",
"Remote Devices": "Urządzenia zdalne",
"Remove": "Usuń",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Wymagany identyfikator dla folderu. Musi być taki sam na wszystkich urządzeniach.",
"Rescan": "Skanuj ponownie",
"Rescan All": "Skanuj wszystko ponownie",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Wyłączanie ukończone",
"Simple File Versioning": "Proste wersjonowanie pliku",
"Single level wildcard (matches within a directory only)": "Wieloznaczność na poziomie plików (uwzględnia nazwy plików)",
"Size": "Size",
"Smallest First": "Najmniejsze na początku",
"Source Code": "Kod źródłowy",
"Stable releases and release candidates": "Wydania stabilne i wydania kandydujące",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "To jest ważna aktualizacja",
"This setting controls the free space required on the home (i.e., index database) disk.": "Te ustawienia kontrolują ilość potrzebnej wolnej przestrzeni na dysku domowym (np. indeksowanie bazy danych).",
"Time": "Czas",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Kontrola werjsi plików w koszu",
"Type": "Typ",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Jeszcze nie zdecydowałem (przypomnij później)",
"Unknown": "Nieznany",
"Unshared": "Nieudostępnione",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anônimo de uso mudou. Gostaria de usar o formato novo?",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados em um apresentador também serão adicionados a este dispositivo.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "A atualização automática agora oferece a escolha entre versões estáveis e candidatas ao lançamento.",
"Automatic upgrades": "Atualizações automáticas",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Tenha cuidado!",
"Bugs": "Erros",
"CPU Utilization": "Uso de CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurado",
"Connection Error": "Erro de conexão",
"Connection Type": "Tipo da conexão",
"Connections": "Connections",
"Copied from elsewhere": "Copiado de outro lugar",
"Copied from original": "Copiado do original",
"Copyright © 2014-2016 the following Contributors:": "Direitos reservados © 2014-2016 aos seguintes colaboradores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 dos seguintes Colaboradores:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Criando filtros, sobrescrevendo o arquivo {{path}}.",
"Danger!": "Perigo!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Apagado",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositivo \"{{name}}\" ({{device}} em {{address}}) quer se conectar. Adicionar novo dispositivo?",
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositivos",
"Disabled": "Desabilitado",
"Disconnected": "Desconectado",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Endereço de escuta da interface web",
"GUI Listen Addresses": "Endereços de escuta da interface",
"GUI Theme": "Tema da interface",
"General": "General",
"Generate": "Gerar",
"Global Changes": "Alterações globais",
"Global Discovery": "Descoberta global",
@@ -123,6 +131,7 @@
"Latest Change": "Última mudança",
"Learn more": "Saiba mais",
"Listeners": "Escutadores",
"Loading data...": "Loading data...",
"Local Discovery": "Descoberta local",
"Local State": "Estado local",
"Local State (Total)": "Estado local (total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Idade máxima",
"Metadata Only": "Somente metadados",
"Minimum Free Disk Space": "Espaço livre mínimo no disco",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Mover para o topo da lista",
"Multi level wildcard (matches multiple directory levels)": "Coringa multi-nível (faz corresponder a vários níveis de pastas)",
"Never": "Nunca",
@@ -139,6 +150,7 @@
"Newest First": "Mais novo primeiro",
"No": "Não",
"No File Versioning": "Desligado",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Sem atualizações",
"Normal": "Normal",
"Notice": "Aviso",
@@ -153,6 +165,7 @@
"Override Changes": "Sobrescrever alterações",
"Path": "Caminho",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Caminho para a pasta na máquina local. Será criado caso não exista. O caractere til (~) pode ser usado como um atalho para",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Caminho do diretório onde as versões são salvas (deixe em branco para que seja o diretório padrão .stversions dentro da pasta compartilhada). ",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "O caminho onde as versões serão salvas (deixe vazio para usar a pasta padrão .stversions dentro desta pasta).",
"Pause": "Pausar",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Versões candidatas ao lançamento possuem os recursos e correções mais recentes. Elas são similares às tradicionais versões quinzenais.",
"Remote Devices": "Dispositivos remotos",
"Remove": "Remover",
"Remove Device": "Remove Device",
"Remove Folder": "Remover pasta",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador obrigatório da pasta. Deve ser igual em todos os dispositivos do grupo.",
"Rescan": "Verificar agora",
"Rescan All": "Verificar todas",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Desligamento completado",
"Simple File Versioning": "Simples",
"Single level wildcard (matches within a directory only)": "Coringa de único nível (faz corresponder apenas dentro de uma pasta)",
"Size": "Size",
"Smallest First": "Menor primeiro",
"Source Code": "Código-fonte",
"Stable releases and release candidates": "Versões estáveis e candidatas ao lançamento",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Esta é uma atualização para uma versão \"major\".",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este ajuste controla o espaço livre necessário no disco que contém o banco de dados do Syncthing.",
"Time": "Hora",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Lixeira",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Não tenho certeza (perguntar sempre)",
"Unknown": "Desconhecida",
"Unshared": "Não compartilhada",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Enviar relatórios anónimos de utilização",
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anónimo de utilização foi alterado. Gostaria de mudar para o novo formato?",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados num dispositivo apresentador serão também adicionados a este dispositivo.",
"Are you sure you want to remove device {%name%}?": "Tem a certeza que quer remover o dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Tem a certeza que quer remover a pasta {{label}}?",
"Auto Accept": "Aceitar automaticamente",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "A actualização automática agora oferece a escolha entre versões estáveis e candidatas a lançamento.",
"Automatic upgrades": "Actualizações automáticas",
"Automatically create or share folders that this device advertises at the default path.": "Criar ou partilhar automaticamente pastas que este dispositivo publicita no caminho predefinido.",
"Be careful!": "Tenha cuidado!",
"Bugs": "Erros",
"CPU Utilization": "Utilização da CPU",
@@ -41,18 +45,21 @@
"Configured": "Configurado",
"Connection Error": "Erro de ligação",
"Connection Type": "Tipo de ligação",
"Connections": "Ligações",
"Copied from elsewhere": "Copiado doutro sítio",
"Copied from original": "Copiado do original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 os seguintes contribuidores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 dos seguintes contribuidores:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Criando padrões de exclusão, sobrescrevendo um ficheiro existente em {{path}}.",
"Danger!": "Perigo!",
"Default Folder Path": "Caminho da pasta predefinida",
"Deleted": "Eliminado",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "O dispositivo \"{{name}}\" ({{device}} em {{address}}) quer conectar-se. Adiciono este novo dispositivo?",
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"Device that last modified the item": "Dispositivo que modificou o item pela última vez",
"Devices": "Dispositivos",
"Disabled": "Desactivado",
"Disconnected": "Desconectado",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Endereço de escuta da interface gráfica",
"GUI Listen Addresses": "Endereço de escuta da interface gráfica",
"GUI Theme": "Tema gráfico",
"General": "Geral",
"Generate": "Gerar",
"Global Changes": "Alterações globais",
"Global Discovery": "Pesquisa global",
@@ -123,6 +131,7 @@
"Latest Change": "Última alteração",
"Learn more": "Saiba mais",
"Listeners": "Auscultadores",
"Loading data...": "Carregando dados...",
"Local Discovery": "Pesquisa local",
"Local State": "Estado local",
"Local State (Total)": "Estado local (total)",
@@ -131,6 +140,8 @@
"Maximum Age": "Idade máxima",
"Metadata Only": "Metadados apenas",
"Minimum Free Disk Space": "Espaço livre mínimo no disco",
"Mod. Device": "Dispositivo mod.",
"Mod. Time": "Quando mod.",
"Move to top of queue": "Mover para o topo da fila",
"Multi level wildcard (matches multiple directory levels)": "Caractere polivalente multi-nível (faz corresponder a vários níveis de pastas)",
"Never": "Nunca",
@@ -139,6 +150,7 @@
"Newest First": "Primeiro os mais recentes",
"No": "Não",
"No File Versioning": "Nenhuma",
"No files will be deleted as a result of this operation.": "Nenhum ficheiro será eliminado como resultado desta operação.",
"No upgrades": "Sem actualizações",
"Normal": "Normal",
"Notice": "Avisos",
@@ -153,6 +165,7 @@
"Override Changes": "Sobrepor alterações",
"Path": "Caminho",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Caminho para a pasta no computador local. Será criada, caso não exista. O caractere (~) pode ser utilizado como atalho para",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Caminho no qual as novas pastas aceites automaticamente serão criadas, assim como o caminho predefinido sugerido ao adicionar novas pastas através da interface do utilizador. O til (~) expande para {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Caminho da pasta onde as versões deverão ser guardadas (deixe vazio para ficar a pasta predefinida .stversions dentro da pasta partilhada).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Caminho onde as versões são guardadas (deixe vazio para usar a pasta predefinida .stversions dentro da pasta a que se refere).",
"Pause": "Pausar",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Versões candidatas a lançamento contêm as funcionalidades e as correcções mais recentes. São semelhantes aos tradicionais lançamentos bi-semanais do Syncthing.",
"Remote Devices": "Dispositivos remotos",
"Remove": "Remover",
"Remove Device": "Remover dispositivo",
"Remove Folder": "Remover pasta",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador obrigatório para a pasta. Tem que ser igual em todos os dispositivos do grupo.",
"Rescan": "Verificar agora",
"Rescan All": "Verificar todas agora",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Encerramento completado",
"Simple File Versioning": "Simples",
"Single level wildcard (matches within a directory only)": "Caractere polivalente de um só nível (faz corresponder apenas dentro de uma pasta)",
"Size": "Tamanho",
"Smallest First": "Primeiro os menores",
"Source Code": "Código fonte",
"Stable releases and release candidates": "Versões estáveis e versões candidatas a lançamento",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Esta é uma actualização para uma versão importante.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este parâmetro controla o espaço livre necessário no disco base (ou seja, o disco da base de dados do índice).",
"Time": "Quando",
"Time the item was last modified": "Quando o item foi modificado pela última vez",
"Trash Can File Versioning": "Reciclagem",
"Type": "Tipo",
"Unavailable": "Indisponível",
"Unavailable/Disabled by administrator or maintainer": "Indisponível/Desactivado pelo administrador ou responsável de manutenção",
"Undecided (will prompt)": "Não definido (será inquirido na altura)",
"Unknown": "Desconhecido",
"Unshared": "Não partilhada",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимных отчётов изменился. Вы хотите переключиться на новый формат?",
"Any devices configured on an introducer device will be added to this device as well.": "Все устройства, подключённые к устройству-рекомендателю, будут добавлены к текущему устройству.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматическое обновление теперь предлагает выбор между стабильными выпусками и кандидатами в релизы.",
"Automatic upgrades": "Автообновление",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Будьте осторожны!",
"Bugs": "Ошибки",
"CPU Utilization": "Загрузка ЦП",
@@ -41,20 +45,23 @@
"Configured": "Сконфигурировано",
"Connection Error": "Ошибка подключения",
"Connection Type": "Тип соединения",
"Connections": "Connections",
"Copied from elsewhere": "Скопировано из другого места",
"Copied from original": "Скопировано с оригинала",
"Copyright © 2014-2016 the following Contributors:": "Авторские права © 20142016 принадлежат:",
"Copyright © 2014-2017 the following Contributors:": "Авторские права © 2014—2017 следующие участники:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Опасно!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Удалено",
"Device": "Устройство",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство «{{name}}» ({{device}} на {{address}}) хочет подключиться. Добавить новое устройство?",
"Device ID": "ID устройства",
"Device Identification": "Идентификация устройства",
"Device Name": "Имя устройства",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Устройства",
"Disabled": "Disabled",
"Disabled": "Отключено",
"Disconnected": "Нет соединения",
"Discovered": "Обнаружено",
"Discovery": "Обнаружение",
@@ -86,7 +93,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Когда Syncthing изменяет или удаляет файлы, их версии с таймштампами помещаются в папку .stversions",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Файлы с временнОй меткой версии помещаются в папку .stversions при их замене или удалении Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файлы защищены от изменений сделанных на других устройствах, но изменения сделанные на этом устройстве будут отправлены всему кластеру.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Notifications": "Уведомления файловой системы",
"Folder": "Папка",
"Folder ID": "ID папки",
"Folder Label": "Ярлык папки",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "Адрес панели управления",
"GUI Theme": "Тема оформления",
"General": "General",
"Generate": "Сгенерировать",
"Global Changes": "Глобальные изменения",
"Global Discovery": "Глобальное обнаружение",
@@ -123,6 +131,7 @@
"Latest Change": "Последнее изменение",
"Learn more": "Узнать больше",
"Listeners": "Прослушиватель",
"Loading data...": "Loading data...",
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
"Local State (Total)": "Локальное состояние (всего)",
@@ -131,6 +140,8 @@
"Maximum Age": "Максимальный срок",
"Metadata Only": "Только метаданные",
"Minimum Free Disk Space": "Минимальное свободное место на диске",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Поместить в начало очереди",
"Multi level wildcard (matches multiple directory levels)": "Многоуровневая маска (поиск совпадений во всех подпапках)",
"Never": "Никогда",
@@ -139,6 +150,7 @@
"Newest First": "Сначала новые",
"No": "Нет",
"No File Versioning": "Без управления версиями файлов",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Нет обновлений",
"Normal": "Нормально",
"Notice": "Внимание",
@@ -153,6 +165,7 @@
"Override Changes": "Перезаписать изменения",
"Path": "Путь",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Путь к папке на локальном компьютере. Если её не существует, то она будет создана. Тильда (~) может использоваться как сокращение для",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Путь, в котором нужно хранить версии (оставьте пустым для папки по умолчанию .stversions внутри общей папки).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Путь, где должны храниться версии (оставьте пустым, чтобы использовать папку по умолчанию .stversions внутри папки).",
"Pause": "Пауза",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Кандидаты в релизы содержат последние улучшения и исправления. Они похожи на традиционные двухнедельные выпуски Syncthing.",
"Remote Devices": "Удалённые устройства",
"Remove": "Удалить",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Обязательный идентификатор папки. Должен быть одним и тем же на всех устройствах кластера.",
"Rescan": "Пересканировать",
"Rescan All": "Пересканировать все",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Выключение",
"Simple File Versioning": "Простое управление версиями файлов",
"Single level wildcard (matches within a directory only)": "Одноуровневая маска (поиск совпадений только внутри папки)",
"Size": "Size",
"Smallest First": "Сначала маленькие",
"Source Code": "Исходный код",
"Stable releases and release candidates": "Стабильные выпуски и кандидаты в релизы",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Это обновление основной версии продукта.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Эта настройка управляет свободным местом, необходимым на домашнем диске (например, для базы индексов).",
"Time": "Время",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Использовать версионность для файлов в Корзине",
"Type": "Тип",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Неизвестно",
"Unshared": "Необщедоступно",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonymné hlásenie o používaní",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Všetky zariadenia nakonfigurované na uvádzači budú tiež pridané na tomto zariadení.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatická aktualizácia teraz ponúka voľbu medzi stabilnými vydaniami a kandidátmi na vydanie.",
"Automatic upgrades": "Automatické aktualizácie",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Buď opatrný!",
"Bugs": "Chyby",
"CPU Utilization": "Využitie CPU",
@@ -41,18 +45,21 @@
"Configured": "Nakonfigurované",
"Connection Error": "Chyba pripojenia",
"Connection Type": "Typ pripojenia",
"Connections": "Connections",
"Copied from elsewhere": "Skoprírované odinakiaľ",
"Copied from original": "Skopírované z originálu",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 následujúci prispivatelia:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 následujúci prispivatelia:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Vytváranie vzorov ignorovania, prepísanie existujúceho súboru v {{path}}.",
"Danger!": "Pozor!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Zmazané",
"Device": "Zariadenie",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Zariadenie \"{{name}}\" ({{device}} na {{address}}) sa chce pripojiť. Pridať nové zariadenie?",
"Device ID": "ID zariadenia",
"Device Identification": "Identifikácia zariadenia",
"Device Name": "Názov zariadenia",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Zariadenia",
"Disabled": "Disabled",
"Disconnected": "Odpojené",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Adresa pre prístup do GUI",
"GUI Listen Addresses": "Adresa pre prístup do GUI",
"GUI Theme": "Grafická téma GUI",
"General": "General",
"Generate": "Generovať",
"Global Changes": "Globálne zmeny",
"Global Discovery": "Globálne vyhľadávanie",
@@ -123,6 +131,7 @@
"Latest Change": "Posledná zmena",
"Learn more": "Zisti viac",
"Listeners": "Načúvajúci",
"Loading data...": "Loading data...",
"Local Discovery": "Lokálne vyhľadávanie",
"Local State": "Lokálny status",
"Local State (Total)": "Lokálny status (celkový)",
@@ -131,6 +140,8 @@
"Maximum Age": "Maximálny časový limit",
"Metadata Only": "Iba metadáta",
"Minimum Free Disk Space": "Minimálna veľkosť voľného miesta na disku",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Presun na začiatok poradia",
"Multi level wildcard (matches multiple directory levels)": "Viacúrovňový zástupný znak (zhoda naprieč viacerými úrovňami adresára)",
"Never": "Nikdy",
@@ -139,6 +150,7 @@
"Newest First": "Najnovší najprv",
"No": "Nie",
"No File Versioning": "Bez verzií súbor",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Bez aktualizácií",
"Normal": "Normalny",
"Notice": "Oznámenie",
@@ -153,6 +165,7 @@
"Override Changes": "Prepísať zmeny",
"Path": "Cesta",
"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áru na lokálnom počítači. Ak neexistuje, bude vytvorená. Znak vlnovky (~) môže byť použitý ako skratka pre",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Cesta, kde budú uložené verzie (ponechajte prázdne pre predvolený adresár .stversions v zdieľanom adresari)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Cesta, kde budú uložené verzie (ponechajte prázdne pre predvolený adresár .stversions v adresári).",
"Pause": "Pozastaviť",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidáti na vydanie obsahujú najnovšie vlastnosti a opravy. Sú podobné tradičným dvojtýždenným vydaniam programu Syncthing.",
"Remote Devices": "Vzdialené zariadenia",
"Remove": "Odstrániť",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Potrebný identifikátor pre adresár. Musí byť rovnaký na všetkých zariadeniach v skupine.",
"Rescan": "Opakovať skenovanie",
"Rescan All": "Opakovať skenovanie všetkých",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Vypnutie ukončené",
"Simple File Versioning": "Jednoduché verzie súborov",
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Size": "Size",
"Smallest First": "Najmenší najprv",
"Source Code": "Zdrojový kód",
"Stable releases and release candidates": "Stabilné verzie a kandidáti na vydanie",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Toto je hlavná aktualizácia.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Toto nastavenie kontroluje voľné miesto požadované na domovskom disku (napr. indexová databáza).",
"Time": "Čas",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Verzie súborov v koši",
"Type": "Typ",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Neznáme",
"Unshared": "Nezdieľané",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonym användarstatistiksrapportering",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymt användningsrapportformat har ändrats. Vill du flytta till det nya formatet?",
"Any devices configured on an introducer device will be added to this device as well.": "Alla enheter konfigurerade på en introduktör enhet kommer också att läggas till den här enheten.",
"Are you sure you want to remove device {%name%}?": "Är du säker på att du vill ta bort enheten {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Är du säker på att du vill ta bort mappen {{label}}?",
"Auto Accept": "Auto acceptera",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatisk uppgradering erbjuder nu valet mellan stabila utgåvor och utgåvskandidater.",
"Automatic upgrades": "Automatiska uppgraderingar",
"Automatically create or share folders that this device advertises at the default path.": "Skapa eller dela automatiskt mappar som den här enheten annonserar på standardsökvägen.",
"Be careful!": "Var aktsam!",
"Bugs": "Buggar",
"CPU Utilization": "CPU användning",
@@ -41,18 +45,21 @@
"Configured": "Konfigurerad",
"Connection Error": "Anslutningsproblem",
"Connection Type": "Anslutningstyp",
"Connections": "Anslutningar",
"Copied from elsewhere": "Kopierat från annanstans",
"Copied from original": "Kopierat från original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 följande bidragare:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 följande bidragande:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Skapa ignorera mönster, skriver över en existerande fil på {{path}}.",
"Danger!": "Fara!",
"Default Folder Path": "Standard mappsökväg",
"Deleted": "Tog bort",
"Device": "Enhet",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
"Device ID": "Enhet-ID",
"Device Identification": "Enhetsidentifikation",
"Device Name": "Enhetsnamn",
"Device that last modified the item": "Enhet som senast ändrade objektet",
"Devices": "Enheter",
"Disabled": "Inaktiverad",
"Disconnected": "Frånkopplad",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI-lyssnaradress",
"GUI Listen Addresses": "GUI-lyssnaradresser",
"GUI Theme": "GUI-tema",
"General": "Allmänt",
"Generate": "Generera",
"Global Changes": "Globala ändringar",
"Global Discovery": "Global annonsering",
@@ -123,14 +131,17 @@
"Latest Change": "Senaste ändring",
"Learn more": "Ta reda på mer",
"Listeners": "Lyssnare",
"Loading data...": "Laddar data...",
"Local Discovery": "Lokal annonsering",
"Local State": "Lokal tillstånd",
"Local State (Total)": "Lokal tillstånd (totalt)",
"Local State": "Lokalt tillstånd",
"Local State (Total)": "Lokalt tillstånd (totalt)",
"Major Upgrade": "Större uppgradering",
"Master": "Huvud",
"Maximum Age": "Maximum ålder",
"Metadata Only": "Endast metadata",
"Minimum Free Disk Space": "Minsta ledigt diskutrymme",
"Mod. Device": "Enhet som utförde ändring",
"Mod. Time": "Tid för ändring",
"Move to top of queue": "Flytta till överst i kön",
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
"Never": "Aldrig",
@@ -139,6 +150,7 @@
"Newest First": "Nyast först",
"No": "Nej",
"No File Versioning": "Ingen filversionshantering",
"No files will be deleted as a result of this operation.": "Inga filer kommer att tas bort till följd av denna operation.",
"No upgrades": "Inga uppgraderingar",
"Normal": "Normal",
"Notice": "Observera",
@@ -153,6 +165,7 @@
"Override Changes": "Åsidosätt förändringar",
"Path": "Sökväg",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sökväg till mappen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Sökvägen där nya automatiskt accepterade mappar kommer att skapas, liksom den föreslagna sökvägen när du lägger till nya mappar via gränssnittet. Tilde tecken (~) expanderar till {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Sökväg där versioner ska lagras (lämna tomt för standard .stversions-katalogen i den delade katalogen).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna tomt för att använda standard .stversions-mappen i mappen).",
"Pause": "Paus",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Utgåvskandidater innehåller de senaste funktionerna och korrigeringarna. De är lika de traditionella Syncthing-utgåvorna som kommer ut varannan vecka.",
"Remote Devices": "Fjärrenheter",
"Remove": "Ta bort",
"Remove Device": "Ta bort enhet",
"Remove Folder": "Ta bort mapp",
"Required identifier for the folder. Must be the same on all cluster devices.": "Krävs identifierare för mappen. Måste vara densamma på alla kluster enheter.",
"Rescan": "Skanna om",
"Rescan All": "Skanna om alla",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Avstängning klar",
"Simple File Versioning": "Enkel filversionshantering",
"Single level wildcard (matches within a directory only)": "Jokertecken som representerar noll eller fler godtyckliga tecken i ett filnamn.",
"Size": "Storlek",
"Smallest First": "Minst först",
"Source Code": "Källkod",
"Stable releases and release candidates": "Stabila utgåvor och utgåvskandidater",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Det här är en stor uppgradering.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Denna inställning kontrollerar ledigt utrymme som krävs på home (d.v.s. index-databas) disk.",
"Time": "Tid",
"Time the item was last modified": "Tidpunkten objektet var senast ändrad",
"Trash Can File Versioning": "Papperskorgs filversionshantering",
"Type": "Typ",
"Unavailable": "Inte tillgänglig",
"Unavailable/Disabled by administrator or maintainer": "Ej tillgänglig/inaktiverad av administratör eller underhållare",
"Undecided (will prompt)": "Obeslutad (kommer att skriva)",
"Unknown": "Okänd",
"Unshared": "Inte delad",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Anonim Kullanım Raporlaması",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Tanıtıcı bir cihaz üzerinde yapılandırılan cihazlar bu cihaza da eklenecektir.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Kendiliğinden yükseltme artık kararlı dağıtımlar ve sürüm adayları arasında seçim yapmayı sağlıyor.",
"Automatic upgrades": "Kendiliğinden yükseltmeler",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Dikkatli ol!",
"Bugs": "Yazılım Hataları",
"CPU Utilization": "İşlemci Kullanımı",
@@ -41,18 +45,21 @@
"Configured": "Yapılandırıldı",
"Connection Error": "Bağlantı hatası",
"Connection Type": "Bağlantı Türü",
"Connections": "Connections",
"Copied from elsewhere": "Başka bir yerden kopyalanmış",
"Copied from original": "Aslından kopyalanmış",
"Copyright © 2014-2016 the following Contributors:": "Telif Hakkı © 2014-2016 takip eden Katkıcılar:",
"Copyright © 2014-2017 the following Contributors:": "Telif Hakkı © 2014-2017 takip eden Katkıcılar:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Tehlike!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Silindi",
"Device": "Aygıt",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" aygıtı ({{address}} adresindeki {{device}}) bağlanmak istiyor. Yeni aygıtı ekliyor musun?",
"Device ID": "Aygıt ID",
"Device Identification": "Aygıt Kimliği",
"Device Name": "Aygıt Adı",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Aygıtlar",
"Disabled": "Disabled",
"Disconnected": "Bağlantı Kesik",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI Dinleme/Bağlantı Adresleri",
"GUI Theme": "GUI Gövdesi",
"General": "General",
"Generate": "Oluştur",
"Global Changes": "Küresel Değişiklikler",
"Global Discovery": "Küresel Discovery",
@@ -123,6 +131,7 @@
"Latest Change": "Son Değişim",
"Learn more": "Daha çoğunu öğren",
"Listeners": "Dinleyiciler",
"Loading data...": "Loading data...",
"Local Discovery": "Yerel Discovery",
"Local State": "Yerel Durum",
"Local State (Total)": "Yerel Durum (Toplamı)",
@@ -131,6 +140,8 @@
"Maximum Age": "Azami Süre",
"Metadata Only": "Yalnızca Üstveri",
"Minimum Free Disk Space": "En Az Boş Disk Alanı",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Kuyruğun başına taşı",
"Multi level wildcard (matches multiple directory levels)": "Çoklu düzey wildcard (çok sayıda dizin düzeyinde eşleşme)",
"Never": "Asla",
@@ -139,6 +150,7 @@
"Newest First": "En yeni olan önce",
"No": "Hayır",
"No File Versioning": "Dosya Sürümleme İşlemi Yok",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Yükseltme yok",
"Normal": "Olağan",
"Notice": "Uyarı",
@@ -153,6 +165,7 @@
"Override Changes": "Değişiklikleri Geçersiz kıl",
"Path": "Yol",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Yerel bilgisayardaki klasöre ulaşım yolu. Klasör yoksa yaratılacak. Tilde (~) karakterinin kısayol olarak kullanılabileceği yol",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sürümlerin saklanması için gereken klasör yolu (öntanımlı .stversions klasörü için boş bırakın)",
"Pause": "Duraklat",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Sürüm adayları en son özellikleri ve hata düzeltmelerini içerir. Bunlar, geleneksel olarak iki haftada bir yayımlanan Syncthing dağıtımlarına benzer.",
"Remote Devices": "Uzak Aygıtlar",
"Remove": "Kaldır",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Klasör için tanımlayıcı gereklidir. Tüm küme cihazlarda aynı olmalıdır.",
"Rescan": "Tekrar Tara",
"Rescan All": "Tümünü Tekrar Tara",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Kapatma İşlemi Tamamlandı",
"Simple File Versioning": "Basit Dosya Sürümleme İşlemi",
"Single level wildcard (matches within a directory only)": "Tekli düzey wildcard (yalnızca bir dizin içinde eşleşme)",
"Size": "Size",
"Smallest First": "En küçük olan önce",
"Source Code": "Kaynak Kodu",
"Stable releases and release candidates": "Kararlı dağıtımlar ve sürüm adayları",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Ana sürüm yükseltmesidir.",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Zaman",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Çöp Kutusu Dosya Sürümleme İşlemi",
"Type": "Tür",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Bilinmiyor",
"Unshared": "Paylaşılmayan",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "Анонімна статистика використання",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Змінився формат анонімного звіту про користування. Бажаєте перейти на новий формат?",
"Any devices configured on an introducer device will be added to this device as well.": "Усі пристрої, налаштовані на пристрої-рекомендувачі, будуть додані до поточного пристрою.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматиче оновлення зараз дозволяє обирати між стабільними випусками та реліз-кандидатами.",
"Automatic upgrades": "Автоматичні оновлення",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "Будьте обережні!",
"Bugs": "Помилки",
"CPU Utilization": "Навантаження CPU",
@@ -41,18 +45,21 @@
"Configured": "Налаштовано",
"Connection Error": "Помилка з’єднання",
"Connection Type": "Тип з*єднання",
"Connections": "Connections",
"Copied from elsewhere": "Скопійовано з іншого місця",
"Copied from original": "Скопійовано з оригіналу",
"Copyright © 2014-2016 the following Contributors:": "© 2014-2016 Всі права застережено, вклад внесли:",
"Copyright © 2014-2017 the following Contributors:": "© 2014-2017 Всі права застережено, вклад внесли:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Створення шаблонів винятків з перезаписом існуючого файлу {{path}}.",
"Danger!": "Небезпечно!",
"Default Folder Path": "Default Folder Path",
"Deleted": "Видалене",
"Device": "Пристрій",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Пристрій \"{{name}}\" ({{device}} за адресою {{address}}) намагається під’єднатися. Додати новий пристрій?",
"Device ID": "ID пристрою",
"Device Identification": "Ідентифікатор пристрою",
"Device Name": "Назва пристрою",
"Device that last modified the item": "Device that last modified the item",
"Devices": "Пристрої",
"Disabled": "Вимкнено",
"Disconnected": "З’єднання відсутнє",
@@ -99,6 +106,7 @@
"GUI Listen Address": "Адреса прослуховування GUI",
"GUI Listen Addresses": "Адреса доступу до панелі управління",
"GUI Theme": "Тема інтерфейсу",
"General": "General",
"Generate": "Згенерувати",
"Global Changes": "Глобальні зміни",
"Global Discovery": "Глобальне виявлення (internet)",
@@ -123,6 +131,7 @@
"Latest Change": "Найостанніша зміна",
"Learn more": "Дізнатися більше",
"Listeners": "Приймачі (TCP & Relay)",
"Loading data...": "Loading data...",
"Local Discovery": "Локальне виявлення (LAN)",
"Local State": "Локальний статус",
"Local State (Total)": "Локальний статус (загалом)",
@@ -131,6 +140,8 @@
"Maximum Age": "Максимальний вік",
"Metadata Only": "Тільки метадані",
"Minimum Free Disk Space": "Мінімальний вільний простір на диску",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "Пересунути у початок черги",
"Multi level wildcard (matches multiple directory levels)": "Багаторівнева маска (пошук збігів в усіх піддиректоріях) ",
"Never": "Ніколи",
@@ -139,6 +150,7 @@
"Newest First": "Спершу новіші",
"No": "Ні",
"No File Versioning": "Версіонування вимкнено",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "Немає оновлень",
"Normal": "Нормальний",
"Notice": "Повідомлення",
@@ -153,6 +165,7 @@
"Override Changes": "Розіслати мою версію",
"Path": "Шлях",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Шлях до директорії на локальному комп’ютері. Буде створений, якщо такий не існує. Символ тильди (~) може бути використаний як ярлик для",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
"Pause": "Пауза",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Реліз-кандидати містять найостанніші функції та виправлення. Вони схожі на традиційні щодвотижневі випуски Syncthing.",
"Remote Devices": "Віддалені пристрої",
"Remove": "Видалити",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Обов'язковий унікальний ідентифікатор директорії. Має бути однаковим на усіх пристроях кластеру.",
"Rescan": "Пересканувати",
"Rescan All": "Пересканувати усе",
@@ -210,6 +225,7 @@
"Shutdown Complete": "Вимикання завершене",
"Simple File Versioning": "Просте версіонування",
"Single level wildcard (matches within a directory only)": "Однорівнева маска (пошук збігів лише в середині директорії) ",
"Size": "Size",
"Smallest First": "Спершу найменші",
"Source Code": "Сирцевий код",
"Stable releases and release candidates": "Стабільні випуски та реліз-кандидати",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "Це оновлення мажорної версії",
"This setting controls the free space required on the home (i.e., index database) disk.": "Це налаштування визначає необхідний вільний простір на домашньому (тобто той, що містить базу даних) диску.",
"Time": "Час",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Версіонування файлів у кошику ",
"Type": "Тип",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Невизначено (буде запитано)",
"Unknown": "Невідомо",
"Unshared": "Не розповсюджується",

View File

@@ -1,296 +0,0 @@
{
"A device with that ID is already added.": "Đã có một thiết bị trùng ID.",
"A negative number of days doesn't make sense.": "Số ngày không được âm.",
"A new major version may not be compatible with previous versions.": "Phiên bản quan trọng mới có thể sẽ không tương thích với các bản cũ.",
"API Key": "Khoá API",
"About": "Thông tin về",
"Action": "Action",
"Actions": "Thao tác",
"Add": "Thêm",
"Add Device": "Thêm thiết bị",
"Add Folder": "Thêm thư mục",
"Add Remote Device": "Thêm thiết bị từ xa",
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
"Add new folder?": "Thêm thư mục mới?",
"Address": "Địa chỉ",
"Addresses": "Các địa chỉ",
"Advanced": "Nâng cao",
"Advanced Configuration": "Cấu hình nâng cao",
"Advanced settings": "Cài đặt nâng cao",
"All Data": "Tất cả dữ liệu",
"Allow Anonymous Usage Reporting?": "Cho phép báo cáo sử dụng ẩn danh?",
"Allowed Networks": "Allowed Networks",
"Alphabetic": "A-Z",
"An external command handles the versioning. It has to remove the file from the shared folder.": "An external command handles the versioning. It has to remove the file from the shared folder.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Một lệnh ngoại vi chịu trách nhiệm phiên bản hoá. Nó sẽ xoá tập tin khỏi thư mục đã đồng bộ.",
"Anonymous Usage Reporting": "Báo cáo sử dụng ẩn danh",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Bất kỳ thiết bị nào liên kết với thiết bị giới thiệu cũng sẽ được thêm vào.",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
"Automatic upgrades": "Cập nhật tự động",
"Be careful!": "Cẩn thận!",
"Bugs": "Lỗi",
"CPU Utilization": "Mức s.dụng CPU",
"Changelog": "Lịch sử thay đổi",
"Clean out after": "Dọn dẹp sau",
"Click to see discovery failures": "Click to see discovery failures",
"Close": "Đóng",
"Command": "Lệnh",
"Comment, when used at the start of a line": "Bình luận, khi dùng trước đầu dòng",
"Compression": "Nén",
"Configured": "Configured",
"Connection Error": "Lỗi kết nối",
"Connection Type": "Connection Type",
"Copied from elsewhere": "Đã sao chép từ nơi khác",
"Copied from original": "Đã sao chép từ nguồn",
"Copyright © 2014-2016 the following Contributors:": "Bản quyền © 2014-2016 thuộc về các nhà cộng tác sau:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Nguy hiểm!",
"Deleted": "Đã xoá",
"Device": "Device",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Thiết bị \"{{name}}\" ({{device}} tại {{address}}) muốn kết nối. Thêm thiết bị mới?",
"Device ID": "ID thiết bị",
"Device Identification": "Danh tính thiết bị",
"Device Name": "Tên thiết bị",
"Devices": "Các thiết bị",
"Disabled": "Disabled",
"Disconnected": "Đã ngắt kết nối",
"Discovered": "Discovered",
"Discovery": "Tìm thấy",
"Discovery Failures": "Discovery Failures",
"Documentation": "Tài liệu",
"Download Rate": "Tốc độ tải xuống",
"Downloaded": "Đã tải xuống",
"Downloading": "Đang tải xuống",
"Edit": "Chỉnh sửa",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Đang ch.sửa",
"Editing {%path%}.": "Editing {{path}}.",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Bật chế độ ch.tiếp",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Nhập các địa chỉ ngăn cách bởi dấu phẩy (\"tcp://ip:port\", \"tcp://host:port\") hoặc \"dynamic\" để tiến hành dò tìm địa chỉ tự động.",
"Enter ignore patterns, one per line.": "Nhập các quy luật bỏ qua, từng dòng một.",
"Error": "Lỗi",
"External File Versioning": "Kiểu ngoại vi",
"Failed Items": "Các n.dung bị lỗi",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
"File Pull Order": "Thứ tự pull tập tin",
"File Versioning": "Ph.bản hoá tập tin",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Các ph.tử g.phép tập tin sẽ được bỏ qua khi t.kiếm th.đổi. Dùng trên h.thống tập tin FAT.",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Files are moved to .stversions directory when replaced or deleted by Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Các t.tin sẽ được chuyển tới th.mục .stversions khi bị th.thế hoặc xoá bởi Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Các t.tin sẽ được chuyển tới các ph.bản được đ.dấu ng.tháng trong th.mục .stversions khi bị th.thế hoặc xoá bởi 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.": "Các t.tin được b.vệ khỏi những th.đổi trên các th.bị khác, nhưng những th.đổi trên th.bị này sẽ được chuyển tới các máy cụm còn lại.",
"Filesystem Notifications": "Filesystem Notifications",
"Folder": "Thư mục",
"Folder ID": "ID thư mục",
"Folder Label": "Nhãn thư mục",
"Folder Path": "Đ.dẫn đến th.mục",
"Folder Type": "Folder Type",
"Folders": "Các th.mục",
"GUI": "GUI",
"GUI Authentication Password": "Mật khẩu xác minh GUI",
"GUI Authentication User": "Người dùng xác minh GUI",
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "Các đ.chỉ lắng nghe GUI",
"GUI Theme": "GUI Theme",
"Generate": "Tạo mới",
"Global Changes": "Global Changes",
"Global Discovery": "Dò tìm toàn cầu",
"Global Discovery Servers": "Các m.chủ dò tìm toàn cầu",
"Global State": "Tr.thái toàn cầu",
"Help": "Trợ giúp",
"Home page": "Trang chủ",
"Ignore": "Bỏ qua",
"Ignore Patterns": "Bỏ qua các quy luật",
"Ignore Permissions": "Bỏ qua các giấy phép",
"Incoming Rate Limit (KiB/s)": "Giới hạn t.độ đầu vào (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Cấu hình không đúng có thể làm mất mát dữ liệu và khiến Syncthing ngừng hoạt động.",
"Introduced By": "Introduced By",
"Introducer": "Th.bị giới thiệu",
"Inversion of the given condition (i.e. do not exclude)": "Đảo ngược điều kiện cho trước (VD: không được loại trừ)",
"Keep Versions": "Giữ các ph.bản",
"Largest First": "Lớn nhất đầu tiên",
"Last File Received": "T.tin nhận được gần đây",
"Last Scan": "Last Scan",
"Last seen": "Thấy lần cuối",
"Later": "Để sau",
"Latest Change": "Latest Change",
"Learn more": "Learn more",
"Listeners": "Listeners",
"Local Discovery": "Dò tìm cục bộ",
"Local State": "Tr.thái cục bộ",
"Local State (Total)": "Tr.thái cục bộ (Tổng)",
"Major Upgrade": "Bản n.cấp q.trọng",
"Master": "Master",
"Maximum Age": "Thời hạn tối đa",
"Metadata Only": "Chỉ siêu dữ liệu",
"Minimum Free Disk Space": "Dung lượng đĩa trống tối thiểu",
"Move to top of queue": "Chuyển đến đầu hàng chờ",
"Multi level wildcard (matches multiple directory levels)": "Ký tự thay thế đa cấp (phù hợp với đa cấp độ thư mục)",
"Never": "Chưa từng",
"New Device": "Thiết bị mới",
"New Folder": "Thư mục mới",
"Newest First": "Mới nhất đầu tiên",
"No": "Không",
"No File Versioning": "Không dùng",
"No upgrades": "No upgrades",
"Normal": "Normal",
"Notice": "Chú ý",
"OK": "OK",
"Off": "Tắt",
"Oldest First": "Cũ nhất đầu tiên",
"Optional descriptive label for the folder. Can be different on each device.": "Nhãn mô tả tuỳ chọn cho thư mục. Có thể khác nhau trên từng thiết bị.",
"Options": "Tuỳ chọn",
"Out of Sync": "Mất đồng bộ",
"Out of Sync Items": "Các n.dung mất đ.bộ",
"Outgoing Rate Limit (KiB/s)": "Giới hạn t.độ đầu ra (KiB/s)",
"Override Changes": "Ghi đè các th.đổi",
"Path": "Path",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Đường dẫn đến thư mục trên máy cục bộ. Sẽ tạo mới nếu chưa hiện hữu. Dấu ngã (~) có thể được dùng làm lối tắt cho",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Đường dẫn nơi các phiên bản được lưu trữ (nếu để trống, thư mục mặc định sẽ là .stversions).",
"Pause": "Tạm dừng",
"Pause All": "Pause All",
"Paused": "Đã t.dừng",
"Please consult the release notes before performing a major upgrade.": "Hãy xem kỹ lịch sử phát hành trước khi tiến hành bản cập nhật q.trọng.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Hãy thiết lập tên ng.dùng và mật khẩu xác minh GUI trong hộp thoại Cài đặt.",
"Please wait": "Xin chờ",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
"Preview": "Xem trước",
"Preview Usage Report": "Xem trước báo cáo s.dụng",
"Quick guide to supported patterns": "H.dẫn sơ lược về các q.luật được hỗ trợ",
"RAM Utilization": "Mức s.dụng RAM",
"Random": "Ngẫu nhiên",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Lịch sử phát hành",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.",
"Remote Devices": "Các thiết bị từ xa",
"Remove": "Xoá",
"Required identifier for the folder. Must be the same on all cluster devices.": "Tên tắt bắt buộc cho thư mục. Phải trùng khớp trên tất cả thiết bị trong cụm.",
"Rescan": "Quét lại",
"Rescan All": "Quét lại t.cả",
"Rescan Interval": "Kh.cách th.gian quét lại",
"Restart": "Kh.động lại",
"Restart Needed": "Cần kh.động lại",
"Restarting": "Đang kh.động lại",
"Resume": "Tiếp tục",
"Resume All": "Resume All",
"Reused": "Đã s.dụng lại",
"Save": "Lưu",
"Scan Time Remaining": "Thời gian quét còn lại",
"Scanning": "Đang quét",
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select a version": "Select a version",
"Select the devices to share this folder with.": "Chọn các thiết bị để chia sẻ thư mục này.",
"Select the folders to share with this device.": "Chọn các thư mục để chia sẻ với thiết bị này.",
"Send & Receive": "Send & Receive",
"Send Only": "Send Only",
"Settings": "Cài đặt",
"Share": "Chia sẻ",
"Share Folder": "Chia sẻ th.mục",
"Share Folders With Device": "Chia sẻ các th.mục với th.bị",
"Share With Devices": "Chia sẻ với các th.bị",
"Share this folder?": "Chia sẻ th.mục này?",
"Shared With": "Đã ch.sẻ với",
"Show ID": "Hiển thị ID",
"Show QR": "Hiển thị QR",
"Show diff with previous version": "Show diff with previous version",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Hiển thị thay cho ID th.bị trong trạng thái cụm. Sẽ được giới thiệu đến các th.bị khác như tên mặc định tuỳ chọn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Hiển thị thay cho ID thiết bị trong trạng thái cụm. Nếu để trống sẽ được cập nhật thành tên mà thiết bị giới thiệu.",
"Shutdown": "Tắt",
"Shutdown Complete": "Tắt hoàn tất",
"Simple File Versioning": "Kiểu đơn giản",
"Single level wildcard (matches within a directory only)": "Ký tự thay thế đơn cấp (phù hợp với chỉ một thư mục)",
"Smallest First": "Nhỏ nhất đầu tiên",
"Source Code": "Mã nguồn",
"Stable releases and release candidates": "Stable releases and release candidates",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.",
"Stable releases only": "Stable releases only",
"Staggered File Versioning": "Theo thời gian",
"Start Browser": "Mở trình duyệt",
"Statistics": "Thống kê",
"Stopped": "Đã dừng",
"Support": "Hỗ trợ",
"Sync Protocol Listen Addresses": "Đồng bộ các đ.chỉ l.nghe giao thức",
"Syncing": "Đang đ.bộ",
"Syncthing has been shut down.": "Đã tắt Syncthing.",
"Syncthing includes the following software or portions thereof:": "Syncthing bao gồm các ph.mềm hoặc ph.tử của các ph.mềm sau:",
"Syncthing is restarting.": "Syncthing đang kh.động lại.",
"Syncthing is upgrading.": "Syncthing đang n.cấp.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Có vẻ như Syncthing đang bị nghẽn hoặc là mạng của bạn có vấn đề. Đang thử lại...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Có vẻ như Syncthing đang gặp phải v.đề khi x.lý yêu cầu của bạn. Xin làm mới trang hoặc kh.động lại Syncthing nếu v.đề vẫn còn tiếp diễn.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Giao diện q.trị của Syncthing được c.hình nhằm cho phép tr.cập từ xa không cần mật khẩu.",
"The aggregated statistics are publicly available at the URL below.": "The aggregated statistics are publicly available at the URL below.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Cấu hình đã được lưu nhưng chưa được kích hoạt. Syncthing phải khởi động lại để kích hoạt cấu hình mới. ",
"The device ID cannot be blank.": "Không được để trống ID thiết bị.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID thiết bị cần nhập có thể được tìm thấy trong hộp thoại \"Thao tác > Hiển thị ID\" trên thiết bị kia. Khoảng trắng và gạch ngang là tuỳ chọn (bỏ qua).",
"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.": "Báo cáo s.dụng đã mã hoá sẽ được gửi đi hằng ngày. Nó được dùng để t.thập s.liệu về các HĐH phổ biến, kích cỡ th.mục và ph.bản ứng dụng. Nếu bộ d.liệu báo cáo có th.đổi, bạn sẽ được nhắc thông qua h.thoại này.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "ID thiết bị đã nhập không hợp lệ. Nó phải là một chuỗi từ 52 đến 56 ký tự, bao gồm chữ cái và các con số, với khoảng trắng và gạch ngang là tuỳ chọn.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Tham số dòng lệnh đầu tiên là đường dẫn thư mục và tham số thứ hai là đường dẫn tương đối trong thư mục.",
"The folder ID cannot be blank.": "Không được để trống ID thư mục.",
"The folder ID must be unique.": "ID thư mục phải là duy nhất.",
"The folder path cannot be blank.": "Không được để trống đ.dẫn đến th.mục.",
"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.": "Các khoảng th.gian sau đây sẽ được s.dụng: một phiên bản, trong giờ đầu tiên, được giữ lại mỗi 30 giây, trong ngày đầu tiên là mỗi giờ, trong 30 ngày đầu tiên là mỗi ngày, cho đến khi th.hạn tối đa mỗi ph.bản được giữ lại là mỗi tuần. ",
"The following items could not be synchronized.": "Không thể đồng bộ những nội dung sau.",
"The maximum age must be a number and cannot be blank.": "Thời hạn tối đa phải là một con số và không được để trống.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Th.gian t.đa một ph.bản được giữ lại (tính bằng ngày, nhập 0 để giữ tập tin mãi mãi).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Phần trăm dung lượng đĩa trống tối thiểu phải là một số không âm (nằm trong khoảng) từ 0 đến 100.",
"The number of days must be a number and cannot be blank.": "Số ngày phải là một con số và không được để trống.",
"The number of days to keep files in the trash can. Zero means forever.": "Số ngày tập tin được giữ trong thùng rác. 0 nghĩa là mãi mãi.",
"The number of old versions to keep, per file.": "Số phiên bản cũ cần giữ lại với mỗi tập tin.",
"The number of versions must be a number and cannot be blank.": "Số phiên bản phải là một con số và không được để trống.",
"The path cannot be blank.": "Không được để trống đường dẫn.",
"The rate limit must be a non-negative number (0: no limit)": "Giới hạn tốc độ phải là một số không âm (0: không giới hạn)",
"The rescan interval must be a non-negative number of seconds.": "Khoảng thời gian quét lại phải là một số giây không âm.",
"They are retried automatically and will be synced when the error is resolved.": "Chúng sẽ được tự động thử lại và đồng bộ khi lỗi được khắc phục.",
"This Device": "Thiết bị này",
"This can easily give hackers access to read and change any files on your computer.": "Th.tác này có thể khiến tin tặc dễ dàng tr.cập để đọc và th.đổi bất kỳ t.tin nào trên máy của bạn.",
"This is a major version upgrade.": "Đây là bản nâng cấp quan trọng.",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"Time": "Time",
"Trash Can File Versioning": "Kiểu thùng rác",
"Type": "Type",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unknown": "Không biết",
"Unshared": "Chưa chia sẻ",
"Unused": "Chưa sử dụng",
"Up to Date": "Đã đồng bộ",
"Updated": "Đã cập nhật",
"Upgrade": "Nâng cấp",
"Upgrade To {%version%}": "Nâng cấp lên {{version}}",
"Upgrading": "Đang nâng cấp",
"Upload Rate": "Tốc độ tải lên",
"Uptime": "Th.gian h.động",
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
"Use HTTPS for GUI": "Sử dụng HTTPS cho GUI",
"Version": "Phiên bản",
"Versions Path": "Đ.dẫn đến các ph.bản",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Các phiên bản sẽ tự động được xoá nếu chúng cũ hơn thời hạn tối đa hoặc vượt quá số tập tin cho phép trong một khoảng thời gian.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a parent directory of an existing folder \"{{otherFolder}}\".",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Warning, this path is a parent directory of an existing folder \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Cảnh báo, đường dẫn này là thư mục con của thư mục hiện hữu \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Warning, this path is a subdirectory of an existing folder \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Khi thêm một thiết bị mới, hãy nhớ rằng thiết bị này cũng phải được thêm vào máy kia.",
"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.": "Khi thêm một thư mục mới, hãy nhớ rằng ID thư mục được dùng để gắn kết thư mục giữa các thiết bị với nhau. Chúng phải chính xác từng chữ, cả viết hoa và thường giữa tất cả thiết bị.",
"Yes": "Phải",
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
"You can change your choice at any time in the Settings dialog.": "You can change your choice at any time in the Settings dialog.",
"You can read more about the two release channels at the link below.": "You can read more about the two release channels at the link below.",
"You must keep at least one version.": "Bạn phải giữ ít nhất một phiên bản.",
"days": "ngày",
"directories": "directories",
"files": "files",
"full documentation": "tài liệu đầy đủ",
"items": "nội dung",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} muốn chia sẻ thư mục \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
}

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "匿名使用报告",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名使用情况的报告格式已经变更。是否要迁移到新的格式?",
"Any devices configured on an introducer device will be added to this device as well.": "在中介设备上添加的任何“远程设备”,也会被自动添加到本机的“远程设备”列表。",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自动升级现提供了稳定版本和发布候选版之间的选择。",
"Are you sure you want to remove device {%name%}?": "您确定要移除设备 {{name}} 吗?",
"Are you sure you want to remove folder {%label%}?": "您确定要移除文件夹 {{label}} 吗?",
"Auto Accept": "自动接受",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自动升级现在提供了稳定版本和发布候选版之间的选择。",
"Automatic upgrades": "自动升级",
"Automatically create or share folders that this device advertises at the default path.": "自动地创建或共享这个设备在默认路径通告的文件夹。",
"Be careful!": "小心!",
"Bugs": "问题回报",
"CPU Utilization": "CPU使用率",
@@ -41,18 +45,21 @@
"Configured": "已配置",
"Connection Error": "连接出错",
"Connection Type": "连接类型",
"Connections": "连接",
"Copied from elsewhere": "从其他设备复制",
"Copied from original": "从源复制",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 以下贡献者:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 以下贡献者:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "正在创建忽略列表,覆盖位于 {{path}} 的已有文件。",
"Danger!": "危险!",
"Default Folder Path": "默认文件夹路径",
"Deleted": "已删除",
"Device": "设备",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "设备 \"{{name}}\"(位于 {{address}} 的 {{device}})请求连接。是否添加新设备?",
"Device ID": "设备 ID",
"Device Identification": "设备标识",
"Device Name": "设备名",
"Device that last modified the item": "最近修改该项的设备",
"Devices": "设备",
"Disabled": "已禁用",
"Disconnected": "连接已断开",
@@ -64,8 +71,8 @@
"Downloaded": "已下载",
"Downloading": "下载中",
"Edit": "选项",
"Edit Device": "修改设备选项",
"Edit Folder": "修改文件夹选项",
"Edit Device": "编辑设备",
"Edit Folder": "编辑文件夹",
"Editing": "正在编辑",
"Editing {%path%}.": "正在编辑 {{path}}。",
"Enable NAT traversal": "启用 NAT 遍历",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI 监听地址",
"GUI Listen Addresses": "图形管理界面监听地址",
"GUI Theme": "GUI 主题",
"General": "常规",
"Generate": "生成",
"Global Changes": "全局变更",
"Global Discovery": "全球发现",
@@ -123,6 +131,7 @@
"Latest Change": "最后更改",
"Learn more": "了解更多",
"Listeners": "侦听程序",
"Loading data...": "正在载入数据…",
"Local Discovery": "本地发现",
"Local State": "本地状态",
"Local State (Total)": "本地状态汇总",
@@ -131,6 +140,8 @@
"Maximum Age": "最长保留时间",
"Metadata Only": "仅元数据",
"Minimum Free Disk Space": "最低可用磁盘空间",
"Mod. Device": "修改设备",
"Mod. Time": "修改时间",
"Move to top of queue": "移动到队列顶端",
"Multi level wildcard (matches multiple directory levels)": "多级通配符(用以匹配多层文件夹)",
"Never": "从未",
@@ -139,6 +150,7 @@
"Newest First": "新文件优先",
"No": "否",
"No File Versioning": "不启用版本控制",
"No files will be deleted as a result of this operation.": "此操作结果不会删除任何文件。",
"No upgrades": "无更新",
"Normal": "普通",
"Notice": "提示",
@@ -153,6 +165,7 @@
"Override Changes": "撤销改变",
"Path": "路径",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "文件夹在本地的路径。如果不存在,则会被创建。波浪线符号(~)是如下路径的缩略符:",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "用于创建自动接受新文件夹的路径,同时也是通过 UI 添加新文件夹时的默认值。波浪号 (~) 扩展成 {{tilde}}。",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "历史版本储存路径(留空则会默认存储在共享文件夹中的 .stversions 目录)。",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "历史版本储存路径(留空则将默认会存储在 .stversions 文件夹中)。",
"Pause": "暂停",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "发布候选版包含最新的特性和修复。它们跟传统的 Syncthing 双周发布版类似。",
"Remote Devices": "远程设备",
"Remove": "移除",
"Remove Device": "移除设备",
"Remove Folder": "移除文件夹",
"Required identifier for the folder. Must be the same on all cluster devices.": "需要给文件夹设置标识。在所有丛设备上必须一致。",
"Rescan": "重新扫描",
"Rescan All": "全部重新扫描",
@@ -210,6 +225,7 @@
"Shutdown Complete": "关闭完成",
"Simple File Versioning": "简易版本控制",
"Single level wildcard (matches within a directory only)": "单级通配符(仅匹配单层文件夹)",
"Size": "大小",
"Smallest First": "小文件优先",
"Source Code": "源代码",
"Stable releases and release candidates": "稳定版本和发布候选版",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "这是一个重大版本更新。",
"This setting controls the free space required on the home (i.e., index database) disk.": "此设置控制主(例如索引数据库)磁盘上需要的可用空间。",
"Time": "时间",
"Time the item was last modified": "该项最近修改的时间",
"Trash Can File Versioning": "回收站式版本控制",
"Type": "类型",
"Unavailable": "无效",
"Unavailable/Disabled by administrator or maintainer": "无效/禁用(由管理员或维护者)",
"Undecided (will prompt)": "待定(将提示)",
"Unknown": "未知",
"Unshared": "未共享",

View File

@@ -26,8 +26,12 @@
"Anonymous Usage Reporting": "匿名的使用資訊回報",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名的使用資訊回報格式已經改變,你想要移至新格式嗎?",
"Any devices configured on an introducer device will be added to this device as well.": "任何在引入者裝置所設置的裝置將會一併新增至此裝置",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自動更新目前有穩定發行版及發行候選版可供選擇。",
"Automatic upgrades": "自動升級",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Be careful!": "請小心!",
"Bugs": "程式錯誤",
"CPU Utilization": "CPU 使用",
@@ -41,18 +45,21 @@
"Configured": "已設定",
"Connection Error": "連線錯誤",
"Connection Type": "連接類型",
"Connections": "Connections",
"Copied from elsewhere": "從別處複製",
"Copied from original": "從原處複製",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 下列貢獻者:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 下列貢獻者:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "建立忽略樣式,覆蓋已存在的 {{path}}。",
"Danger!": "危險!",
"Default Folder Path": "Default Folder Path",
"Deleted": "已刪除",
"Device": "裝置",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "裝置 \"{{name}}\" ({{device}} 位於 {{address}}) 想要連線。 要增加新裝置嗎?",
"Device ID": "裝置識別碼",
"Device Identification": "裝置識別",
"Device Name": "裝置名稱",
"Device that last modified the item": "Device that last modified the item",
"Devices": "裝置",
"Disabled": "關閉",
"Disconnected": "斷線",
@@ -99,6 +106,7 @@
"GUI Listen Address": "GUI 監聽位址",
"GUI Listen Addresses": "GUI 監聽位址",
"GUI Theme": "主題",
"General": "General",
"Generate": "產生",
"Global Changes": "全域變動",
"Global Discovery": "全域探索",
@@ -123,6 +131,7 @@
"Latest Change": "最近變動",
"Learn more": "瞭解更多",
"Listeners": "監聽者",
"Loading data...": "Loading data...",
"Local Discovery": "本機探索",
"Local State": "本機狀態",
"Local State (Total)": "本機狀態 (總結)",
@@ -131,6 +140,8 @@
"Maximum Age": "最長保留時間",
"Metadata Only": "僅中繼資料",
"Minimum Free Disk Space": "最少閒置磁碟空間",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Move to top of queue": "移到隊列頂端",
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
"Never": "從未",
@@ -139,6 +150,7 @@
"Newest First": "最新的優先",
"No": "否",
"No File Versioning": "無檔案版本控制",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "不更新",
"Normal": "正常的",
"Notice": "注意",
@@ -153,6 +165,7 @@
"Override Changes": "置換改變",
"Path": "路徑",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "資料夾在本機的路徑。若資料夾不存在則會建立。波浪符號 (~) 可用作下列資料夾的捷徑:",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "儲存歷史版本的路徑(若為空,則預設使用資料夾中的 .stversions 資料夾)。",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "儲存歷史版本的路徑 (若為空,則預設使用資料夾中的 .stversions 資料夾)。",
"Pause": "暫停",
@@ -174,6 +187,8 @@
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "發行候選版包含最新的功能及修補。與傳統 Syncthing 雙週發行版相似。",
"Remote Devices": "遠端裝置",
"Remove": "移除",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "資料夾的識別字。必須在叢集內所有的裝置上皆相同。",
"Rescan": "重新掃描",
"Rescan All": "全部重新掃描",
@@ -210,6 +225,7 @@
"Shutdown Complete": "關閉完成",
"Simple File Versioning": "簡單檔案版本控制",
"Single level wildcard (matches within a directory only)": "單階層萬用字元 (只在單個資料夾階層內比對)",
"Size": "Size",
"Smallest First": "最小的優先",
"Source Code": "原始碼",
"Stable releases and release candidates": "穩定發行版及發行候選版",
@@ -257,8 +273,11 @@
"This is a major version upgrade.": "這是一個主要版本更新。",
"This setting controls the free space required on the home (i.e., index database) disk.": "此設定控制家目錄(即:索引資料庫)的必須可用空間。",
"Time": "時間",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "垃圾筒式檔案版本控制",
"Type": "類型",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "未定的(將會提示)",
"Unknown": "未知",
"Unshared": "未共享",

View File

@@ -1 +1 @@
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}

View File

@@ -1 +1 @@
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","eo","es","es-ES","eu","fi","fr","fr-CA","fy","hu","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","vi","zh-CN","zh-TW"]
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","eo","es","es-ES","eu","fi","fr","fr-CA","fy","hu","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","zh-CN","zh-TW"]

View File

@@ -78,6 +78,7 @@
<li><a href="" data-toggle="modal" data-target="#about"><span class="fa fa-fw fa-heart-o"></span>&nbsp;<span translate>About</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="advanced()"><span class="fa fa-fw fa-cogs"></span>&nbsp;<span translate>Advanced</span></a></li>
<li><a href="" ng-click="logging.show()"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Logs</span></a></li>
</ul>
</li>
</ul>
@@ -402,7 +403,7 @@
<th><span class="fa fa-fw fa-share-alt"></span>&nbsp;<span translate>Shared With</span></th>
<td class="text-right" ng-attr-title="{{sharesFolder(folder)}}">{{sharesFolder(folder)}}</td>
</tr>
<tr>
<tr ng-if="folderStats[folder.id].lastScan">
<th><span class="fa fa-fw fa-clock-o"></span>&nbsp;<span translate>Last Scan</span></th>
<td translate ng-if="folderStats[folder.id].lastScanDays >= 365" class="text-right">Never</td>
<td ng-if="folderStats[folder.id].lastScanDays < 365" class="text-right">
@@ -603,6 +604,12 @@
</a>
</td>
</tr>
<tr ng-if="deviceStatus(deviceCfg) == 'syncing'">
<th><span class="fa fa-fw fa-exchange"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right">
<a href="" ng-click="showRemoteNeed(deviceCfg)">{{completion[deviceCfg.deviceID]._needItems | alwaysNumber}} <span translate>items</span>, ~{{completion[deviceCfg.deviceID]._needBytes | binary}}B</a>
</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-link"></span>&nbsp<span translate>Address</span></th>
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
@@ -722,9 +729,13 @@
<ng-include src="'syncthing/usagereport/usageReportPreviewModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/neededFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/failedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/remoteNeededFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/core/majorUpgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/aboutModalView.html'"></ng-include>
<ng-include src="'syncthing/core/discoveryFailuresModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/removeFolderDialogView.html'"></ng-include>
<ng-include src="'syncthing/device/removeDeviceDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/logViewerModalView.html'"></ng-include>
<!-- vendor scripts -->
<script type="text/javascript" src="vendor/jquery/jquery-2.2.2.js"></script>

View File

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

View File

@@ -0,0 +1,36 @@
<modal id="logViewer" icon="book" status="default" heading="{{'Logs' | translate}}" large="yes" closeable="yes">
<div class="modal-body">
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#log-viewer-log" translate>Log</a></li>
<li><a data-toggle="tab" href="#log-viewer-facilities" translate>Debugging Facilities</a></li>
</ul>
<div class="tab-content">
<div id="log-viewer-log" class="tab-pane in active">
<label translate ng-if="logging.logEntries.length == 0">Loading...</label>
<textarea ng-focus="logging.paused = true" ng-blur="logging.paused = false" id="logViewerText" class="form-control" rows="20" ng-if="logging.logEntries.length != 0" readonly style="font-family: Consolas; font-size: 11px; overflow: auto;">{{ logging.content() }}</textarea>
<p translate class="help-block" ng-style="{'visibility': logging.paused ? 'visible' : 'hidden'}">Log tailing paused. Click here to continue.</p>
</div>
<div id="log-viewer-facilities" class="tab-pane">
<label>Available debug logging facilities:</label>
<table class="table table-condensed table-striped">
<tbody>
<tr ng-repeat="(name, data) in logging.facilities">
<td>
<input type="checkbox" ng-model="data.enabled" ng-change="logging.onFacilityChange(name)" ng-disabled="data.enabled == null"> <span>{{ name }}</span>
</td>
<td>{{ data.description }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</modal>

View File

@@ -2,7 +2,7 @@ angular.module('syncthing.core')
.config(function($locationProvider) {
$locationProvider.html5Mode({enabled: true, requireBase: false}).hashPrefix('!');
})
.controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q) {
.controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q, $interval) {
'use strict';
// private/helper definitions
@@ -45,7 +45,6 @@ angular.module('syncthing.core')
$scope.progress = {};
$scope.version = {};
$scope.needed = [];
$scope.neededTotal = 0;
$scope.neededCurrentPage = 1;
$scope.neededPageSize = 10;
$scope.failed = {};
@@ -56,6 +55,7 @@ angular.module('syncthing.core')
$scope.globalChangeEvents = {};
$scope.metricRates = false;
$scope.folderPathErrors = {};
resetRemoteNeed();
try {
$scope.metricRates = (window.localStorage["metricRates"] == "true");
@@ -241,7 +241,8 @@ angular.module('syncthing.core')
};
$scope.completion[arg.data.id] = {
_total: 100,
_needBytes: 0
_needBytes: 0,
_needItems: 0
};
}
});
@@ -389,7 +390,8 @@ angular.module('syncthing.core')
$scope.devices.forEach(function (deviceCfg) {
$scope.completion[deviceCfg.deviceID] = {
_total: 100,
_needBytes: 0
_needBytes: 0,
_needItems: 0
};
});
$scope.devices.sort(deviceCompare);
@@ -431,7 +433,7 @@ angular.module('syncthing.core')
}
}
$scope.listenersFailed = listenersFailed;
$scope.listenersTotal = Object.keys(data.connectionServiceStatus).length;
$scope.listenersTotal = $scope.sizeOf(data.connectionServiceStatus);
$scope.discoveryTotal = data.discoveryMethods;
var discoveryFailed = [];
@@ -476,21 +478,24 @@ angular.module('syncthing.core')
}
function recalcCompletion(device) {
var total = 0, needed = 0, deletes = 0;
var total = 0, needed = 0, deletes = 0, items = 0;
for (var folder in $scope.completion[device]) {
if (folder === "_total" || folder === '_needBytes') {
if (folder === "_total" || folder === '_needBytes' || folder === '_needItems') {
continue;
}
total += $scope.completion[device][folder].globalBytes;
needed += $scope.completion[device][folder].needBytes;
items += $scope.completion[device][folder].needItems;
deletes += $scope.completion[device][folder].needDeletes;
}
if (total == 0) {
$scope.completion[device]._total = 100;
$scope.completion[device]._needBytes = 0;
$scope.completion[device]._needItems = 0;
} else {
$scope.completion[device]._total = Math.floor(100 * (1 - needed / total));
$scope.completion[device]._needBytes = needed
$scope.completion[device]._needItems = items;
}
if (needed == 0 && deletes > 0) {
@@ -498,7 +503,6 @@ angular.module('syncthing.core')
// to do. Drop down the completion percentage to indicate
// that we have stuff to do.
$scope.completion[device]._total = 95;
$scope.completion[device]._needBytes = 0;
}
console.log("recalcCompletion", device, $scope.completion[device]);
@@ -616,7 +620,6 @@ angular.module('syncthing.core')
merged.push(item);
});
$scope.needed = merged;
$scope.neededTotal = data.total;
}
function pathJoin(base, name) {
@@ -638,6 +641,12 @@ angular.module('syncthing.core')
return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
}
function resetRemoteNeed() {
$scope.remoteNeed = {};
$scope.remoteNeedFolders = [];
$scope.remoteNeedDevice = undefined;
}
$scope.neededPageChanged = function (page) {
$scope.neededCurrentPage = page;
refreshNeed($scope.neededFolder);
@@ -656,6 +665,20 @@ angular.module('syncthing.core')
$scope.failedPageSize = perpage;
};
$scope.refreshRemoteNeed = function (folder, page, perpage) {
var url = urlbase + '/db/remoteneed?device=' + $scope.remoteNeedDevice.deviceID;
url += '&folder=' + encodeURIComponent(folder);
url += "&page=" + page + "&perpage=" + perpage;
$http.get(url).success(function (data) {
if ($scope.remoteNeedDevice !== '') {
$scope.remoteNeed[folder] = data;
}
}).error(function (err) {
$scope.remoteNeed[folder] = undefined;
$scope.emitHTTPError(err);
});
};
var refreshDeviceStats = debounce(function () {
$http.get(urlbase + "/stats/device").success(function (data) {
$scope.deviceStats = data;
@@ -965,7 +988,7 @@ angular.module('syncthing.core')
}
// enumerate notifications
if ($scope.openNoAuth || !$scope.configInSync || Object.keys($scope.deviceRejections).length > 0 || Object.keys($scope.folderRejections).length > 0 || $scope.errorList().length > 0 || !online) {
if ($scope.openNoAuth || !$scope.configInSync || $scope.sizeOf($scope.deviceRejections) > 0 || $scope.sizeOf($scope.folderRejections) > 0 || $scope.errorList().length > 0 || !online) {
notifyCount++;
}
@@ -1067,6 +1090,76 @@ angular.module('syncthing.core')
$('#discovery-failures').modal();
};
$scope.logging = {
facilities: {},
refreshFacilities: function() {
$http.get(urlbase + '/system/debug').success(function (data) {
var facilities = {};
data.enabled = data.enabled || [];
$.each(data.facilities, function(key, value) {
facilities[key] = {
description: value,
enabled: data.enabled.indexOf(key) > -1
}
})
$scope.logging.facilities = facilities;
}).error($scope.emitHTTPError);
},
show: function() {
$scope.logging.refreshFacilities();
$scope.logging.timer = $interval($scope.logging.fetch, 0, 1);
$('#logViewer').modal().on('hidden.bs.modal', function () {
$interval.cancel($scope.logging.timer);
$scope.logging.timer = null;
$scope.logging.entries = [];
});
},
onFacilityChange: function(facility) {
var enabled = $scope.logging.facilities[facility].enabled;
// Disable checkboxes while we're in flight.
$.each($scope.logging.facilities, function(key) {
$scope.logging.facilities[key].enabled = null;
})
$http.post(urlbase + '/system/debug?' + (enabled ? 'enable=':'disable=') + facility)
.success($scope.logging.refreshFacilities)
.error($scope.emitHTTPError);
},
timer: null,
entries: [],
paused: false,
content: function() {
var content = "";
$.each($scope.logging.entries, function (idx, entry) {
content += entry.when.split('.')[0].replace('T', ' ') + ' ' + entry.message + "\n";
});
return content;
},
fetch: function() {
var textArea = $('#logViewerText');
if (textArea.is(":focus")) {
if (!$scope.logging.timer) return;
$scope.logging.timer = $interval($scope.logging.fetch, 500, 1);
return;
}
var last = null;
if ($scope.logging.entries.length > 0) {
last = $scope.logging.entries[$scope.logging.entries.length-1].when;
}
$http.get(urlbase + '/system/log' + (last ? '?since=' + encodeURIComponent(last) : '')).success(function (data) {
if (!$scope.logging.timer) return;
$scope.logging.timer = $interval($scope.logging.fetch, 2000, 1);
if (!textArea.is(":focus")) {
if (data.messages) {
$scope.logging.entries.push.apply($scope.logging.entries, data.messages);
}
textArea.scrollTop(textArea[0].scrollHeight);
}
});
}
}
$scope.editSettings = function () {
// Make a working copy
$scope.tmpOptions = angular.copy($scope.config.options);
@@ -1623,17 +1716,14 @@ angular.module('syncthing.core')
$scope.deviceFolders = function (deviceCfg) {
var folders = [];
for (var folderID in $scope.folders) {
var devices = $scope.folders[folderID].devices;
for (var i = 0; i < devices.length; i++) {
if (devices[i].deviceID === deviceCfg.deviceID) {
folders.push(folderID);
$scope.folderList().forEach(function (folder) {
for (var i = 0; i < folder.devices.length; i++) {
if (folder.devices[i].deviceID === deviceCfg.deviceID) {
folders.push(folder.id);
break;
}
}
}
folders.sort(folderCompare);
});
return folders;
};
@@ -1729,11 +1819,25 @@ angular.module('syncthing.core')
$('#needed').modal().on('hidden.bs.modal', function () {
$scope.neededFolder = undefined;
$scope.needed = undefined;
$scope.neededTotal = 0;
$scope.neededCurrentPage = 1;
});
};
$scope.showRemoteNeed = function (device) {
resetRemoteNeed();
$scope.remoteNeedDevice = device;
$scope.deviceFolders(device).forEach(function(folder) {
if ($scope.completion[device.deviceID][folder].needItems === 0) {
return;
}
$scope.remoteNeedFolders.push(folder);
$scope.refreshRemoteNeed(folder, 1, 10);
});
$('#remoteNeed').modal().on('hidden.bs.modal', function () {
resetRemoteNeed();
});
};
$scope.showFailed = function (folder) {
$scope.failedCurrent = $scope.failed[folder];
$scope.failedFolderPath = $scope.folders[folder].path;
@@ -1900,12 +2004,16 @@ angular.module('syncthing.core')
// pseudo main. called on all definitions assigned
initController();
}
}
};
$scope.toggleUnits = function () {
$scope.metricRates = !$scope.metricRates;
try {
window.localStorage["metricRates"] = $scope.metricRates;
} catch (exception) { }
}
};
$scope.sizeOf = function (dict) {
return Object.keys(dict).length;
};
});

View File

@@ -24,31 +24,59 @@
</div>
<div ng-if="editingExisting" class="well well-sm text-monospace" select-on-click>{{currentDevice.deviceID}}</div>
</div>
<div class="form-group">
<label translate for="name">Device Name</label>
<input id="name" class="form-control" type="text" ng-model="currentDevice.name" />
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label translate for="name">Device Name</label>
<input id="name" class="form-control" type="text" ng-model="currentDevice.name" />
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input>
<p translate class="help-block">Enter comma separated ("tcp://ip:port", "tcp://host:port") addresses or "dynamic" to perform automatic discovery of the address.</p>
</div>
</div>
</div>
<div class="form-group">
<label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input>
<p translate class="help-block">Enter comma separated ("tcp://ip:port", "tcp://host:port") addresses or "dynamic" to perform automatic discovery of the address.</p>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label translate>Compression</label>
<select class="form-control" ng-model="currentDevice.compression">
<option value="always" translate>All Data</option>
<option value="metadata" translate>Metadata Only</option>
<option value="never" translate>Off</option>
</select>
</div>
</div>
<div class="col-md-6">
</div>
</div>
<div class="form-group">
<label translate>Compression</label>
<select class="form-control" ng-model="currentDevice.compression">
<option value="always" translate>All Data</option>
<option value="metadata" translate>Metadata Only</option>
<option value="never" translate>Off</option>
</select>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.introducer"> <span translate>Introducer</span>
</label>
<p translate class="help-block">Add devices from the introducer to our device list, for mutually shared folders.</p>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.introducer">
<span translate>Introducer</span>
<p translate class="help-block">Add devices from the introducer to our device list, for mutually shared folders.</p>
</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.autoAcceptFolders">
<span translate>Auto Accept</span>
<p translate class="help-block">Automatically create or share folders that this device advertises at the default path.</p>
</label>
</div>
</div>
</div>
</div>
<div class="row">
@@ -87,7 +115,7 @@
<button type="button" class="btn btn-warning btn-sm disabled" ng-if="willBeReintroducedBy" tooltip data-original-title="This device will be reintroduced by {{ willBeReintroducedBy }}">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
<button type="button" class="btn btn-warning btn-sm" ng-click="deleteDevice()" ng-if="!willBeReintroducedBy">
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation" ng-if="!willBeReintroducedBy">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
</div>

View File

@@ -0,0 +1,15 @@
<modal id="remove-device-confirmation" status="warning" icon="exclamation-circle" heading="{{'Remove Device' | translate}}" large="no" closeable="yes">
<div class="modal-body">
<p ng-model="currentDevice.name" style=" overflow : hidden; text-overflow: ellipsis; white-space: nowrap;">
<span translate translate-value-name="{{currentDevice.name}}">Are you sure you want to remove device {%name%}?</span>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning pull-left btn-sm" data-dismiss="modal" ng-click="deleteDevice()">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Yes</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>No</span>
</button>
</div>
</modal>

View File

@@ -199,7 +199,7 @@
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>Close</span>
</button>
<button type="button" class="btn btn-warning pull-left btn-sm" ng-click="deleteFolder(currentFolder.id)" ng-if="editingExisting">
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
</div>

View File

@@ -0,0 +1,18 @@
<modal id="remove-folder-confirmation" status="warning" icon="exclamation-circle" heading="{{'Remove Folder' | translate}}" large="no" closeable="yes">
<div class="modal-body">
<p ng-model="currentFolder.label" style=" overflow : hidden; text-overflow: ellipsis; white-space: nowrap;">
<span translate translate-value-label="{{currentFolder.label}}">Are you sure you want to remove folder {%label%}?</span>
</p>
<p translate>
No files will be deleted as a result of this operation.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning pull-left btn-sm" data-dismiss="modal" ng-click="deleteFolder(currentFolder.id)">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Yes</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>No</span>
</button>
</div>
</modal>

View File

@@ -1,36 +1,181 @@
<modal id="settings" status="default" icon="cog" heading="{{'Settings' | translate}}" large="yes" closeable="yes">
<div class="modal-body">
<form role="form" name="settingsEditor">
<div class="row">
<div class="col-md-6">
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#settings-general" translate>General</a></li>
<li><a data-toggle="tab" href="#settings-gui" translate>GUI</a></li>
<li><a data-toggle="tab" href="#settings-connections" translate>Connections</a></li>
</ul>
<div class="tab-content">
<div id="settings-general" class="tab-pane in active">
<div class="form-group">
<label translate for="DeviceName">Device Name</label>
<input id="DeviceName" class="form-control" type="text" ng-model="tmpOptions.deviceName"/>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-horizontal">
<div class="form-group" ng-class="{'has-error': settingsEditor.minHomeDiskFree.$invalid && settingsEditor.minHomeDiskFree.$dirty}">
<label class="col-xs-12" for="minHomeDiskFree"><span translate>Minimum Free Disk Space</span></label><br/>
<div class="col-xs-9"><input name="minHomeDiskFree" id="minHomeDiskFree" class="form-control" type="number" ng-model="tmpOptions.minHomeDiskFree.value" required="" aria-required="true" min="0" step="0.01"/></div>
<div class="col-xs-3"><select class="col-sm-3 form-control" ng-model="tmpOptions.minHomeDiskFree.unit">
<option value="%">%</option>
<option value="kB">kB</option>
<option value="MB">MB</option>
<option value="GB">GB</option>
<option value="TB">TB</option>
</select></div>
<p class="col-xs-12 help-block">
<span translate ng-show="settingsEditor.minHomeDiskFree.$invalid">Enter a non-negative number (e.g., "2.35") and select a unit. Percentages are as part of the total disk size.</span>
<span translate ng-hide="settingsEditor.minHomeDiskFree.$invalid">This setting controls the free space required on the home (i.e., index database) disk.</span>
</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate>API Key</label>
<div class="input-group">
<input type="text" readonly class="text-monospace form-control" value="{{tmpGUI.apiKey || '-'}}"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default btn-secondary" ng-click="setAPIKey(tmpGUI)">
<span class="fa fa-repeat"></span>&nbsp;<span translate>Generate</span>
</button>
</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div ng-if="tmpOptions.upgrades != 'candidate'">
<label translate for="urVersion">Anonymous Usage Reporting</label> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>)
<select class="form-control" id="urVersion" ng-model="tmpOptions._urAcceptedStr">
<option ng-repeat="n in urVersions()" value="{{n}}">{{'Version' | translate}} {{n}}</option>
<!-- 1 does not exist, as we did not support incremental formats back then. -->
<option value="0" translate>Undecided (will prompt)</option>
<option value="-1" translate>Disabled</option>
</select>
</div>
<p class="help-block" ng-if="tmpOptions.upgrades == 'candidate'">
<span translate>Usage reporting is always enabled for candidate releases.</span> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>)
</p>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate>Automatic upgrades</label>&emsp;<a href="https://docs.syncthing.net/users/releases.html" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="tmpOptions.upgrades" ng-if="upgradeInfo">
<option value="none" translate>No upgrades</option>
<option value="stable" translate>Stable releases only</option>
<option value="candidate" translate>Stable releases and release candidates</option>
</select>
<p class="help-block" ng-if="!upgradeInfo">
<span translate>Unavailable/Disabled by administrator or maintainer</span>
</p>
</div>
</div>
</div>
<div class="form-group">
<label translate for="urVersion">Default Folder Path</label>
<input id="DefaultFolderPath" class="form-control" type="text" ng-model="tmpOptions.defaultFolderPath"/>
<p class="help-block">
<span translate translate-value-tilde="{{system.tilde}}">
Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.
</span>
</p>
</div>
</div>
<div id="settings-gui" class="tab-pane">
<div class="form-group" ng-class="{'has-error': settingsEditor.Address.$invalid && settingsEditor.Address.$dirty}">
<label translate for="Address">GUI Listen Address</label>&emsp;<a href="https://docs.syncthing.net/users/guilisten.html" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<input id="Address" name="Address" class="form-control" type="text" ng-model="tmpGUI.address" ng-pattern="/.*:0*((102[4-9])|(10[3-9][0-9])|(1[1-9][0-9][0-9])|([2-9][0-9][0-9][0-9])|([1-6]\d{4}))$/"/>
<p class="help-block" ng-show="settingsEditor.Address.$invalid" translate>
Enter a non-privileged port number (1024 - 65535).
</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label translate for="User">GUI Authentication User</label>
<input id="User" class="form-control" type="text" ng-model="tmpGUI.user"/>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate for="Password">GUI Authentication Password</label>
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.password" ng-trim="false"/>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input id="UseTLS" type="checkbox" ng-model="tmpGUI.useTLS"/> <span translate>Use HTTPS for GUI</span>
</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input id="StartBrowser" type="checkbox" ng-model="tmpOptions.startBrowser"/> <span translate>Start Browser</span>
</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label translate>GUI Theme</label>
<select class="form-control" ng-model="tmpGUI.theme" ng-if="themes.length > 1">
<option ng-repeat="theme in themes.sort()" value="{{ theme }}">
{{ themeName(theme) }}
</option>
</select>
<p class="help-block" ng-if="themes.length < 2">
<span translate>Unavailable</span>
</p>
</div>
</div>
<div class="col-md-6">
</div>
</div>
</div>
<div id="settings-connections" class="tab-pane">
<div class="form-group">
<label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>&emsp;<a href="https://docs.syncthing.net/users/config.html#listen-addresses" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<input id="ListenAddressesStr" class="form-control" type="text" ng-model="tmpOptions._listenAddressesStr"/>
</div>
<div class="form-group" ng-class="{'has-error': settingsEditor.MaxRecvKbps.$invalid && settingsEditor.MaxRecvKbps.$dirty}">
<label translate for="MaxRecvKbps">Incoming Rate Limit (KiB/s)</label>
<input id="MaxRecvKbps" name="MaxRecvKbps" class="form-control" type="number" ng-model="tmpOptions.maxRecvKbps" min="0"/>
<p class="help-block">
<span translate ng-if="settingsEditor.MaxRecvKbps.$error.min && settingsEditor.MaxRecvKbps.$dirty">The rate limit must be a non-negative number (0: no limit)</span>
</p>
<div class="row">
<div class="col-md-6">
<div class="form-group" ng-class="{'has-error': settingsEditor.MaxRecvKbps.$invalid && settingsEditor.MaxRecvKbps.$dirty}">
<label translate for="MaxRecvKbps">Incoming Rate Limit (KiB/s)</label>
<input id="MaxRecvKbps" name="MaxRecvKbps" class="form-control" type="number" ng-model="tmpOptions.maxRecvKbps" min="0"/>
<p class="help-block">
<span translate ng-if="settingsEditor.MaxRecvKbps.$error.min && settingsEditor.MaxRecvKbps.$dirty">The rate limit must be a non-negative number (0: no limit)</span>
</p>
</div>
</div>
<div class="col-md-6">
<div class="form-group" ng-class="{'has-error': settingsEditor.MaxSendKbps.$invalid && settingsEditor.MaxSendKbps.$dirty}">
<label translate for="MaxSendKbps">Outgoing Rate Limit (KiB/s)</label>
<input id="MaxSendKbps" name="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.maxSendKbps" min="0"/>
<p class="help-block">
<span translate ng-if="settingsEditor.MaxSendKbps.$error.min && settingsEditor.MaxSendKbps.$dirty">The rate limit must be a non-negative number (0: no limit)</span>
</p>
</div>
</div>
</div>
<div class="form-group" ng-class="{'has-error': settingsEditor.MaxSendKbps.$invalid && settingsEditor.MaxSendKbps.$dirty}">
<label translate for="MaxSendKbps">Outgoing Rate Limit (KiB/s)</label>
<input id="MaxSendKbps" name="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.maxSendKbps" min="0"/>
<p class="help-block">
<span translate ng-if="settingsEditor.MaxSendKbps.$error.min && settingsEditor.MaxSendKbps.$dirty">The rate limit must be a non-negative number (0: no limit)</span>
</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
@@ -51,7 +196,6 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
@@ -72,107 +216,18 @@
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="form-group">
<label translate for="GlobalAnnServersStr">Global Discovery Servers</label>
<input ng-disabled="!tmpOptions.globalAnnounceEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions._globalAnnounceServersStr"/>
</div>
<div class="form-horizontal">
<div class="form-group" ng-class="{'has-error': settingsEditor.minHomeDiskFree.$invalid && settingsEditor.minHomeDiskFree.$dirty}">
<label class="col-xs-12" for="minHomeDiskFree"><span translate>Minimum Free Disk Space</span></label><br/>
<div class="col-xs-9"><input name="minHomeDiskFree" id="minHomeDiskFree" class="form-control" type="number" ng-model="tmpOptions.minHomeDiskFree.value" required="" aria-required="true" min="0" step="0.01"/></div>
<div class="col-xs-3"><select class="col-sm-3 form-control" ng-model="tmpOptions.minHomeDiskFree.unit">
<option value="%">%</option>
<option value="kB">kB</option>
<option value="MB">MB</option>
<option value="GB">GB</option>
<option value="TB">TB</option>
</select></div>
<p class="col-xs-12 help-block">
<span translate ng-show="settingsEditor.minHomeDiskFree.$invalid">Enter a non-negative number (e.g., "2.35") and select a unit. Percentages are as part of the total disk size.</span>
<span translate ng-hide="settingsEditor.minHomeDiskFree.$invalid">This setting controls the free space required on the home (i.e., index database) disk.</span>
</p>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label translate for="GlobalAnnServersStr">Global Discovery Servers</label>
<input ng-disabled="!tmpOptions.globalAnnounceEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions._globalAnnounceServersStr"/>
</div>
</div>
<div class="col-md-6">
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group" ng-class="{'has-error': settingsEditor.Address.$invalid && settingsEditor.Address.$dirty}">
<label translate for="Address">GUI Listen Address</label>&emsp;<a href="https://docs.syncthing.net/users/guilisten.html" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<input id="Address" name="Address" class="form-control" type="text" ng-model="tmpGUI.address" ng-pattern="/.*:0*((102[4-9])|(10[3-9][0-9])|(1[1-9][0-9][0-9])|([2-9][0-9][0-9][0-9])|([1-6]\d{4}))$/"/>
<p class="help-block" ng-show="settingsEditor.Address.$invalid" translate>
Enter a non-privileged port number (1024 - 65535).
</p>
</div>
<div class="form-group">
<label translate for="User">GUI Authentication User</label>
<input id="User" class="form-control" type="text" ng-model="tmpGUI.user"/>
</div>
<div class="form-group">
<label translate for="Password">GUI Authentication Password</label>
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.password" ng-trim="false"/>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="UseTLS" type="checkbox" ng-model="tmpGUI.useTLS"/> <span translate>Use HTTPS for GUI</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="StartBrowser" type="checkbox" ng-model="tmpOptions.startBrowser"/> <span translate>Start Browser</span>
</label>
</div>
</div>
<div class="form-group" ng-if="upgradeInfo">
<label translate>Automatic upgrades</label>&emsp;<a href="https://docs.syncthing.net/users/releases.html" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="tmpOptions.upgrades">
<option value="none" translate>No upgrades</option>
<option value="stable" translate>Stable releases only</option>
<option value="candidate" translate>Stable releases and release candidates</option>
</select>
</div>
<div class="form-group">
<div ng-if="tmpOptions.upgrades != 'candidate'">
<label translate for="urVersion">Anonymous Usage Reporting</label> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>)
<select class="form-control" id="urVersion" ng-model="tmpOptions._urAcceptedStr">
<option ng-repeat="n in urVersions()" value="{{n}}">{{'Version' | translate}} {{n}}</option>
<!-- 1 does not exist, as we did not support incremental formats back then. -->
<option value="0" translate>Undecided (will prompt)</option>
<option value="-1" translate>Disabled</option>
</select>
</div>
<p class="help-block" ng-if="tmpOptions.upgrades == 'candidate'">
<span translate>Usage reporting is always enabled for candidate releases.</span> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>)
</p>
</div>
<hr />
<div class="form-group">
<label translate>API Key</label>
<div class="well well-sm text-monospace" select-on-click>{{tmpGUI.apiKey || "-"}}</div>
<button type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(tmpGUI)">
<span class="fa fa-repeat"></span>&nbsp;<span translate>Generate</span>
</button>
</div>
<div class="form-group" ng-if="themes.length > 1">
<label translate>GUI Theme</label>
<select class="form-control" ng-model="tmpGUI.theme">
<option ng-repeat="theme in themes.sort()" value="{{ theme }}">
{{ themeName(theme) }}
</option>
</select>
</div>
</div>
</div>
</form>
</div>

View File

@@ -14,7 +14,7 @@
<table class="table table-striped table-condensed">
<tr dir-paginate="f in needed | itemsPerPage: neededPageSize" current-page="neededCurrentPage" total-items="neededTotal" pagination-id="needed">
<tr dir-paginate="f in needed | itemsPerPage: neededPageSize" current-page="neededCurrentPage" total-items="neededItems(neededFolder)" pagination-id="needed">
<!-- Icon -->
<td class="small-data col-xs-2">

View File

@@ -0,0 +1,45 @@
é<modal id="remoteNeed" status="info" icon="exchange" heading="{{'Out of Sync Items' | translate}} - {{deviceName(remoteNeedDevice)}}" large="yes" closeable="yes">
<div class="modal-body">
<div ng-if="sizeOf(remoteNeed) == 0">
<span translate>Loading data...</span>
</div>
<div ng-if="sizeOf(remoteNeed) > 0">
<div class="panel panel-default" ng-repeat="folder in remoteNeedFolders" ng-if="remoteNeed[folder] && remoteNeed[folder].files.length > 0">
<button class="btn panel-heading" data-toggle="collapse" data-target="#remoteNeed-{{folder}}" aria-expanded="false">
<h4 class="panel-title">
<span>{{folderLabel(folder)}}</span>
</h4>
</button>
<div id="remoteNeed-{{folder}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table table-striped table-dynamic">
<thead>
<tr>
<th translate>Path</th>
<th translate>Size</th>
<th><span tooltip data-original-title="{{'Time the item was last modified' | translate}}" translate>Mod. Time</span></th>
<th><span tooltip data-original-title="{{'Device that last modified the item' | translate}}" translate>Mod. Device</span></th>
</tr>
</thead>
<tr dir-paginate="file in remoteNeed[folder].files | itemsPerPage: remoteNeed[folder].perpage" current-page="remoteNeed[folder].page" total-items="completion[remoteNeedDevice.deviceID][folder].needItems" pagination-id="'remoteNeed-' + folder">
<td>{{file.name}}</td>
<td><span ng-hide="file.type == 'DIRECTORY'">{{file.size | binary}}B</span></td>
<td>{{file.modified | date:"yyyy-MM-dd HH:mm:ss"}}</td>
<td ng-if="file.modifiedBy">{{friendlyNameFromShort(file.modifiedBy)}}</td>
<td ng-if="!file.modifiedBy"><span translate>Unknown</span></td>
</tr>
</table>
<dir-pagination-controls on-page-change="refreshRemoteNeed(folder, newPageNumber, remoteNeed[folder].perpage)" pagination-id="'remoteNeed-' + folder"></dir-pagination-controls>
<ul class="pagination pull-right">
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: remoteNeed[folder].perpage == option }">
<a href="#" ng-click="refreshRemoteNeed(folder, remoteNeed[folder].page, option)">{{option}}</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div>
</modal>

View File

@@ -52,7 +52,7 @@ func TestReplaceCommit(t *testing.T) {
// Replace config. We should get back a clean response and the config
// should change.
err := w.Replace(Configuration{Version: 1})
_, err := w.Replace(Configuration{Version: 1})
if err != nil {
t.Fatal("Should not have a validation error:", err)
}
@@ -69,7 +69,7 @@ func TestReplaceCommit(t *testing.T) {
sub0 := requiresRestart{committed: make(chan struct{}, 1)}
w.Subscribe(sub0)
err = w.Replace(Configuration{Version: 2})
_, err = w.Replace(Configuration{Version: 2})
if err != nil {
t.Fatal("Should not have a validation error:", err)
}
@@ -87,7 +87,7 @@ func TestReplaceCommit(t *testing.T) {
w.Subscribe(validationError{})
err = w.Replace(Configuration{Version: 3})
_, err = w.Replace(Configuration{Version: 3})
if err == nil {
t.Fatal("Should have a validation error")
}

View File

@@ -10,6 +10,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@@ -20,6 +21,7 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -430,6 +432,62 @@ func TestFolderPath(t *testing.T) {
}
}
func TestFolderCheckPath(t *testing.T) {
n, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
err = os.MkdirAll(filepath.Join(n, "dir", ".stfolder"), os.FileMode(0777))
if err != nil {
t.Fatal(err)
}
testcases := []struct {
path string
err error
}{
{
path: "",
err: errMarkerMissing,
},
{
path: "does not exist",
err: errPathMissing,
},
{
path: "dir",
err: nil,
},
}
err = osutil.DebugSymlinkForTestsOnly(filepath.Join(n, "dir"), filepath.Join(n, "link"))
if err == nil {
t.Log("running with symlink check")
testcases = append(testcases, struct {
path string
err error
}{
path: "link",
err: nil,
})
} else if runtime.GOOS != "windows" {
t.Log("running without symlink check")
t.Fatal(err)
}
for _, testcase := range testcases {
cfg := FolderConfiguration{
Path: filepath.Join(n, testcase.path),
MarkerName: DefaultMarkerName,
}
if err := cfg.CheckPath(); testcase.err != err {
t.Errorf("unexpected error in case %s: %s != %s", testcase.path, err, testcase.err)
}
}
}
func TestNewSaveLoad(t *testing.T) {
path := "testdata/temp.xml"
os.Remove(path)
@@ -768,7 +826,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
t.Error("Should have less devices")
}
err = wrapper.Replace(raw)
_, err = wrapper.Replace(raw)
if err != nil {
t.Errorf("Failed: %s", err)
}

View File

@@ -19,6 +19,7 @@ type DeviceConfiguration struct {
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
Paused bool `xml:"paused" json:"paused"`
AllowedNetworks []string `xml:"allowedNetwork,omitempty" json:"allowedNetworks"`
AutoAcceptFolders bool `xml:"autoAcceptFolders" json:"autoAcceptFolders"`
}
func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {

View File

@@ -13,18 +13,20 @@ import (
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/util"
)
var (
errPathMissing = errors.New("folder path missing")
errMarkerMissing = errors.New("folder marker missing")
errPathNotDirectory = errors.New("folder path not a directory")
errPathMissing = errors.New("folder path missing")
errMarkerMissing = errors.New("folder marker missing")
)
const DefaultMarkerName = ".stfolder"
type FolderConfiguration struct {
ID string `xml:"id,attr" json:"id"`
Label string `xml:"label,attr" json:"label"`
Label string `xml:"label,attr" json:"label" restart:"false"`
FilesystemType fs.FilesystemType `xml:"filesystemType" json:"filesystemType"`
Path string `xml:"path,attr" json:"path"`
Type FolderType `xml:"type,attr" json:"type"`
@@ -61,11 +63,18 @@ type FolderDeviceConfiguration struct {
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
}
func NewFolderConfiguration(id string, fsType fs.FilesystemType, path string) FolderConfiguration {
func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration {
f := FolderConfiguration{
ID: id,
FilesystemType: fsType,
Path: path,
ID: id,
Label: label,
RescanIntervalS: 60,
FSWatcherDelayS: 10,
MinDiskFree: Size{Value: 1, Unit: "%"},
Devices: []FolderDeviceConfiguration{{DeviceID: myID}},
AutoNormalize: true,
MaxConflicts: -1,
FilesystemType: fsType,
Path: path,
}
f.prepare()
return f
@@ -124,12 +133,28 @@ func (f *FolderConfiguration) CreateMarker() error {
// CheckPath returns nil if the folder root exists and contains the marker file
func (f *FolderConfiguration) CheckPath() error {
fi, err := f.Filesystem().Stat(".")
if err != nil || !fi.IsDir() {
if err != nil {
if !fs.IsNotExist(err) {
return err
}
return errPathMissing
}
// Users might have the root directory as a symlink or reparse point.
// Furthermore, OneDrive bullcrap uses a magic reparse point to the cloudz...
// Yet it's impossible for this to happen, as filesystem adds a trailing
// path separator to the root, so even if you point the filesystem at a file
// Stat ends up calling stat on C:\dir\file\ which, fails with "is not a directory"
// in the error check above, and we don't even get to here.
if !fi.IsDir() && !fi.IsSymlink() {
return errPathNotDirectory
}
_, err = f.Filesystem().Stat(f.MarkerName)
if err != nil {
if !fs.IsNotExist(err) {
return err
}
return errMarkerMissing
}
@@ -201,6 +226,25 @@ func (f *FolderConfiguration) prepare() {
}
}
// RequiresRestartOnly returns a copy with only the attributes that require
// restart on change.
func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
copy := f
// Manual handling for things that are not taken care of by the tag
// copier, yet should not cause a restart.
copy.cachedFilesystem = nil
blank := FolderConfiguration{}
util.CopyMatchingTag(&blank, &copy, "restart", func(v string) bool {
if len(v) > 0 && v != "false" {
panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "false"`, v))
}
return v == "false"
})
return copy
}
type FolderDeviceConfigurationList []FolderDeviceConfiguration
func (l FolderDeviceConfigurationList) Less(a, b int) bool {

View File

@@ -10,6 +10,8 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"github.com/syncthing/syncthing/lib/util"
)
type WeakHashSelectionMethod int
@@ -96,11 +98,11 @@ func (WeakHashSelectionMethod) ParseDefault(value string) (interface{}, error) {
type OptionsConfiguration struct {
ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default" restart:"true"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" restart:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" restart:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" restart:"true"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027" restart:"true"`
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
@@ -117,21 +119,21 @@ type OptionsConfiguration struct {
URURL string `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
URPostInsecurely bool `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases"` // when auto upgrades are enabled
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false"`
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" restart:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" restart:"true"` // 0 for off
UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases" restart:"true"` // when auto upgrades are enabled
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false" restart:"true"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
MinHomeDiskFree Size `xml:"minHomeDiskFree" json:"minHomeDiskFree" default:"1 %"`
ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json" restart:"true"`
AlwaysLocalNets []string `xml:"alwaysLocalNet" json:"alwaysLocalNets"`
OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod"`
WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod" restart:"true"`
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
StunKeepaliveS int `xml:"stunKeepaliveSeconds" json:"stunKeepaliveSeconds" default:"24"`
KCPNoDelay bool `xml:"kcpNoDelay" json:"kcpNoDelay" default:"false"`
@@ -162,3 +164,17 @@ func (orig OptionsConfiguration) Copy() OptionsConfiguration {
copy(c.UnackedNotificationIDs, orig.UnackedNotificationIDs)
return c
}
// RequiresRestartOnly returns a copy with only the attributes that require
// restart on change.
func (orig OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration {
copy := orig
blank := OptionsConfiguration{}
util.CopyMatchingTag(&blank, &copy, "restart", func(v string) bool {
if len(v) > 0 && v != "true" {
panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
}
return v != "true"
})
return copy
}

View File

@@ -45,6 +45,15 @@ type Committer interface {
String() string
}
// Waiter allows to wait for the given config operation to complete.
type Waiter interface {
Wait()
}
type noopWaiter struct{}
func (noopWaiter) Wait() {}
// A wrapper around a Configuration that manages loads, saves and published
// notifications of changes to registered Handlers
@@ -130,37 +139,25 @@ func (w *Wrapper) RawCopy() Configuration {
return w.cfg.Copy()
}
// ReplaceBlocking swaps the current configuration object for the given one,
// and waits for subscribers to be notified.
func (w *Wrapper) ReplaceBlocking(cfg Configuration) error {
w.mut.Lock()
wg := sync.NewWaitGroup()
err := w.replaceLocked(cfg, wg)
w.mut.Unlock()
wg.Wait()
return err
}
// Replace swaps the current configuration object for the given one.
func (w *Wrapper) Replace(cfg Configuration) error {
func (w *Wrapper) Replace(cfg Configuration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
return w.replaceLocked(cfg, nil)
return w.replaceLocked(cfg)
}
func (w *Wrapper) replaceLocked(to Configuration, wg sync.WaitGroup) error {
func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
from := w.cfg
if err := to.clean(); err != nil {
return err
return noopWaiter{}, err
}
for _, sub := range w.subs {
l.Debugln(sub, "verifying configuration")
if err := sub.VerifyConfiguration(from, to); err != nil {
l.Debugln(sub, "rejected config:", err)
return err
return noopWaiter{}, err
}
}
@@ -168,23 +165,19 @@ func (w *Wrapper) replaceLocked(to Configuration, wg sync.WaitGroup) error {
w.deviceMap = nil
w.folderMap = nil
w.notifyListeners(from, to, wg)
return nil
return w.notifyListeners(from, to), nil
}
func (w *Wrapper) notifyListeners(from, to Configuration, wg sync.WaitGroup) {
if wg != nil {
wg.Add(len(w.subs))
}
func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
wg := sync.NewWaitGroup()
wg.Add(len(w.subs))
for _, sub := range w.subs {
go func(commiter Committer) {
w.notifyListener(commiter, from.Copy(), to.Copy())
if wg != nil {
wg.Done()
}
wg.Done()
}(sub)
}
return wg
}
func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
@@ -211,7 +204,7 @@ func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
// SetDevices adds new devices to the configuration, or overwrites existing
// devices with the same ID.
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) error {
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
@@ -231,17 +224,17 @@ func (w *Wrapper) SetDevices(devs []DeviceConfiguration) error {
}
}
return w.replaceLocked(newCfg, nil)
return w.replaceLocked(newCfg)
}
// SetDevice adds a new device to the configuration, or overwrites an existing
// device with the same ID.
func (w *Wrapper) SetDevice(dev DeviceConfiguration) error {
func (w *Wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
return w.SetDevices([]DeviceConfiguration{dev})
}
// RemoveDevice removes the device from the configuration
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) error {
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
@@ -255,10 +248,10 @@ func (w *Wrapper) RemoveDevice(id protocol.DeviceID) error {
}
}
if !removed {
return nil
return noopWaiter{}, nil
}
return w.replaceLocked(newCfg, nil)
return w.replaceLocked(newCfg)
}
// Folders returns a map of folders. Folder structures should not be changed,
@@ -277,7 +270,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
// SetFolder adds a new folder to the configuration, or overwrites an existing
// folder with the same ID.
func (w *Wrapper) SetFolder(fld FolderConfiguration) error {
func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
@@ -294,7 +287,7 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) error {
newCfg.Folders = append(w.cfg.Folders, fld)
}
return w.replaceLocked(newCfg, nil)
return w.replaceLocked(newCfg)
}
// Options returns the current options configuration object.
@@ -305,12 +298,12 @@ func (w *Wrapper) Options() OptionsConfiguration {
}
// SetOptions replaces the current options configuration object.
func (w *Wrapper) SetOptions(opts OptionsConfiguration) error {
func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
newCfg.Options = opts
return w.replaceLocked(newCfg, nil)
return w.replaceLocked(newCfg)
}
// GUI returns the current GUI configuration object.
@@ -321,12 +314,12 @@ func (w *Wrapper) GUI() GUIConfiguration {
}
// SetGUI replaces the current GUI configuration object.
func (w *Wrapper) SetGUI(gui GUIConfiguration) error {
func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
newCfg.GUI = gui
return w.replaceLocked(newCfg, nil)
return w.replaceLocked(newCfg)
}
// IgnoredDevice returns whether or not connection attempts from the given

View File

@@ -11,9 +11,9 @@ import (
"net/url"
"time"
"github.com/AudriusButkevicius/kcp-go"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/xtaci/kcp-go"
"github.com/xtaci/smux"
)
@@ -44,7 +44,6 @@ func (d *kcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, erro
conn, err = kcp.DialWithOptions(uri.Host, nil, 0, 0)
}
if err != nil {
conn.Close()
return internalConn{}, err
}

View File

@@ -15,9 +15,9 @@ import (
"sync/atomic"
"time"
"github.com/AudriusButkevicius/kcp-go"
"github.com/AudriusButkevicius/pfilter"
"github.com/ccding/go-stun/stun"
"github.com/xtaci/kcp-go"
"github.com/xtaci/smux"
"github.com/syncthing/syncthing/lib/config"

View File

@@ -15,8 +15,8 @@ import (
"sync/atomic"
"time"
"github.com/AudriusButkevicius/kcp-go"
"github.com/AudriusButkevicius/pfilter"
"github.com/xtaci/kcp-go"
"github.com/xtaci/smux"
)

View File

@@ -14,6 +14,7 @@ import (
"net/url"
"sort"
"strings"
stdsync "sync"
"time"
"github.com/syncthing/syncthing/lib/config"
@@ -758,7 +759,7 @@ func dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (interna
for _, prio := range priorities {
tgts := dialTargetBuckets[prio]
res := make(chan internalConn, len(tgts))
wg := sync.NewWaitGroup()
wg := stdsync.WaitGroup{}
for _, tgt := range tgts {
wg.Add(1)
go func(tgt dialTarget) {

View File

@@ -25,6 +25,7 @@ const (
KeyTypeFolderIdx
KeyTypeDeviceIdx
KeyTypeIndexID
KeyTypeFolderMeta
)
func (l VersionList) String() string {
@@ -36,7 +37,7 @@ func (l VersionList) String() string {
b.WriteString(", ")
}
copy(id[:], v.Device)
fmt.Fprintf(&b, "{%d, %v}", v.Version, id)
fmt.Fprintf(&b, "{%v, %v}", v.Version, id)
}
b.WriteString("}")
return b.String()

View File

@@ -93,12 +93,11 @@ func (db *Instance) Location() string {
return db.location
}
func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, localSize, globalSize *sizeTracker) {
func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
var fk []byte
isLocalDevice := bytes.Equal(device, protocol.LocalDeviceID[:])
for _, f := range fs {
name := []byte(f.Name)
fk = db.deviceKeyInto(fk[:cap(fk)], folder, device, name)
@@ -116,15 +115,14 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, l
continue
}
if isLocalDevice {
if err == nil {
localSize.removeFile(ef)
}
localSize.addFile(f)
devID := protocol.DeviceIDFromBytes(device)
if err == nil {
meta.removeFile(devID, ef)
}
meta.addFile(devID, f)
t.insertFile(folder, device, f)
t.updateGlobal(folder, device, f, globalSize)
t.updateGlobal(folder, device, f, meta)
// Write out and reuse the batch every few records, to avoid the batch
// growing too large and thus allocating unnecessarily much memory.
@@ -404,7 +402,7 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, needAllInvali
break
}
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v invalid=%v haveV=%d globalV=%d globalDev=%v", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveFileVersion.Invalid, haveFileVersion.Version, needVersion, needDevice)
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v invalid=%v haveV=%v globalV=%v globalDev=%v", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveFileVersion.Invalid, haveFileVersion.Version, needVersion, needDevice)
if cont := fn(gf); !cont {
return
@@ -465,7 +463,7 @@ func (db *Instance) dropFolder(folder []byte) {
dbi.Release()
}
func (db *Instance) dropDeviceFolder(device, folder []byte, globalSize *sizeTracker) {
func (db *Instance) dropDeviceFolder(device, folder []byte, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
@@ -475,13 +473,13 @@ func (db *Instance) dropDeviceFolder(device, folder []byte, globalSize *sizeTrac
for dbi.Next() {
key := dbi.Key()
name := db.deviceKeyName(key)
t.removeFromGlobal(folder, device, name, globalSize)
t.removeFromGlobal(folder, device, name, meta)
t.Delete(key)
t.checkFlush()
}
}
func (db *Instance) checkGlobals(folder []byte, globalSize *sizeTracker) {
func (db *Instance) checkGlobals(folder []byte, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
@@ -520,7 +518,7 @@ func (db *Instance) checkGlobals(folder []byte, globalSize *sizeTracker) {
if i == 0 {
if fi, ok := t.getFile(folder, version.Device, name); ok {
globalSize.addFile(fi)
meta.addFile(globalDeviceID, fi)
}
}
}
@@ -589,7 +587,7 @@ func (db *Instance) AddInvalidToGlobal(folder, device []byte) int {
if file.Invalid {
changed++
l.Debugf("add invalid to global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
l.Debugf("add invalid to global; folder=%q device=%v file=%q version=%v", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
// this is an adapted version of readWriteTransaction.updateGlobal
name := []byte(file.Name)
@@ -760,6 +758,13 @@ func (db *Instance) mtimesKey(folder []byte) []byte {
return prefix
}
func (db *Instance) folderMetaKey(folder []byte) []byte {
prefix := make([]byte, 5) // key type + 4 bytes folder idx number
prefix[0] = KeyTypeFolderMeta
binary.BigEndian.PutUint32(prefix[1:], db.folderIdx.ID(folder))
return prefix
}
// DropDeltaIndexIDs removes all index IDs from the database. This will
// cause a full index transmission on the next connection.
func (db *Instance) DropDeltaIndexIDs() {
@@ -770,6 +775,10 @@ func (db *Instance) dropMtimes(folder []byte) {
db.dropPrefix(db.mtimesKey(folder))
}
func (db *Instance) dropFolderMeta(folder []byte) {
db.dropPrefix(db.folderMetaKey(folder))
}
func (db *Instance) dropPrefix(prefix []byte) {
t := db.newReadWriteTransaction()
defer t.close()

View File

@@ -85,8 +85,8 @@ func (t readWriteTransaction) insertFile(folder, device []byte, file protocol.Fi
// updateGlobal adds this device+version to the version list for the given
// file. If the device is already present in the list, the version is updated.
// If the file does not have an entry in the global list, it is created.
func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.FileInfo, globalSize *sizeTracker) bool {
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.FileInfo, meta *metadataTracker) bool {
l.Debugf("update global; folder=%q device=%v file=%q version=%v invalid=%v", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version, file.Invalid)
name := []byte(file.Name)
gk := t.db.globalKey(folder, name)
svl, _ := t.Get(gk, nil) // skip error, we check len(svl) != 0 later
@@ -106,7 +106,7 @@ func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.
if i == 0 {
// Keep the current newest file around so we can subtract it from
// the globalSize if we replace it.
// the metadata if we replace it.
oldFile, hasOldFile = t.getFile(folder, fl.Versions[0].Device, name)
}
@@ -169,16 +169,16 @@ insert:
// We just inserted a new newest version. Fixup the global size
// calculation.
if !file.Version.Equal(oldFile.Version) {
globalSize.addFile(file)
meta.addFile(globalDeviceID, file)
if hasOldFile {
// We have the old file that was removed at the head of the list.
globalSize.removeFile(oldFile)
meta.removeFile(globalDeviceID, oldFile)
} else if len(fl.Versions) > 1 {
// The previous newest version is now at index 1, grab it from there.
if oldFile, ok := t.getFile(folder, fl.Versions[1].Device, name); ok {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
globalSize.removeFile(oldFile)
meta.removeFile(globalDeviceID, oldFile)
}
}
}
@@ -193,7 +193,7 @@ insert:
// removeFromGlobal removes the device from the global version list for the
// given file. If the version list is empty after this, the file entry is
// removed entirely.
func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, globalSize *sizeTracker) {
func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, meta *metadataTracker) {
l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file)
gk := t.db.globalKey(folder, file)
@@ -214,13 +214,13 @@ func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, glob
removed := false
for i := range fl.Versions {
if bytes.Equal(fl.Versions[i].Device, device) {
if i == 0 && globalSize != nil {
if i == 0 && meta != nil {
f, ok := t.getFile(folder, device, file)
if !ok {
// didn't exist anyway, apparently
continue
}
globalSize.removeFile(f)
meta.removeFile(globalDeviceID, f)
removed = true
}
fl.Versions = append(fl.Versions[:i], fl.Versions[i+1:]...)
@@ -238,7 +238,7 @@ func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, glob
if f, ok := t.getFile(folder, fl.Versions[0].Device, file); ok {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
globalSize.addFile(f)
meta.addFile(globalDeviceID, f)
}
}
}

220
lib/db/meta.go Normal file
View File

@@ -0,0 +1,220 @@
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package db
import (
"time"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
// like protocol.LocalDeviceID but with 0xf8 in all positions
var globalDeviceID = protocol.DeviceID{0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8}
type metadataTracker struct {
mut sync.RWMutex
counts CountsSet
indexes map[protocol.DeviceID]int // device ID -> index in counts
}
func newMetadataTracker() *metadataTracker {
return &metadataTracker{
mut: sync.NewRWMutex(),
indexes: make(map[protocol.DeviceID]int),
}
}
// Unmarshal loads a metadataTracker from the corresponding protobuf
// representation
func (m *metadataTracker) Unmarshal(bs []byte) error {
if err := m.counts.Unmarshal(bs); err != nil {
return err
}
// Initialize the index map
for i, c := range m.counts.Counts {
m.indexes[protocol.DeviceIDFromBytes(c.DeviceID)] = i
}
return nil
}
// Unmarshal returns the protobuf representation of the metadataTracker
func (m *metadataTracker) Marshal() ([]byte, error) {
return m.counts.Marshal()
}
// toDB saves the marshalled metadataTracker to the given db, under the key
// corresponding to the given folder
func (m *metadataTracker) toDB(db *Instance, folder []byte) error {
key := db.folderMetaKey(folder)
bs, err := m.Marshal()
if err != nil {
return err
}
return db.Put(key, bs, nil)
}
// fromDB initializes the metadataTracker from the marshalled data found in
// the database under the key corresponding to the given folder
func (m *metadataTracker) fromDB(db *Instance, folder []byte) error {
key := db.folderMetaKey(folder)
bs, err := db.Get(key, nil)
if err != nil {
return err
}
return m.Unmarshal(bs)
}
// countsPtr returns a pointer to the corresponding Counts struct, if
// necessary allocating one in the process
func (m *metadataTracker) countsPtr(dev protocol.DeviceID) *Counts {
// must be called with the mutex held
idx, ok := m.indexes[dev]
if !ok {
idx = len(m.counts.Counts)
m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:]})
m.indexes[dev] = idx
}
return &m.counts.Counts[idx]
}
// addFile adds a file to the counts, adjusting the sequence number as
// appropriate
func (m *metadataTracker) addFile(dev protocol.DeviceID, f FileIntf) {
if f.IsInvalid() {
return
}
m.mut.Lock()
cp := m.countsPtr(dev)
switch {
case f.IsDeleted():
cp.Deleted++
case f.IsDirectory() && !f.IsSymlink():
cp.Directories++
case f.IsSymlink():
cp.Symlinks++
default:
cp.Files++
}
cp.Bytes += f.FileSize()
if seq := f.SequenceNo(); seq > cp.Sequence {
cp.Sequence = seq
}
m.mut.Unlock()
}
// removeFile removes a file from the counts
func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) {
if f.IsInvalid() {
return
}
m.mut.Lock()
cp := m.countsPtr(dev)
switch {
case f.IsDeleted():
cp.Deleted--
case f.IsDirectory() && !f.IsSymlink():
cp.Directories--
case f.IsSymlink():
cp.Symlinks--
default:
cp.Files--
}
cp.Bytes -= f.FileSize()
if cp.Deleted < 0 || cp.Files < 0 || cp.Directories < 0 || cp.Symlinks < 0 {
panic("bug: removed more than added")
}
m.mut.Unlock()
}
// resetAll resets all metadata for the given device
func (m *metadataTracker) resetAll(dev protocol.DeviceID) {
m.mut.Lock()
*m.countsPtr(dev) = Counts{DeviceID: dev[:]}
m.mut.Unlock()
}
// resetCounts resets the file, dir, etc. counters, while retaining the
// sequence number
func (m *metadataTracker) resetCounts(dev protocol.DeviceID) {
m.mut.Lock()
c := m.countsPtr(dev)
c.Bytes = 0
c.Deleted = 0
c.Directories = 0
c.Files = 0
c.Symlinks = 0
// c.Sequence deliberately untouched
m.mut.Unlock()
}
// Counts returns the counts for the given device ID
func (m *metadataTracker) Counts(dev protocol.DeviceID) Counts {
m.mut.RLock()
defer m.mut.RUnlock()
idx, ok := m.indexes[dev]
if !ok {
return Counts{}
}
return m.counts.Counts[idx]
}
// nextSeq allocates a new sequence number for the given device
func (m *metadataTracker) nextSeq(dev protocol.DeviceID) int64 {
m.mut.Lock()
defer m.mut.Unlock()
c := m.countsPtr(dev)
c.Sequence++
return c.Sequence
}
// devices returns the list of devices tracked, excluding the local device
// (which we don't know the ID of)
func (m *metadataTracker) devices() []protocol.DeviceID {
devs := make([]protocol.DeviceID, 0, len(m.counts.Counts))
m.mut.RLock()
for _, dev := range m.counts.Counts {
if dev.Sequence > 0 {
id := protocol.DeviceIDFromBytes(dev.DeviceID)
if id == globalDeviceID || id == protocol.LocalDeviceID {
continue
}
devs = append(devs, id)
}
}
m.mut.RUnlock()
return devs
}
func (m *metadataTracker) Created() time.Time {
m.mut.RLock()
defer m.mut.RUnlock()
return time.Unix(0, m.counts.Created)
}
func (m *metadataTracker) SetCreated() {
m.mut.Lock()
m.counts.Created = time.Now().UnixNano()
m.mut.Unlock()
}

View File

@@ -13,8 +13,8 @@
package db
import (
stdsync "sync"
"sync/atomic"
"os"
"time"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
@@ -23,16 +23,13 @@ import (
)
type FileSet struct {
sequence int64 // Our local sequence number
folder string
fs fs.Filesystem
db *Instance
blockmap *BlockMap
localSize sizeTracker
globalSize sizeTracker
folder string
fs fs.Filesystem
db *Instance
blockmap *BlockMap
meta *metadataTracker
remoteSequence map[protocol.DeviceID]int64 // Highest seen sequence numbers for other devices
updateMutex sync.Mutex // protects remoteSequence and database updates
updateMutex sync.Mutex // protects database updates and the corresponding metadata changes
}
// FileIntf is the set of methods implemented by both protocol.FileInfo and
@@ -45,6 +42,7 @@ type FileIntf interface {
IsDirectory() bool
IsSymlink() bool
HasPermissionBits() bool
SequenceNo() int64
}
// The Iterator is called with either a protocol.FileInfo or a
@@ -52,126 +50,76 @@ type FileIntf interface {
// continue iteration, false to stop.
type Iterator func(f FileIntf) bool
type Counts struct {
Files int
Directories int
Symlinks int
Deleted int
Bytes int64
}
var databaseRecheckInterval = 30 * 24 * time.Hour
type sizeTracker struct {
Counts
mut stdsync.Mutex
}
func (s *sizeTracker) addFile(f FileIntf) {
if f.IsInvalid() {
return
func init() {
if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
databaseRecheckInterval = dur
}
s.mut.Lock()
switch {
case f.IsDeleted():
s.Deleted++
case f.IsDirectory() && !f.IsSymlink():
s.Directories++
case f.IsSymlink():
s.Symlinks++
default:
s.Files++
}
s.Bytes += f.FileSize()
s.mut.Unlock()
}
func (s *sizeTracker) removeFile(f FileIntf) {
if f.IsInvalid() {
return
}
s.mut.Lock()
switch {
case f.IsDeleted():
s.Deleted--
case f.IsDirectory() && !f.IsSymlink():
s.Directories--
case f.IsSymlink():
s.Symlinks--
default:
s.Files--
}
s.Bytes -= f.FileSize()
if s.Deleted < 0 || s.Files < 0 || s.Directories < 0 || s.Symlinks < 0 {
panic("bug: removed more than added")
}
s.mut.Unlock()
}
func (s *sizeTracker) reset() {
s.mut.Lock()
defer s.mut.Unlock()
s.Counts = Counts{}
}
func (s *sizeTracker) Size() Counts {
s.mut.Lock()
defer s.mut.Unlock()
return s.Counts
}
func NewFileSet(folder string, fs fs.Filesystem, db *Instance) *FileSet {
var s = FileSet{
remoteSequence: make(map[protocol.DeviceID]int64),
folder: folder,
fs: fs,
db: db,
blockmap: NewBlockMap(db, db.folderIdx.ID([]byte(folder))),
updateMutex: sync.NewMutex(),
folder: folder,
fs: fs,
db: db,
blockmap: NewBlockMap(db, db.folderIdx.ID([]byte(folder))),
meta: newMetadataTracker(),
updateMutex: sync.NewMutex(),
}
s.db.checkGlobals([]byte(folder), &s.globalSize)
var deviceID protocol.DeviceID
s.db.withAllFolderTruncated([]byte(folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
if deviceID == protocol.LocalDeviceID {
if f.Sequence > s.sequence {
s.sequence = f.Sequence
}
s.localSize.addFile(f)
} else if f.Sequence > s.remoteSequence[deviceID] {
s.remoteSequence[deviceID] = f.Sequence
}
return true
})
l.Debugf("loaded sequence for %q: %#v", folder, s.sequence)
if err := s.meta.fromDB(db, []byte(folder)); err != nil {
l.Infof("No stored folder metadata for %q: recalculating", folder)
s.recalcCounts()
} else if age := time.Since(s.meta.Created()); age > databaseRecheckInterval {
l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age)
s.recalcCounts()
}
return &s
}
func (s *FileSet) recalcCounts() {
s.meta = newMetadataTracker()
s.db.checkGlobals([]byte(s.folder), s.meta)
var deviceID protocol.DeviceID
s.db.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
s.meta.addFile(deviceID, f)
return true
})
s.meta.SetCreated()
s.meta.toDB(s.db, []byte(s.folder))
}
func (s *FileSet) Drop(device protocol.DeviceID) {
l.Debugf("%s Drop(%v)", s.folder, device)
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.db.dropDeviceFolder(device[:], []byte(s.folder), &s.globalSize)
s.db.dropDeviceFolder(device[:], []byte(s.folder), s.meta)
if device == protocol.LocalDeviceID {
s.blockmap.Drop()
s.localSize.reset()
// We deliberately do not reset s.sequence here. Dropping all files
// for the local device ID only happens in testing - which expects
// the sequence to be retained, like an old Replace() of all files
// would do. However, if we ever did it "in production" we would
// anyway want to retain the sequence for delta indexes to be happy.
s.meta.resetCounts(device)
// We deliberately do not reset the sequence number here. Dropping
// all files for the local device ID only happens in testing - which
// expects the sequence to be retained, like an old Replace() of all
// files would do. However, if we ever did it "in production" we
// would anyway want to retain the sequence for delta indexes to be
// happy.
} else {
// Here, on the other hand, we want to make sure that any file
// announced from the remote is newer than our current sequence
// number.
s.remoteSequence[device] = 0
s.meta.resetAll(device)
}
s.meta.toDB(s.db, []byte(s.folder))
}
func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
@@ -181,12 +129,6 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.updateLocked(device, fs)
}
func (s *FileSet) updateLocked(device protocol.DeviceID, fs []protocol.FileInfo) {
// names must be normalized and the lock held
if device == protocol.LocalDeviceID {
discards := make([]protocol.FileInfo, 0, len(fs))
updates := make([]protocol.FileInfo, 0, len(fs))
@@ -200,7 +142,7 @@ func (s *FileSet) updateLocked(device protocol.DeviceID, fs []protocol.FileInfo)
continue
}
nf.Sequence = atomic.AddInt64(&s.sequence, 1)
nf.Sequence = s.meta.nextSeq(protocol.LocalDeviceID)
fs = append(fs, nf)
if ok {
@@ -210,10 +152,10 @@ func (s *FileSet) updateLocked(device protocol.DeviceID, fs []protocol.FileInfo)
}
s.blockmap.Discard(discards)
s.blockmap.Update(updates)
} else {
s.remoteSequence[device] = maxSequence(fs)
}
s.db.updateFiles([]byte(s.folder), device[:], fs, &s.localSize, &s.globalSize)
s.db.updateFiles([]byte(s.folder), device[:], fs, s.meta)
s.meta.toDB(s.db, []byte(s.folder))
}
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
@@ -298,21 +240,15 @@ func (s *FileSet) Availability(file string) []protocol.DeviceID {
}
func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
if device == protocol.LocalDeviceID {
return atomic.LoadInt64(&s.sequence)
}
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
return s.remoteSequence[device]
return s.meta.Counts(device).Sequence
}
func (s *FileSet) LocalSize() Counts {
return s.localSize.Size()
return s.meta.Counts(protocol.LocalDeviceID)
}
func (s *FileSet) GlobalSize() Counts {
return s.globalSize.Size()
return s.meta.Counts(globalDeviceID)
}
func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {
@@ -339,29 +275,7 @@ func (s *FileSet) MtimeFS() *fs.MtimeFS {
}
func (s *FileSet) ListDevices() []protocol.DeviceID {
s.updateMutex.Lock()
devices := make([]protocol.DeviceID, 0, len(s.remoteSequence))
for id, seq := range s.remoteSequence {
if seq > 0 {
devices = append(devices, id)
}
}
s.updateMutex.Unlock()
return devices
}
// maxSequence returns the highest of the Sequence numbers found in
// the given slice of FileInfos. This should really be the Sequence of
// the last item, but Syncthing v0.14.0 and other implementations may not
// implement update sorting....
func maxSequence(fs []protocol.FileInfo) int64 {
var max int64
for _, f := range fs {
if f.Sequence > max {
max = f.Sequence
}
}
return max
return s.meta.devices()
}
// DropFolder clears out all information related to the given folder from the
@@ -369,6 +283,7 @@ func maxSequence(fs []protocol.FileInfo) int64 {
func DropFolder(db *Instance, folder string) {
db.dropFolder([]byte(folder))
db.dropMtimes([]byte(folder))
db.dropFolderMeta([]byte(folder))
bm := &BlockMap{
db: db,
folder: db.folderIdx.ID([]byte(folder)),

View File

@@ -89,7 +89,7 @@ func (l fileList) String() string {
var b bytes.Buffer
b.WriteString("[]protocol.FileList{\n")
for _, f := range l {
fmt.Fprintf(&b, " %q: #%d, %d bytes, %d blocks, perms=%o\n", f.Name, f.Version, f.Size, len(f.Blocks), f.Permissions)
fmt.Fprintf(&b, " %q: #%v, %d bytes, %d blocks, perms=%o\n", f.Name, f.Version, f.Size, len(f.Blocks), f.Permissions)
}
b.WriteString("}")
return b.String()
@@ -169,7 +169,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Global incorrect;\n A: %v !=\n E: %v", g, expectedGlobal)
}
globalFiles, globalDirectories, globalDeleted, globalBytes := 0, 0, 0, int64(0)
globalFiles, globalDirectories, globalDeleted, globalBytes := int32(0), int32(0), int32(0), int64(0)
for _, f := range g {
if f.IsInvalid() {
continue
@@ -205,7 +205,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Have incorrect;\n A: %v !=\n E: %v", h, localTot)
}
haveFiles, haveDirectories, haveDeleted, haveBytes := 0, 0, 0, int64(0)
haveFiles, haveDirectories, haveDeleted, haveBytes := int32(0), int32(0), int32(0), int64(0)
for _, f := range h {
if f.IsInvalid() {
continue

View File

@@ -64,6 +64,10 @@ func (f FileInfoTruncated) ModTime() time.Time {
return time.Unix(f.ModifiedS, int64(f.ModifiedNs))
}
func (f FileInfoTruncated) SequenceNo() int64 {
return f.Sequence
}
func (f FileInfoTruncated) ConvertToInvalidFileInfo(invalidatedBy protocol.ShortID) protocol.FileInfo {
return protocol.FileInfo{
Name: f.Name,

View File

@@ -11,6 +11,8 @@
FileVersion
VersionList
FileInfoTruncated
Counts
CountsSet
*/
package db
@@ -75,10 +77,39 @@ func (m *FileInfoTruncated) Reset() { *m = FileInfoTruncated{
func (*FileInfoTruncated) ProtoMessage() {}
func (*FileInfoTruncated) Descriptor() ([]byte, []int) { return fileDescriptorStructs, []int{2} }
// For each folder and device we keep one of these to track the current
// counts and sequence. We also keep one for the global state of the folder.
type Counts struct {
Files int32 `protobuf:"varint,1,opt,name=files,proto3" json:"files,omitempty"`
Directories int32 `protobuf:"varint,2,opt,name=directories,proto3" json:"directories,omitempty"`
Symlinks int32 `protobuf:"varint,3,opt,name=symlinks,proto3" json:"symlinks,omitempty"`
Deleted int32 `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"`
Bytes int64 `protobuf:"varint,5,opt,name=bytes,proto3" json:"bytes,omitempty"`
Sequence int64 `protobuf:"varint,6,opt,name=sequence,proto3" json:"sequence,omitempty"`
DeviceID []byte `protobuf:"bytes,17,opt,name=deviceID,proto3" json:"deviceID,omitempty"`
}
func (m *Counts) Reset() { *m = Counts{} }
func (m *Counts) String() string { return proto.CompactTextString(m) }
func (*Counts) ProtoMessage() {}
func (*Counts) Descriptor() ([]byte, []int) { return fileDescriptorStructs, []int{3} }
type CountsSet struct {
Counts []Counts `protobuf:"bytes,1,rep,name=counts" json:"counts"`
Created int64 `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"`
}
func (m *CountsSet) Reset() { *m = CountsSet{} }
func (m *CountsSet) String() string { return proto.CompactTextString(m) }
func (*CountsSet) ProtoMessage() {}
func (*CountsSet) Descriptor() ([]byte, []int) { return fileDescriptorStructs, []int{4} }
func init() {
proto.RegisterType((*FileVersion)(nil), "db.FileVersion")
proto.RegisterType((*VersionList)(nil), "db.VersionList")
proto.RegisterType((*FileInfoTruncated)(nil), "db.FileInfoTruncated")
proto.RegisterType((*Counts)(nil), "db.Counts")
proto.RegisterType((*CountsSet)(nil), "db.CountsSet")
}
func (m *FileVersion) Marshal() (dAtA []byte, err error) {
size := m.ProtoSize()
@@ -257,6 +288,97 @@ func (m *FileInfoTruncated) MarshalTo(dAtA []byte) (int, error) {
return i, nil
}
func (m *Counts) Marshal() (dAtA []byte, err error) {
size := m.ProtoSize()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Counts) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.Files != 0 {
dAtA[i] = 0x8
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Files))
}
if m.Directories != 0 {
dAtA[i] = 0x10
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Directories))
}
if m.Symlinks != 0 {
dAtA[i] = 0x18
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Symlinks))
}
if m.Deleted != 0 {
dAtA[i] = 0x20
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Deleted))
}
if m.Bytes != 0 {
dAtA[i] = 0x28
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Bytes))
}
if m.Sequence != 0 {
dAtA[i] = 0x30
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Sequence))
}
if len(m.DeviceID) > 0 {
dAtA[i] = 0x8a
i++
dAtA[i] = 0x1
i++
i = encodeVarintStructs(dAtA, i, uint64(len(m.DeviceID)))
i += copy(dAtA[i:], m.DeviceID)
}
return i, nil
}
func (m *CountsSet) Marshal() (dAtA []byte, err error) {
size := m.ProtoSize()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CountsSet) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Counts) > 0 {
for _, msg := range m.Counts {
dAtA[i] = 0xa
i++
i = encodeVarintStructs(dAtA, i, uint64(msg.ProtoSize()))
n, err := msg.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
}
i += n
}
}
if m.Created != 0 {
dAtA[i] = 0x10
i++
i = encodeVarintStructs(dAtA, i, uint64(m.Created))
}
return i, nil
}
func encodeFixed64Structs(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
@@ -357,6 +479,49 @@ func (m *FileInfoTruncated) ProtoSize() (n int) {
return n
}
func (m *Counts) ProtoSize() (n int) {
var l int
_ = l
if m.Files != 0 {
n += 1 + sovStructs(uint64(m.Files))
}
if m.Directories != 0 {
n += 1 + sovStructs(uint64(m.Directories))
}
if m.Symlinks != 0 {
n += 1 + sovStructs(uint64(m.Symlinks))
}
if m.Deleted != 0 {
n += 1 + sovStructs(uint64(m.Deleted))
}
if m.Bytes != 0 {
n += 1 + sovStructs(uint64(m.Bytes))
}
if m.Sequence != 0 {
n += 1 + sovStructs(uint64(m.Sequence))
}
l = len(m.DeviceID)
if l > 0 {
n += 2 + l + sovStructs(uint64(l))
}
return n
}
func (m *CountsSet) ProtoSize() (n int) {
var l int
_ = l
if len(m.Counts) > 0 {
for _, e := range m.Counts {
l = e.ProtoSize()
n += 1 + l + sovStructs(uint64(l))
}
}
if m.Created != 0 {
n += 1 + sovStructs(uint64(m.Created))
}
return n
}
func sovStructs(x uint64) (n int) {
for {
n++
@@ -913,6 +1078,301 @@ func (m *FileInfoTruncated) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *Counts) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Counts: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Counts: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Files", wireType)
}
m.Files = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Files |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Directories", wireType)
}
m.Directories = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Directories |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Symlinks", wireType)
}
m.Symlinks = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Symlinks |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Deleted", wireType)
}
m.Deleted = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Deleted |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Bytes", wireType)
}
m.Bytes = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Bytes |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType)
}
m.Sequence = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Sequence |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 17:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field DeviceID", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + byteLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.DeviceID = append(m.DeviceID[:0], dAtA[iNdEx:postIndex]...)
if m.DeviceID == nil {
m.DeviceID = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipStructs(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthStructs
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *CountsSet) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CountsSet: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CountsSet: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Counts", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Counts = append(m.Counts, Counts{})
if err := m.Counts[len(m.Counts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Created", wireType)
}
m.Created = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Created |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipStructs(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthStructs
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipStructs(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
@@ -1021,36 +1481,43 @@ var (
func init() { proto.RegisterFile("structs.proto", fileDescriptorStructs) }
var fileDescriptorStructs = []byte{
// 487 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0xc1, 0x6a, 0xdb, 0x40,
0x10, 0xf5, 0xc6, 0x4a, 0x6c, 0xaf, 0xe2, 0xb4, 0x59, 0x4a, 0x58, 0x0c, 0x95, 0x85, 0xa1, 0x20,
0x0a, 0x95, 0x5b, 0x87, 0x5e, 0xda, 0x9b, 0x29, 0x81, 0x40, 0x29, 0x45, 0x09, 0x39, 0x15, 0x8c,
0x25, 0x8d, 0xe5, 0xa5, 0xd2, 0xae, 0xa2, 0x5d, 0x19, 0xd4, 0x2f, 0xe9, 0x31, 0x9f, 0xe3, 0x63,
0xcf, 0x3d, 0x84, 0xd6, 0xfd, 0x8e, 0x42, 0xd1, 0x4a, 0x56, 0xd4, 0x5b, 0x7b, 0x9b, 0x37, 0x7a,
0x6f, 0xdf, 0x9b, 0x19, 0xe1, 0xa1, 0x54, 0x59, 0x1e, 0x28, 0xe9, 0xa6, 0x99, 0x50, 0x82, 0x1c,
0x84, 0xfe, 0xe8, 0x45, 0xc4, 0xd4, 0x3a, 0xf7, 0xdd, 0x40, 0x24, 0xd3, 0x48, 0x44, 0x62, 0xaa,
0x3f, 0xf9, 0xf9, 0x4a, 0x23, 0x0d, 0x74, 0x55, 0x49, 0x46, 0xaf, 0x5b, 0x74, 0x59, 0xf0, 0x40,
0xad, 0x19, 0x8f, 0x5a, 0x55, 0xcc, 0xfc, 0xea, 0x85, 0x40, 0xc4, 0x53, 0x1f, 0xd2, 0x4a, 0x36,
0xb9, 0xc5, 0xe6, 0x05, 0x8b, 0xe1, 0x06, 0x32, 0xc9, 0x04, 0x27, 0x2f, 0x71, 0x6f, 0x53, 0x95,
0x14, 0xd9, 0xc8, 0x31, 0x67, 0x8f, 0xdd, 0xbd, 0xc8, 0xbd, 0x81, 0x40, 0x89, 0x6c, 0x6e, 0x6c,
0xef, 0xc7, 0x1d, 0x6f, 0x4f, 0x23, 0x67, 0xf8, 0x28, 0x84, 0x0d, 0x0b, 0x80, 0x1e, 0xd8, 0xc8,
0x39, 0xf6, 0x6a, 0x44, 0x28, 0xee, 0x31, 0xbe, 0x59, 0xc6, 0x2c, 0xa4, 0x5d, 0x1b, 0x39, 0x7d,
0x6f, 0x0f, 0x27, 0x17, 0xd8, 0xac, 0xed, 0xde, 0x33, 0xa9, 0xc8, 0x2b, 0xdc, 0xaf, 0xdf, 0x92,
0x14, 0xd9, 0x5d, 0xc7, 0x9c, 0x3d, 0x72, 0x43, 0xdf, 0x6d, 0xa5, 0xaa, 0x2d, 0x1b, 0xda, 0x1b,
0xe3, 0xeb, 0xdd, 0xb8, 0x33, 0xf9, 0xdd, 0xc5, 0xa7, 0x25, 0xeb, 0x92, 0xaf, 0xc4, 0x75, 0x96,
0xf3, 0x60, 0xa9, 0x20, 0x24, 0x04, 0x1b, 0x7c, 0x99, 0x80, 0x8e, 0x3f, 0xf0, 0x74, 0x4d, 0x9e,
0x63, 0x43, 0x15, 0x69, 0x95, 0xf0, 0x64, 0x76, 0xf6, 0x30, 0x52, 0x23, 0x2f, 0x52, 0xf0, 0x34,
0xa7, 0xd4, 0x4b, 0xf6, 0x05, 0x74, 0xe8, 0xae, 0xa7, 0x6b, 0x62, 0x63, 0x33, 0x85, 0x2c, 0x61,
0xb2, 0x4a, 0x69, 0xd8, 0xc8, 0x19, 0x7a, 0xed, 0x16, 0x79, 0x8a, 0x71, 0x22, 0x42, 0xb6, 0x62,
0x10, 0x2e, 0x24, 0x3d, 0xd4, 0xda, 0xc1, 0xbe, 0x73, 0x55, 0x2e, 0x23, 0x84, 0x18, 0x14, 0x84,
0xf4, 0xa8, 0x5a, 0x46, 0x0d, 0xdb, 0x6b, 0xea, 0xfd, 0xb5, 0x26, 0xf2, 0x0c, 0x9f, 0x70, 0xb1,
0x68, 0xfb, 0xf6, 0x35, 0x61, 0xc8, 0xc5, 0xc7, 0x96, 0x73, 0xeb, 0x62, 0x83, 0x7f, 0xbb, 0xd8,
0x08, 0xf7, 0x25, 0xdc, 0xe6, 0xc0, 0x03, 0xa0, 0x58, 0x27, 0x6d, 0x30, 0x19, 0x63, 0xb3, 0x99,
0x83, 0x4b, 0x6a, 0xda, 0xc8, 0x39, 0xf4, 0x9a, 0xd1, 0x3e, 0x48, 0xf2, 0xa9, 0x45, 0xf0, 0x0b,
0x7a, 0x6c, 0x23, 0xc7, 0x98, 0xbf, 0x2d, 0x0d, 0xbe, 0xdf, 0x8f, 0xcf, 0xff, 0xe3, 0x1f, 0x74,
0xaf, 0xd6, 0x22, 0x53, 0x97, 0xef, 0x1e, 0x5e, 0x9f, 0x17, 0xe5, 0xcc, 0xb2, 0x48, 0x62, 0xc6,
0x3f, 0x2f, 0xd4, 0x32, 0x8b, 0x40, 0xd1, 0x53, 0x7d, 0xc6, 0x61, 0xdd, 0xbd, 0xd6, 0xcd, 0xea,
0xfe, 0xf3, 0x27, 0xdb, 0x9f, 0x56, 0x67, 0xbb, 0xb3, 0xd0, 0xb7, 0x9d, 0x85, 0x7e, 0xec, 0xac,
0xce, 0xdd, 0x2f, 0x0b, 0xf9, 0x47, 0xda, 0xe0, 0xfc, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe,
0xcd, 0x11, 0xef, 0x52, 0x03, 0x00, 0x00,
// 598 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6a, 0xdb, 0x4c,
0x14, 0xb5, 0x62, 0xc9, 0xb1, 0x47, 0x71, 0xbe, 0x2f, 0x43, 0x08, 0xc2, 0x50, 0x5b, 0x18, 0x0a,
0xa2, 0x50, 0xa5, 0x4d, 0xe8, 0xa6, 0xdd, 0xb9, 0x21, 0x10, 0x28, 0x6d, 0x99, 0x84, 0xac, 0x0a,
0xc1, 0x92, 0xae, 0x9d, 0xa1, 0xf2, 0x8c, 0xa3, 0x19, 0x07, 0xd4, 0x27, 0xe9, 0x32, 0x0f, 0xd3,
0x45, 0x96, 0x5d, 0x77, 0x11, 0x5a, 0xf7, 0x39, 0x0a, 0x45, 0x77, 0x24, 0x45, 0xe9, 0xaa, 0xdd,
0xdd, 0x73, 0xff, 0xef, 0x39, 0x33, 0xa4, 0xaf, 0x74, 0xb6, 0x8a, 0xb5, 0x0a, 0x97, 0x99, 0xd4,
0x92, 0x6e, 0x24, 0xd1, 0xe0, 0xe9, 0x9c, 0xeb, 0xcb, 0x55, 0x14, 0xc6, 0x72, 0xb1, 0x3f, 0x97,
0x73, 0xb9, 0x8f, 0xa1, 0x68, 0x35, 0x43, 0x84, 0x00, 0x2d, 0x53, 0x32, 0x78, 0xd1, 0x48, 0x57,
0xb9, 0x88, 0xf5, 0x25, 0x17, 0xf3, 0x86, 0x95, 0xf2, 0xc8, 0x74, 0x88, 0x65, 0xba, 0x1f, 0xc1,
0xd2, 0x94, 0x8d, 0xaf, 0x88, 0x7b, 0xcc, 0x53, 0x38, 0x87, 0x4c, 0x71, 0x29, 0xe8, 0x33, 0xb2,
0x79, 0x6d, 0x4c, 0xcf, 0xf2, 0xad, 0xc0, 0x3d, 0xf8, 0x3f, 0xac, 0x8a, 0xc2, 0x73, 0x88, 0xb5,
0xcc, 0x26, 0xf6, 0xed, 0xdd, 0xa8, 0xc5, 0xaa, 0x34, 0xba, 0x47, 0x3a, 0x09, 0x5c, 0xf3, 0x18,
0xbc, 0x0d, 0xdf, 0x0a, 0xb6, 0x58, 0x89, 0xa8, 0x47, 0x36, 0xb9, 0xb8, 0x9e, 0xa6, 0x3c, 0xf1,
0xda, 0xbe, 0x15, 0x74, 0x59, 0x05, 0xc7, 0xc7, 0xc4, 0x2d, 0xc7, 0xbd, 0xe1, 0x4a, 0xd3, 0xe7,
0xa4, 0x5b, 0xf6, 0x52, 0x9e, 0xe5, 0xb7, 0x03, 0xf7, 0xe0, 0xbf, 0x30, 0x89, 0xc2, 0xc6, 0x56,
0xe5, 0xc8, 0x3a, 0xed, 0xa5, 0xfd, 0xf9, 0x66, 0xd4, 0x1a, 0xff, 0x6a, 0x93, 0x9d, 0x22, 0xeb,
0x44, 0xcc, 0xe4, 0x59, 0xb6, 0x12, 0xf1, 0x54, 0x43, 0x42, 0x29, 0xb1, 0xc5, 0x74, 0x01, 0xb8,
0x7e, 0x8f, 0xa1, 0x4d, 0x9f, 0x10, 0x5b, 0xe7, 0x4b, 0xb3, 0xe1, 0xf6, 0xc1, 0xde, 0xfd, 0x49,
0x75, 0x79, 0xbe, 0x04, 0x86, 0x39, 0x45, 0xbd, 0xe2, 0x9f, 0x00, 0x97, 0x6e, 0x33, 0xb4, 0xa9,
0x4f, 0xdc, 0x25, 0x64, 0x0b, 0xae, 0xcc, 0x96, 0xb6, 0x6f, 0x05, 0x7d, 0xd6, 0x74, 0xd1, 0x47,
0x84, 0x2c, 0x64, 0xc2, 0x67, 0x1c, 0x92, 0x0b, 0xe5, 0x39, 0x58, 0xdb, 0xab, 0x3c, 0xa7, 0x05,
0x19, 0x09, 0xa4, 0xa0, 0x21, 0xf1, 0x3a, 0x86, 0x8c, 0x12, 0x36, 0x69, 0xda, 0x7c, 0x40, 0x13,
0x7d, 0x4c, 0xb6, 0x85, 0xbc, 0x68, 0xce, 0xed, 0x62, 0x42, 0x5f, 0xc8, 0xf7, 0x8d, 0xc9, 0x0d,
0xc5, 0x7a, 0x7f, 0xa7, 0xd8, 0x80, 0x74, 0x15, 0x5c, 0xad, 0x40, 0xc4, 0xe0, 0x11, 0xdc, 0xb4,
0xc6, 0x74, 0x44, 0xdc, 0xfa, 0x0e, 0xa1, 0x3c, 0xd7, 0xb7, 0x02, 0x87, 0xd5, 0xa7, 0xbd, 0x55,
0xf4, 0x43, 0x23, 0x21, 0xca, 0xbd, 0x2d, 0xdf, 0x0a, 0xec, 0xc9, 0xab, 0x62, 0xc0, 0xb7, 0xbb,
0xd1, 0xe1, 0x3f, 0xbc, 0xc1, 0xf0, 0xf4, 0x52, 0x66, 0xfa, 0xe4, 0xe8, 0xbe, 0xfb, 0x24, 0x2f,
0x6e, 0x56, 0xf9, 0x22, 0xe5, 0xe2, 0xe3, 0x85, 0x9e, 0x66, 0x73, 0xd0, 0xde, 0x0e, 0xca, 0xd8,
0x2f, 0xbd, 0x67, 0xe8, 0x2c, 0xf5, 0xff, 0x62, 0x91, 0xce, 0x6b, 0xb9, 0x12, 0x5a, 0xd1, 0x5d,
0xe2, 0xcc, 0x78, 0x0a, 0x0a, 0x55, 0x77, 0x98, 0x01, 0x85, 0x6c, 0x09, 0xcf, 0x90, 0x03, 0x0e,
0x0a, 0xd5, 0x77, 0x58, 0xd3, 0x85, 0x54, 0x98, 0xce, 0x0a, 0x05, 0x77, 0x58, 0x8d, 0x9b, 0x9a,
0xd9, 0x18, 0xaa, 0x35, 0xdb, 0x25, 0x4e, 0x94, 0x6b, 0xa8, 0x74, 0x36, 0xe0, 0x01, 0xad, 0x9d,
0x3f, 0x68, 0x1d, 0x90, 0xae, 0xf9, 0x16, 0x27, 0x47, 0x78, 0xd1, 0x16, 0xab, 0xf1, 0xf8, 0x1d,
0xe9, 0x99, 0x2b, 0x4e, 0x41, 0xd3, 0x80, 0x74, 0x62, 0x04, 0xe5, 0x57, 0x20, 0xc5, 0x57, 0x30,
0xe1, 0x52, 0xc6, 0x32, 0x5e, 0xac, 0x17, 0x67, 0x50, 0x3c, 0x79, 0x3c, 0xac, 0xcd, 0x2a, 0x38,
0xd9, 0xbd, 0xfd, 0x31, 0x6c, 0xdd, 0xae, 0x87, 0xd6, 0xd7, 0xf5, 0xd0, 0xfa, 0xbe, 0x1e, 0xb6,
0x6e, 0x7e, 0x0e, 0xad, 0xa8, 0x83, 0xc4, 0x1f, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x86,
0x97, 0x21, 0x6a, 0x04, 0x00, 0x00,
}

View File

@@ -37,3 +37,20 @@ message FileInfoTruncated {
int64 sequence = 10;
string symlink_target = 17;
}
// For each folder and device we keep one of these to track the current
// counts and sequence. We also keep one for the global state of the folder.
message Counts {
int32 files = 1;
int32 directories = 2;
int32 symlinks = 3;
int32 deleted = 4;
int64 bytes = 5;
int64 sequence = 6; // zero for the global state
bytes deviceID = 17; // device ID for remote devices, or special values for local/global
}
message CountsSet {
repeated Counts counts = 1 [(gogoproto.nullable) = false];
int64 created = 2; // unix nanos
}

View File

@@ -21,8 +21,8 @@ import (
var (
l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
proxyDialer = getDialer(proxy.Direct)
usingProxy = proxyDialer != proxy.Direct
proxyDialer proxy.Dialer
usingProxy bool
noFallback = os.Getenv("ALL_PROXY_NO_FALLBACK") != ""
)
@@ -30,6 +30,11 @@ type dialFunc func(network, addr string) (net.Conn, error)
func init() {
l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
proxy.RegisterDialerType("socks", socksDialerFunction)
proxyDialer = getDialer(proxy.Direct)
usingProxy = proxyDialer != proxy.Direct
if usingProxy {
http.DefaultTransport = &http.Transport{
Dial: Dial,
@@ -78,6 +83,20 @@ func dialWithFallback(proxyDialFunc dialFunc, fallbackDialFunc dialFunc, network
return conn, err
}
// This is a rip off of proxy.FromURL for "socks" URL scheme
func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
var auth *proxy.Auth
if u.User != nil {
auth = new(proxy.Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
return proxy.SOCKS5("tcp", u.Host, auth, forward)
}
// This is a rip off of proxy.FromEnvironment with a custom forward dialer
func getDialer(forward proxy.Dialer) proxy.Dialer {
allProxy := os.Getenv("all_proxy")

View File

@@ -8,6 +8,7 @@
package events
import (
"encoding/json"
"errors"
"runtime"
"time"
@@ -118,6 +119,18 @@ func (t EventType) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t *EventType) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*t = UnmarshalEventType(s)
return nil
}
func UnmarshalEventType(s string) EventType {
switch s {
case "Starting":

View File

@@ -7,6 +7,7 @@
package events
import (
"encoding/json"
"fmt"
"testing"
"time"
@@ -321,3 +322,20 @@ func TestSinceUsesSubscriptionId(t *testing.T) {
t.Fatal("Incorrect number of events:", len(events))
}
}
func TestUnmarshalEvent(t *testing.T) {
var event Event
s := `
{
"id": 1,
"globalID": 1,
"time": "2006-01-02T15:04:05.999999999Z",
"type": "Starting",
"data": {}
}`
if err := json.Unmarshal([]byte(s), &event); err != nil {
t.Fatal("Failed to unmarshal event:", err)
}
}

View File

@@ -322,14 +322,12 @@ type fsFileInfo struct {
os.FileInfo
}
func (e fsFileInfo) Mode() FileMode {
return FileMode(e.FileInfo.Mode())
func (e fsFileInfo) IsSymlink() bool {
// Must use fsFileInfo.Mode() because it may apply magic.
return e.Mode()&ModeSymlink != 0
}
func (e fsFileInfo) IsRegular() bool {
return e.FileInfo.Mode().IsRegular()
}
func (e fsFileInfo) IsSymlink() bool {
return e.FileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
// Must use fsFileInfo.Mode() because it may apply magic.
return e.Mode()&ModeType == 0
}

View File

@@ -218,7 +218,11 @@ func testScenario(t *testing.T, name string, testCase func(), expectedEvents []E
go testWatchOutput(t, name, eventChan, expectedEvents, allowOthers, ctx, cancel)
timeout := time.NewTimer(2 * time.Second)
timeoutDuration := 2 * time.Second
if runtime.GOOS == "darwin" {
timeoutDuration *= 2
}
timeout := time.NewTimer(timeoutDuration)
testCase()

View File

@@ -14,9 +14,9 @@ import (
)
var (
l = logger.DefaultLogger.NewFacility("filesystem", "Filesystem access")
l = logger.DefaultLogger.NewFacility("fs", "Filesystem access")
)
func init() {
l.SetDebug("filesystem", strings.Contains(os.Getenv("STTRACE"), "filesystem") || os.Getenv("STTRACE") == "all")
l.SetDebug("fs", strings.Contains(os.Getenv("STTRACE"), "fs") || os.Getenv("STTRACE") == "all")
}

View File

@@ -126,6 +126,8 @@ const ModePerm = FileMode(os.ModePerm)
const ModeSetgid = FileMode(os.ModeSetgid)
const ModeSetuid = FileMode(os.ModeSetuid)
const ModeSticky = FileMode(os.ModeSticky)
const ModeSymlink = FileMode(os.ModeSymlink)
const ModeType = FileMode(os.ModeType)
const PathSeparator = os.PathSeparator
const OptAppend = os.O_APPEND
const OptCreate = os.O_CREATE

13
lib/fs/fsfileinfo_unix.go Normal file
View File

@@ -0,0 +1,13 @@
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build !windows
package fs
func (e fsFileInfo) Mode() FileMode {
return FileMode(e.FileInfo.Mode())
}

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package fs
import (
"os"
)
func (e fsFileInfo) Mode() FileMode {
m := e.FileInfo.Mode()
if m&os.ModeSymlink != 0 && e.Size() > 0 {
// "Symlinks" with nonzero size are in fact "hard" links, such as
// NTFS deduped files. Remove the symlink bit.
m &^= os.ModeSymlink
}
return FileMode(m)
}

View File

@@ -308,6 +308,7 @@ type recorder struct {
type Line struct {
When time.Time `json:"when"`
Message string `json:"message"`
Level LogLevel `json:"level"`
}
func NewRecorder(l Logger, level LogLevel, size, initial int) Recorder {
@@ -324,18 +325,18 @@ func (r *recorder) Since(t time.Time) []Line {
defer r.mut.Unlock()
res := r.lines
for i := 0; i < len(res) && res[i].When.Before(t); i++ {
// nothing, just incrementing i
}
if len(res) == 0 {
return nil
}
// We must copy the result as r.lines can be mutated as soon as the lock
// is released.
cp := make([]Line, len(res))
copy(cp, res)
return cp
for i := 0; i < len(res); i++ {
if res[i].When.After(t) {
// We must copy the result as r.lines can be mutated as soon as the lock
// is released.
res = res[i:]
cp := make([]Line, len(res))
copy(cp, res)
return cp
}
}
return nil
}
func (r *recorder) Clear() {
@@ -348,6 +349,7 @@ func (r *recorder) append(l LogLevel, msg string) {
line := Line{
When: time.Now(),
Message: msg,
Level: l,
}
r.mut.Lock()
@@ -367,6 +369,6 @@ func (r *recorder) append(l LogLevel, msg string) {
r.lines = append(r.lines, line)
if len(r.lines) == r.initial {
r.lines = append(r.lines, Line{time.Now(), "..."})
r.lines = append(r.lines, Line{time.Now(), "...", l})
}
}

View File

@@ -130,4 +130,24 @@ func TestRecorder(t *testing.T) {
}
}
// Check that since works
now := time.Now()
time.Sleep(time.Millisecond)
lines = r1.Since(now)
if len(lines) != 0 {
t.Error("unexpected lines")
}
l.Infoln("hah")
lines = r1.Since(now)
if len(lines) != 1 {
t.Fatalf("unexpected line count: %d", len(lines))
}
if lines[0].Message != "hah" {
t.Errorf("incorrect line: %s", lines[0])
}
}

View File

@@ -110,6 +110,7 @@ var (
errDevicePaused = errors.New("device is paused")
errDeviceIgnored = errors.New("device is ignored")
errFolderPaused = errors.New("folder is paused")
errFolderNotRunning = errors.New("folder is not running")
errFolderMissing = errors.New("no such folder")
errNetworkNotAllowed = errors.New("network not allowed")
)
@@ -182,15 +183,13 @@ func (m *Model) StartFolder(folder string) {
}
func (m *Model) startFolderLocked(folder string) config.FolderType {
cfg, ok := m.folderCfgs[folder]
if !ok {
panic("cannot start nonexistent folder " + cfg.Description())
if err := m.checkFolderRunningLocked(folder); err == errFolderMissing {
panic("cannot start nonexistent folder " + folder)
} else if err == nil {
panic("cannot start already running folder " + folder)
}
_, ok = m.folderRunners[folder]
if ok {
panic("cannot start already running folder " + cfg.Description())
}
cfg := m.folderCfgs[folder]
folderFactory, ok := folderFactories[cfg.Type]
if !ok {
@@ -383,12 +382,12 @@ func (m *Model) RestartFolder(cfg config.FolderConfiguration) {
m.pmut.Lock()
m.tearDownFolderLocked(cfg.ID)
if !cfg.Paused {
if cfg.Paused {
l.Infoln("Paused folder", cfg.Description())
} else {
m.addFolderLocked(cfg)
folderType := m.startFolderLocked(cfg.ID)
l.Infoln("Restarted folder", cfg.Description(), fmt.Sprintf("(%s)", folderType))
} else {
l.Infoln("Paused folder", cfg.Description())
}
m.pmut.Unlock()
@@ -585,6 +584,7 @@ func (m *Model) FolderStatistics() map[string]stats.FolderStatistics {
type FolderCompletion struct {
CompletionPct float64
NeedBytes int64
NeedItems int64
GlobalBytes int64
NeedDeletes int64
}
@@ -611,7 +611,7 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
counts := m.deviceDownloads[device].GetBlockCounts(folder)
m.pmut.RUnlock()
var need, fileNeed, downloaded, deletes int64
var need, items, fileNeed, downloaded, deletes int64
rf.WithNeedTruncated(device, func(f db.FileIntf) bool {
ft := f.(db.FileInfoTruncated)
@@ -630,6 +630,8 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
}
need += fileNeed
items++
return true
})
@@ -649,6 +651,7 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
return FolderCompletion{
CompletionPct: completionPct,
NeedBytes: need,
NeedItems: items,
GlobalBytes: tot,
NeedDeletes: deletes,
}
@@ -715,15 +718,13 @@ func (m *Model) NeedSize(folder string) db.Counts {
// NeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int) {
func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
m.fmut.RLock()
defer m.fmut.RUnlock()
total := 0
rf, ok := m.folderFiles[folder]
if !ok {
return nil, nil, nil, 0
return nil, nil, nil
}
var progress, queued, rest []db.FileInfoTruncated
@@ -766,7 +767,6 @@ func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfo
return true
}
total++
if skip > 0 {
skip--
return true
@@ -778,10 +778,43 @@ func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfo
get--
}
}
return true
return get > 0
})
return progress, queued, rest, total
return progress, queued, rest
}
// RemoteNeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (m *Model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
m.fmut.RLock()
m.pmut.RLock()
if err := m.checkDeviceFolderConnectedLocked(device, folder); err != nil {
m.pmut.RUnlock()
m.fmut.RUnlock()
return nil, err
}
rf := m.folderFiles[folder]
m.pmut.RUnlock()
m.fmut.RUnlock()
files := make([]db.FileInfoTruncated, 0, perpage)
skip := (page - 1) * perpage
get := perpage
rf.WithNeedTruncated(device, func(f db.FileIntf) bool {
if skip > 0 {
skip--
return true
}
if get > 0 {
files = append(files, f.(db.FileInfoTruncated))
get--
}
return get > 0
})
return files, nil
}
// Index is called when a new device is connected and we receive their full index.
@@ -892,6 +925,8 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
}
dbLocation := filepath.Dir(m.db.Location())
changed := false
deviceCfg := m.cfg.Devices()[deviceID]
// See issue #3802 - in short, we can't send modern symlink entries to older
// clients.
@@ -901,6 +936,13 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
dropSymlinks = true
}
// Needs to happen outside of the fmut, as can cause CommitConfiguration
if deviceCfg.AutoAcceptFolders {
for _, folder := range cm.Folders {
changed = m.handleAutoAccepts(deviceCfg, folder) || changed
}
}
m.fmut.Lock()
var paused []string
for _, folder := range cm.Folders {
@@ -927,6 +969,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
continue
}
if !folder.DisableTempIndexes {
tempIndexFolders = append(tempIndexFolders, folder.ID)
}
@@ -1021,8 +1064,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
}
}
var changed = false
if deviceCfg := m.cfg.Devices()[deviceID]; deviceCfg.Introducer {
if deviceCfg.Introducer {
foldersDevices, introduced := m.handleIntroductions(deviceCfg, cm)
if introduced {
changed = true
@@ -1063,6 +1105,11 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
// the folder.
nextDevice:
for _, device := range folder.Devices {
// No need to share with self.
if device.ID == m.id {
continue
}
foldersDevices.set(device.ID, folder.ID)
if _, ok := m.cfg.Devices()[device.ID]; !ok {
@@ -1081,7 +1128,8 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
// We don't yet share this folder with this device. Add the device
// to sharing list of the folder.
m.introduceDeviceToFolder(device, folder, introducerCfg)
l.Infof("Sharing folder %s with %v (vouched for by introducer %v)", folder.Description(), device.ID, introducerCfg.DeviceID)
m.shareFolderWithDeviceLocked(device.ID, folder.ID, introducerCfg.DeviceID)
changed = true
}
}
@@ -1089,7 +1137,7 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
return foldersDevices, changed
}
// handleIntroductions handles removals of devices/shares that are removed by an introducer device
// handleDeintroductions handles removals of devices/shares that are removed by an introducer device
func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
changed := false
foldersIntroducedByOthers := make(folderDeviceSet)
@@ -1142,6 +1190,51 @@ func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
return changed
}
// handleAutoAccepts handles adding and sharing folders for devices that have
// AutoAcceptFolders set to true.
func (m *Model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) bool {
if _, ok := m.cfg.Folder(folder.ID); !ok {
defaultPath := m.cfg.Options().DefaultFolderPath
defaultPathFs := fs.NewFilesystem(fs.FilesystemTypeBasic, defaultPath)
for _, path := range []string{folder.Label, folder.ID} {
if _, err := defaultPathFs.Lstat(path); !fs.IsNotExist(err) {
continue
}
fcfg := config.NewFolderConfiguration(m.id, folder.ID, folder.Label, fs.FilesystemTypeBasic, filepath.Join(defaultPath, path))
// Need to wait for the waiter, as this calls CommitConfiguration,
// which sets up the folder and as we return from this call,
// ClusterConfig starts poking at m.folderFiles and other things
// that might not exist until the config is committed.
w, _ := m.cfg.SetFolder(fcfg)
w.Wait()
// This needs to happen under a lock.
m.fmut.Lock()
w = m.shareFolderWithDeviceLocked(deviceCfg.DeviceID, folder.ID, protocol.DeviceID{})
m.fmut.Unlock()
w.Wait()
l.Infof("Auto-accepted %s folder %s at path %s", deviceCfg.DeviceID, folder.Description(), fcfg.Path)
return true
}
l.Infof("Failed to auto-accept folder %s from %s due to path conflict", folder.Description(), deviceCfg.DeviceID)
return false
}
// Folder does not exist yet.
if m.folderSharedWith(folder.ID, deviceCfg.DeviceID) {
return false
}
m.fmut.Lock()
w := m.shareFolderWithDeviceLocked(deviceCfg.DeviceID, folder.ID, protocol.DeviceID{})
m.fmut.Unlock()
w.Wait()
l.Infof("Shared %s with %s due to auto-accept", folder.ID, deviceCfg.DeviceID)
return true
}
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
addresses := []string{"dynamic"}
for _, addr := range device.Addresses {
@@ -1170,18 +1263,17 @@ func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.Dev
m.cfg.SetDevice(newDeviceCfg)
}
func (m *Model) introduceDeviceToFolder(device protocol.Device, folder protocol.Folder, introducerCfg config.DeviceConfiguration) {
l.Infof("Sharing folder %s with %v (vouched for by introducer %v)", folder.Description(), device.ID, introducerCfg.DeviceID)
func (m *Model) shareFolderWithDeviceLocked(deviceID protocol.DeviceID, folder string, introducer protocol.DeviceID) config.Waiter {
m.deviceFolders[deviceID] = append(m.deviceFolders[deviceID], folder)
m.folderDevices.set(deviceID, folder)
m.deviceFolders[device.ID] = append(m.deviceFolders[device.ID], folder.ID)
m.folderDevices.set(device.ID, folder.ID)
folderCfg := m.cfg.Folders()[folder.ID]
folderCfg := m.cfg.Folders()[folder]
folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{
DeviceID: device.ID,
IntroducedBy: introducerCfg.DeviceID,
DeviceID: deviceID,
IntroducedBy: introducer,
})
m.cfg.SetFolder(folderCfg)
w, _ := m.cfg.SetFolder(folderCfg)
return w
}
// Closed is called when a connection has been closed
@@ -1486,7 +1578,7 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
conn.ClusterConfig(cm)
device, ok := m.cfg.Devices()[deviceID]
if ok && (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) {
if ok && (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
device.Name = hello.DeviceName
m.cfg.SetDevice(device)
m.cfg.Save()
@@ -1806,22 +1898,30 @@ func (m *Model) ScanFolder(folder string) error {
}
func (m *Model) ScanFolderSubdirs(folder string, subs []string) error {
m.fmut.Lock()
runner, okRunner := m.folderRunners[folder]
cfg, okCfg := m.folderCfgs[folder]
m.fmut.Unlock()
if !okRunner {
if okCfg && cfg.Paused {
return errFolderPaused
}
return errFolderMissing
m.fmut.RLock()
if err := m.checkFolderRunningLocked(folder); err != nil {
m.fmut.RUnlock()
return err
}
runner := m.folderRunners[folder]
m.fmut.RUnlock()
return runner.Scan(subs)
}
func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, subDirs []string) error {
m.fmut.RLock()
if err := m.checkFolderRunningLocked(folder); err != nil {
m.fmut.RUnlock()
return err
}
fset := m.folderFiles[folder]
folderCfg := m.folderCfgs[folder]
ignores := m.folderIgnores[folder]
runner := m.folderRunners[folder]
m.fmut.RUnlock()
mtimefs := fset.MtimeFS()
for i := 0; i < len(subDirs); i++ {
sub := osutil.NativeFilename(subDirs[i])
@@ -1840,14 +1940,6 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
subDirs[i] = sub
}
m.fmut.Lock()
fset := m.folderFiles[folder]
folderCfg := m.folderCfgs[folder]
ignores := m.folderIgnores[folder]
runner, ok := m.folderRunners[folder]
m.fmut.Unlock()
mtimefs := fset.MtimeFS()
// Check if the ignore patterns changed as part of scanning this folder.
// If they did we should schedule a pull of the folder so that we
// request things we might have suddenly become unignored and so on.
@@ -1859,13 +1951,6 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
}
}()
if !ok {
if folderCfg.Paused {
return errFolderPaused
}
return errFolderMissing
}
if err := runner.CheckHealth(); err != nil {
return err
}
@@ -2366,10 +2451,10 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
if _, ok := fromFolders[folderID]; !ok {
// A folder was added.
if cfg.Paused {
l.Infoln(m, "Paused folder", cfg.Description())
l.Infoln("Paused folder", cfg.Description())
cfg.CreateRoot()
} else {
l.Infoln(m, "Adding folder", cfg.Description())
l.Infoln("Adding folder", cfg.Description())
m.AddFolder(cfg)
m.StartFolder(folderID)
}
@@ -2385,13 +2470,8 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
}
// This folder exists on both sides. Settings might have changed.
// Check if anything differs, apart from the label.
toCfgCopy := toCfg
fromCfgCopy := fromCfg
fromCfgCopy.Label = ""
toCfgCopy.Label = ""
if !reflect.DeepEqual(fromCfgCopy, toCfgCopy) {
// Check if anything differs that requires a restart.
if !reflect.DeepEqual(fromCfg.RequiresRestartOnly(), toCfg.RequiresRestartOnly()) {
m.RestartFolder(toCfg)
}
@@ -2431,25 +2511,9 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
}
// Some options don't require restart as those components handle it fine
// by themselves.
from.Options.URAccepted = to.Options.URAccepted
from.Options.URSeen = to.Options.URSeen
from.Options.URUniqueID = to.Options.URUniqueID
from.Options.ListenAddresses = to.Options.ListenAddresses
from.Options.RelaysEnabled = to.Options.RelaysEnabled
from.Options.UnackedNotificationIDs = to.Options.UnackedNotificationIDs
from.Options.MaxRecvKbps = to.Options.MaxRecvKbps
from.Options.MaxSendKbps = to.Options.MaxSendKbps
from.Options.LimitBandwidthInLan = to.Options.LimitBandwidthInLan
from.Options.StunKeepaliveS = to.Options.StunKeepaliveS
from.Options.StunServers = to.Options.StunServers
// All of the other generic options require restart. Or at least they may;
// removing this check requires going through those options carefully and
// making sure there are individual services that handle them correctly.
// This code is the "original" requires-restart check and protects other
// components that haven't yet been converted to VerifyConfig/CommitConfig
// handling.
if !reflect.DeepEqual(from.Options, to.Options) {
// by themselves. Compare the options structs containing only the
// attributes that require restart and act apprioriately.
if !reflect.DeepEqual(from.Options.RequiresRestartOnly(), to.Options.RequiresRestartOnly()) {
l.Debugln(m, "requires restart, options differ")
return false
}
@@ -2457,6 +2521,48 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
return true
}
// checkFolderRunningLocked returns nil if the folder is up and running and a
// descriptive error if not.
// Need to hold (read) lock on m.fmut when calling this.
func (m *Model) checkFolderRunningLocked(folder string) error {
_, ok := m.folderRunners[folder]
if ok {
return nil
}
if cfg, ok := m.cfg.Folder(folder); !ok {
return errFolderMissing
} else if cfg.Paused {
return errFolderPaused
}
return errFolderNotRunning
}
// checkFolderDeviceStatusLocked first checks the folder and then whether the
// given device is connected and shares this folder.
// Need to hold (read) lock on both m.fmut and m.pmut when calling this.
func (m *Model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folder string) error {
if err := m.checkFolderRunningLocked(folder); err != nil {
return err
}
if cfg, ok := m.cfg.Device(device); !ok {
return errDeviceUnknown
} else if cfg.Paused {
return errDevicePaused
}
if _, ok := m.conn[device]; !ok {
return errors.New("device is not connected")
}
if !m.folderDevices.has(device, folder) {
return errors.New("folder is not shared with device")
}
return nil
}
// mapFolders returns a map of folder ID to folder configuration for the given
// slice of folder configurations.
func mapFolders(folders []config.FolderConfiguration) map[string]config.FolderConfiguration {

View File

@@ -18,6 +18,7 @@ import (
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
@@ -25,6 +26,7 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
@@ -36,13 +38,14 @@ var device1, device2 protocol.DeviceID
var defaultConfig *config.Wrapper
var defaultFolderConfig config.FolderConfiguration
var defaultFs fs.Filesystem
var defaultAutoAcceptCfg config.Configuration
func init() {
device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
defaultFs = fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata")
defaultFolderConfig = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "testdata")
defaultFolderConfig = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
defaultFolderConfig.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
_defaultConfig := config.Configuration{
Folders: []config.FolderConfiguration{defaultFolderConfig},
@@ -53,6 +56,17 @@ func init() {
},
}
defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
defaultAutoAcceptCfg = config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
AutoAcceptFolders: true,
},
},
Options: config.OptionsConfiguration{
DefaultFolderPath: "testdata",
},
}
}
var testDataExpected = map[string]protocol.FileInfo{
@@ -87,6 +101,20 @@ func init() {
}
}
func newState(cfg config.Configuration) (*config.Wrapper, *Model) {
db := db.OpenMemory()
wcfg := config.Wrap("/tmp/test", cfg)
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
for _, folder := range cfg.Folders {
m.AddFolder(folder)
}
m.ServeBackground()
m.AddConnection(&fakeConnection{id: device1}, protocol.HelloResult{})
return wcfg, m
}
func TestRequest(t *testing.T) {
db := db.OpenMemory()
@@ -152,6 +180,7 @@ func genFiles(n int) []protocol.FileInfo {
ModifiedS: t,
Sequence: int64(i + 1),
Blocks: []protocol.BlockInfo{{Offset: 0, Size: 100, Hash: []byte("some hash bytes")}},
Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}},
}
}
@@ -609,20 +638,6 @@ func TestIntroducer(t *testing.T) {
return false
}
newState := func(cfg config.Configuration) (*config.Wrapper, *Model) {
db := db.OpenMemory()
wcfg := config.Wrap("/tmp/test", cfg)
m := NewModel(wcfg, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
for _, folder := range cfg.Folders {
m.AddFolder(folder)
}
m.ServeBackground()
m.AddConnection(&fakeConnection{id: device1}, protocol.HelloResult{})
return wcfg, m
}
wcfg, m := newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
@@ -970,6 +985,237 @@ func TestIntroducer(t *testing.T) {
}
}
func TestAutoAcceptRejected(t *testing.T) {
// Nothing happens if AutoAcceptFolders not set
tcfg := defaultAutoAcceptCfg.Copy()
tcfg.Devices[0].AutoAcceptFolders = false
wcfg, m := newState(tcfg)
id := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if _, ok := wcfg.Folder(id); ok || m.folderSharedWith(id, device1) {
t.Error("unexpected shared", id)
}
}
func TestAutoAcceptNewFolder(t *testing.T) {
// New folder
wcfg, m := newState(defaultAutoAcceptCfg)
id := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
t.Error("expected shared", id)
}
}
func TestAutoAcceptMultipleFolders(t *testing.T) {
// Multiple new folders
wcfg, m := newState(defaultAutoAcceptCfg)
id1 := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id1))
id2 := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id2))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id1,
Label: id1,
},
{
ID: id2,
Label: id2,
},
},
})
if _, ok := wcfg.Folder(id1); !ok || !m.folderSharedWith(id1, device1) {
t.Error("expected shared", id1)
}
if _, ok := wcfg.Folder(id2); !ok || !m.folderSharedWith(id2, device1) {
t.Error("expected shared", id2)
}
}
func TestAutoAcceptExistingFolder(t *testing.T) {
// Existing folder
id := srand.String(8)
idOther := srand.String(8) // To check that path does not get changed.
defer os.RemoveAll(filepath.Join("testdata", id))
tcfg := defaultAutoAcceptCfg.Copy()
tcfg.Folders = []config.FolderConfiguration{
{
ID: id,
Path: filepath.Join("testdata", idOther), // To check that path does not get changed.
},
}
wcfg, m := newState(tcfg)
if _, ok := wcfg.Folder(id); !ok || m.folderSharedWith(id, device1) {
t.Error("missing folder, or shared", id)
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || fcfg.Path != filepath.Join("testdata", idOther) {
t.Error("missing folder, or unshared, or path changed", id)
}
}
func TestAutoAcceptNewAndExistingFolder(t *testing.T) {
// New and existing folder
id1 := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id1))
id2 := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id2))
tcfg := defaultAutoAcceptCfg.Copy()
tcfg.Folders = []config.FolderConfiguration{
{
ID: id1,
Path: filepath.Join("testdata", id1), // from previous test case, to verify that path doesn't get changed.
},
}
wcfg, m := newState(tcfg)
if _, ok := wcfg.Folder(id1); !ok || m.folderSharedWith(id1, device1) {
t.Error("missing folder, or shared", id1)
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id1,
Label: id1,
},
{
ID: id2,
Label: id2,
},
},
})
for i, id := range []string{id1, id2} {
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
t.Error("missing folder, or unshared", i, id)
}
}
}
func TestAutoAcceptAlreadyShared(t *testing.T) {
// Already shared
id := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id))
tcfg := defaultAutoAcceptCfg.Copy()
tcfg.Folders = []config.FolderConfiguration{
{
ID: id,
Path: filepath.Join("testdata", id),
Devices: []config.FolderDeviceConfiguration{
{
DeviceID: device1,
},
},
},
}
wcfg, m := newState(tcfg)
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
t.Error("missing folder, or not shared", id)
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
t.Error("missing folder, or not shared", id)
}
}
func TestAutoAcceptNameConflict(t *testing.T) {
id := srand.String(8)
label := srand.String(8)
os.MkdirAll(filepath.Join("testdata", id), 0777)
os.MkdirAll(filepath.Join("testdata", label), 0777)
defer os.RemoveAll(filepath.Join("testdata", id))
defer os.RemoveAll(filepath.Join("testdata", label))
wcfg, m := newState(defaultAutoAcceptCfg)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: label,
},
},
})
if _, ok := wcfg.Folder(id); ok || m.folderSharedWith(id, device1) {
t.Error("unexpected folder", id)
}
}
func TestAutoAcceptPrefersLabel(t *testing.T) {
// Prefers label, falls back to ID.
wcfg, m := newState(defaultAutoAcceptCfg)
id := srand.String(8)
label := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id))
defer os.RemoveAll(filepath.Join("testdata", label))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: label,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || !strings.HasSuffix(fcfg.Path, label) {
t.Error("expected shared, or wrong path", id, label, fcfg.Path)
}
}
func TestAutoAcceptFallsBackToID(t *testing.T) {
// Prefers label, falls back to ID.
wcfg, m := newState(defaultAutoAcceptCfg)
id := srand.String(8)
label := srand.String(8)
os.MkdirAll(filepath.Join("testdata", label), 0777)
defer os.RemoveAll(filepath.Join("testdata", label))
defer os.RemoveAll(filepath.Join("testdata", id))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: label,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || !strings.HasSuffix(fcfg.Path, id) {
t.Error("expected shared, or wrong path", id, label, fcfg.Path)
}
}
func changeIgnores(t *testing.T, m *Model, expected []string) {
arrEqual := func(a, b []string) bool {
if len(a) != len(b) {
@@ -1101,7 +1347,7 @@ func TestROScanRecovery(t *testing.T) {
ldb := db.OpenMemory()
set := db.NewFileSet("default", defaultFs, ldb)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile"},
{Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}},
})
fcfg := config.FolderConfiguration{
@@ -1189,7 +1435,7 @@ func TestRWScanRecovery(t *testing.T) {
ldb := db.OpenMemory()
set := db.NewFileSet("default", defaultFs, ldb)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile"},
{Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}},
})
fcfg := config.FolderConfiguration{
@@ -1877,39 +2123,6 @@ func TestIssue3028(t *testing.T) {
}
}
func TestIssue3164(t *testing.T) {
os.RemoveAll("testdata/issue3164")
defer os.RemoveAll("testdata/issue3164")
if err := os.MkdirAll("testdata/issue3164/oktodelete/foobar", 0777); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/issue3164/oktodelete/foobar/file", []byte("Hello"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/issue3164/oktodelete/file", []byte("Hello"), 0644); err != nil {
t.Fatal(err)
}
f := protocol.FileInfo{
Name: "issue3164",
}
m := ignore.New(defaultFs)
if err := m.Parse(bytes.NewBufferString("(?d)oktodelete"), ""); err != nil {
t.Fatal(err)
}
fl := sendReceiveFolder{
dbUpdates: make(chan dbUpdateJob, 1),
fs: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
}
fl.deleteDir(f, m)
if _, err := os.Stat("testdata/issue3164"); !os.IsNotExist(err) {
t.Fatal(err)
}
}
func TestIssue4357(t *testing.T) {
db := db.OpenMemory()
cfg := defaultConfig.RawCopy()
@@ -1920,7 +2133,9 @@ func TestIssue4357(t *testing.T) {
defer m.Stop()
// Force the model to wire itself and add the folders
if err := wrapper.ReplaceBlocking(cfg); err != nil {
p, err := wrapper.Replace(cfg)
p.Wait()
if err != nil {
t.Error(err)
}
@@ -1931,7 +2146,9 @@ func TestIssue4357(t *testing.T) {
newCfg := wrapper.RawCopy()
newCfg.Folders[0].Paused = true
if err := wrapper.ReplaceBlocking(newCfg); err != nil {
p, err = wrapper.Replace(newCfg)
p.Wait()
if err != nil {
t.Error(err)
}
@@ -1943,7 +2160,9 @@ func TestIssue4357(t *testing.T) {
t.Error("should still have folder in config")
}
if err := wrapper.ReplaceBlocking(config.Configuration{}); err != nil {
p, err = wrapper.Replace(config.Configuration{})
p.Wait()
if err != nil {
t.Error(err)
}
@@ -1952,7 +2171,9 @@ func TestIssue4357(t *testing.T) {
}
// Add the folder back, should be running
if err := wrapper.ReplaceBlocking(cfg); err != nil {
p, err = wrapper.Replace(cfg)
p.Wait()
if err != nil {
t.Error(err)
}
@@ -1964,7 +2185,9 @@ func TestIssue4357(t *testing.T) {
}
// Should not panic when removing a running folder.
if err := wrapper.ReplaceBlocking(config.Configuration{}); err != nil {
p, err = wrapper.Replace(config.Configuration{})
p.Wait()
if err != nil {
t.Error(err)
}
@@ -2066,7 +2289,7 @@ func TestIssue2782(t *testing.T) {
db := db.OpenMemory()
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
m.AddFolder(config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "~/"+testName+"/synclink/"))
m.AddFolder(config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "~/"+testName+"/synclink/"))
m.StartFolder("default")
m.ServeBackground()
defer m.Stop()
@@ -2111,7 +2334,7 @@ func TestIndexesForUnknownDevicesDropped(t *testing.T) {
func TestSharedWithClearedOnDisconnect(t *testing.T) {
dbi := db.OpenMemory()
fcfg := config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "testdata")
fcfg := config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
fcfg.Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
@@ -2177,7 +2400,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
cfg = cfg.Copy()
cfg.Devices = cfg.Devices[:1]
if err := wcfg.Replace(cfg); err != nil {
if _, err := wcfg.Replace(cfg); err != nil {
t.Error(err)
}
@@ -2309,7 +2532,7 @@ func TestIssue3496(t *testing.T) {
// The one we added synthetically above
t.Errorf("Incorrect need size; %d, %d != 1, 1234", need.Files, need.Bytes)
}
if need.Deleted != len(localFiles)-1 {
if int(need.Deleted) != len(localFiles)-1 {
// The rest
t.Errorf("Incorrect need deletes; %d != %d", need.Deleted, len(localFiles)-1)
}
@@ -2350,7 +2573,7 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
dbi := db.OpenMemory()
fcfg := config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "testdata")
fcfg := config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
fcfg.Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
@@ -2507,6 +2730,260 @@ func TestCustomMarkerName(t *testing.T) {
}
}
func TestRemoveDirWithContent(t *testing.T) {
defer func() {
defaultFs.RemoveAll("dirwith")
}()
defaultFs.MkdirAll("dirwith", 0755)
content := filepath.Join("dirwith", "content")
fd, err := defaultFs.Create(content)
if err != nil {
t.Fatal(err)
return
}
fd.Close()
dbi := db.OpenMemory()
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
m.AddFolder(defaultFolderConfig)
m.StartFolder("default")
m.ServeBackground()
defer m.Stop()
m.ScanFolder("default")
dir, ok := m.CurrentFolderFile("default", "dirwith")
if !ok {
t.Fatalf("Can't get dir \"dirwith\" after initial scan")
}
dir.Deleted = true
dir.Version = dir.Version.Update(device1.Short()).Update(device1.Short())
file, ok := m.CurrentFolderFile("default", content)
if !ok {
t.Fatalf("Can't get file \"%v\" after initial scan", content)
}
file.Deleted = true
file.Version = file.Version.Update(device1.Short()).Update(device1.Short())
m.IndexUpdate(device1, "default", []protocol.FileInfo{dir, file})
// Is there something we could trigger on instead of just waiting?
timeout := time.NewTimer(5 * time.Second)
for {
dir, ok := m.CurrentFolderFile("default", "dirwith")
if !ok {
t.Fatalf("Can't get dir \"dirwith\" after index update")
}
file, ok := m.CurrentFolderFile("default", content)
if !ok {
t.Fatalf("Can't get file \"%v\" after index update", content)
}
if dir.Deleted && file.Deleted {
return
}
select {
case <-timeout.C:
if !dir.Deleted && !file.Deleted {
t.Errorf("Neither the dir nor its content was deleted before timing out.")
} else if !dir.Deleted {
t.Errorf("The dir was not deleted before timing out.")
} else {
t.Errorf("The content of the dir was not deleted before timing out.")
}
return
default:
time.Sleep(100 * time.Millisecond)
}
}
}
func TestIssue4475(t *testing.T) {
defer func() {
defaultFs.RemoveAll("delDir")
}()
err := defaultFs.MkdirAll("delDir", 0755)
if err != nil {
t.Fatal(err)
}
dbi := db.OpenMemory()
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
m.AddFolder(defaultFolderConfig)
m.StartFolder("default")
m.ServeBackground()
defer m.Stop()
m.ScanFolder("default")
// Scenario: Dir is deleted locally and before syncing/index exchange
// happens, a file is create in that dir on the remote.
// This should result in the directory being recreated and added to the
// db locally.
if err = defaultFs.RemoveAll("delDir"); err != nil {
t.Fatal(err)
}
m.ScanFolder("default")
conn := addFakeConn(m, device1)
conn.folder = "default"
if !m.folderSharedWith("default", device1) {
t.Fatal("not shared with device1")
}
fileName := filepath.Join("delDir", "file")
conn.addFile(fileName, 0644, protocol.FileInfoTypeFile, nil)
conn.sendIndexUpdate()
// Is there something we could trigger on instead of just waiting?
timeout := time.NewTimer(5 * time.Second)
created := false
for {
if !created {
if _, ok := m.CurrentFolderFile("default", fileName); ok {
created = true
}
} else {
dir, ok := m.CurrentFolderFile("default", "delDir")
if !ok {
t.Fatalf("can't get dir from db")
}
if !dir.Deleted {
return
}
}
select {
case <-timeout.C:
if created {
t.Errorf("Timed out before file from remote was created")
} else {
t.Errorf("Timed out before directory was resurrected in db")
}
return
default:
time.Sleep(100 * time.Millisecond)
}
}
}
func TestPausedFolders(t *testing.T) {
// Create a separate wrapper not to pollute other tests.
cfg := defaultConfig.RawCopy()
wrapper := config.Wrap("/tmp/test", cfg)
db := db.OpenMemory()
m := NewModel(wrapper, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig)
m.StartFolder("default")
m.ServeBackground()
defer m.Stop()
if err := m.ScanFolder("default"); err != nil {
t.Error(err)
}
pausedConfig := wrapper.RawCopy()
pausedConfig.Folders[0].Paused = true
w, err := m.cfg.Replace(pausedConfig)
if err != nil {
t.Fatal(err)
}
w.Wait()
if err := m.ScanFolder("default"); err != errFolderPaused {
t.Errorf("Expected folder paused error, received: %v", err)
}
if err := m.ScanFolder("nonexistent"); err != errFolderMissing {
t.Errorf("Expected missing folder error, received: %v", err)
}
}
func TestPullInvalid(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("Windows only")
}
tmpDir, err := ioutil.TempDir(".", "_model-")
if err != nil {
panic("Failed to create temporary testing dir")
}
defer os.RemoveAll(tmpDir)
cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
w := config.Wrap("/tmp/cfg", cfg)
db := db.OpenMemory()
m := NewModel(w, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
m.AddFolder(cfg.Folders[0])
m.StartFolder("default")
m.ServeBackground()
defer m.Stop()
m.ScanFolder("default")
if err := m.SetIgnores("default", []string{"*:ignored"}); err != nil {
panic(err)
}
ign := "invalid:ignored"
del := "invalid:deleted"
var version protocol.Vector
version = version.Update(device1.Short())
m.IndexUpdate(device1, "default", []protocol.FileInfo{
{
Name: ign,
Size: 1234,
Type: protocol.FileInfoTypeFile,
Version: version,
},
{
Name: del,
Size: 1234,
Type: protocol.FileInfoTypeFile,
Version: version,
Deleted: true,
},
})
sub := events.Default.Subscribe(events.FolderErrors)
defer events.Default.Unsubscribe(sub)
timeout := time.NewTimer(5 * time.Second)
for {
select {
case ev := <-sub.C():
t.Fatalf("Errors while pulling: %v", ev)
case <-timeout.C:
t.Fatalf("File wasn't added to index until timeout")
default:
}
file, ok := m.CurrentFolderFile("default", ign)
if !ok {
time.Sleep(100 * time.Millisecond)
continue
}
if !file.Invalid {
t.Error("Ignored file isn't marked as invalid")
}
if file, ok = m.CurrentFolderFile("default", del); ok {
t.Error("Deleted invalid file was added to index")
}
return
}
}
func addFakeConn(m *Model, dev protocol.DeviceID) *fakeConnection {
fc := &fakeConnection{id: dev, model: m}
m.AddConnection(fc, protocol.HelloResult{})

View File

@@ -216,7 +216,7 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
panic("Failed to create temporary testing dir")
}
cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
@@ -292,7 +292,7 @@ func setupModelWithConnection() (*Model, *fakeConnection, string) {
panic("Failed to create temporary testing dir")
}
cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},

View File

@@ -60,6 +60,9 @@ var (
activity = newDeviceActivity()
errNoDevice = errors.New("peers who had this file went away, or the file has changed while syncing. will retry later")
errSymlinksUnsupported = errors.New("symlinks not supported")
errDirHasToBeScanned = errors.New("directory contains unexpected files, scheduling scan")
errDirHasIgnored = errors.New("directory contains ignored files (see ignore documentation for (?d) prefix)")
errDirNotEmpty = errors.New("directory is not empty")
)
const (
@@ -92,7 +95,6 @@ type sendReceiveFolder struct {
pause time.Duration
queue *jobQueue
dbUpdates chan dbUpdateJob
pullScheduled chan struct{}
errors map[string]string // path -> error string
@@ -251,17 +253,21 @@ func (f *sendReceiveFolder) pull(prevIgnoreHash string) (curIgnoreHash string, s
curIgnoreHash = curIgnores.Hash()
ignoresChanged := curIgnoreHash != prevIgnoreHash
l.Debugln(f, "pulling")
l.Debugf("%v pulling (ignoresChanged=%v)", f, ignoresChanged)
f.setState(FolderSyncing)
f.clearErrors()
scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
var changed int
tries := 0
for {
tries++
changed := f.pullerIteration(curIgnores, ignoresChanged)
changed = f.pullerIteration(curIgnores, ignoresChanged, scanChan)
l.Debugln(f, "changed", changed)
if changed == 0 {
@@ -294,6 +300,8 @@ func (f *sendReceiveFolder) pull(prevIgnoreHash string) (curIgnoreHash string, s
f.setState(FolderIdle)
close(scanChan)
if changed == 0 {
return curIgnoreHash, true
}
@@ -305,23 +313,23 @@ func (f *sendReceiveFolder) pull(prevIgnoreHash string) (curIgnoreHash string, s
// returns the number items that should have been synced (even those that
// might have failed). One puller iteration handles all files currently
// flagged as needed in the folder.
func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChanged bool) int {
func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChanged bool, scanChan chan<- string) int {
pullChan := make(chan pullBlockState)
copyChan := make(chan copyBlocksState)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob)
updateWg := sync.NewWaitGroup()
copyWg := sync.NewWaitGroup()
pullWg := sync.NewWaitGroup()
copyWg := sync.NewWaitGroup()
doneWg := sync.NewWaitGroup()
updateWg := sync.NewWaitGroup()
l.Debugln(f, "c", f.Copiers, "p", f.Pullers)
f.dbUpdates = make(chan dbUpdateJob)
updateWg.Add(1)
go func() {
// dbUpdaterRoutine finishes when f.dbUpdates is closed
f.dbUpdaterRoutine()
// dbUpdaterRoutine finishes when dbUpdateChan is closed
f.dbUpdaterRoutine(dbUpdateChan)
updateWg.Done()
}()
@@ -346,7 +354,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
doneWg.Add(1)
// finisherRoutine finishes when finisherChan is closed
go func() {
f.finisherRoutine(finisherChan)
f.finisherRoutine(ignores, finisherChan, dbUpdateChan, scanChan)
doneWg.Done()
}()
@@ -370,14 +378,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
iterate(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
if f.IgnoreDelete && intf.IsDeleted() {
return true
}
// If filename isn't valid, we can terminate early with an appropriate error.
// in case it is deleted, we don't care about the filename, so don't complain.
if !intf.IsDeleted() && runtime.GOOS == "windows" && fs.WindowsInvalidFilename(intf.FileName()) {
f.newError("need", intf.FileName(), fs.ErrInvalidFilename)
changed++
l.Debugln(f, "ignore file deletion (config)", intf.FileName())
return true
}
@@ -387,7 +388,12 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
case ignores.ShouldIgnore(file.Name):
file.Invalidate(f.model.id.Short())
l.Debugln(f, "Handling ignored file", file)
f.dbUpdates <- dbUpdateJob{file, dbUpdateInvalidate}
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
case runtime.GOOS == "windows" && fs.WindowsInvalidFilename(file.Name):
f.newError("need", file.Name, fs.ErrInvalidFilename)
changed++
return true
case file.IsDeleted():
processDirectly = append(processDirectly, file)
@@ -410,7 +416,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
case runtime.GOOS == "windows" && file.IsSymlink():
file.Invalidate(f.model.id.Short())
l.Debugln(f, "Invalidating symlink (unsupported)", file.Name)
f.dbUpdates <- dbUpdateJob{file, dbUpdateInvalidate}
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
default:
// Directories, symlinks
@@ -463,11 +469,12 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
case fi.IsDirectory() && !fi.IsSymlink():
l.Debugln(f, "Handling directory", fi.Name)
f.handleDir(fi)
f.handleDir(fi, dbUpdateChan)
case fi.IsSymlink():
l.Debugln("Handling symlink", fi.Name)
l.Debugln(f, "Handling symlink", fi.Name)
f.handleSymlink(fi)
f.handleSymlink(fi, dbUpdateChan)
default:
l.Warnln(fi)
@@ -522,13 +529,33 @@ nextFile:
continue
}
dirName := filepath.Dir(fi.Name)
// Verify that the thing we are handling lives inside a directory,
// and not a symlink or empty space.
if err := osutil.TraversesSymlink(f.fs, filepath.Dir(fi.Name)); err != nil {
if err := osutil.TraversesSymlink(f.fs, dirName); err != nil {
f.newError("traverses q", fi.Name, err)
continue
}
// issues #114 and #4475: This works around a race condition
// between two devices, when one device removes a directory and the
// other creates a file in it. However that happens, we end up with
// a directory for "foo" with the delete bit, but a file "foo/bar"
// that we want to sync. We never create the directory, and hence
// fail to create the file and end up looping forever on it. This
// breaks that by creating the directory and scheduling a scan,
// where it will be found and the delete bit on it removed. The
// user can then clean up as they like...
if _, err := f.fs.Lstat(dirName); fs.IsNotExist(err) {
l.Debugln("%v resurrecting parent directory of %v", f, fi.Name)
if err := f.fs.MkdirAll(dirName, 0755); err != nil {
f.newError("resurrecting parent dir", fi.Name, err)
continue
}
scanChan <- dirName
}
// Check our list of files to be removed for a match, in which case
// we can just do a rename instead.
key := string(fi.Blocks[0].Hash)
@@ -546,7 +573,7 @@ nextFile:
// Remove the pending deletion (as we perform it by renaming)
delete(fileDeletions, candidate.Name)
f.renameFile(desired, fi)
f.renameFile(desired, fi, dbUpdateChan)
f.queue.Done(fileName)
continue nextFile
@@ -554,7 +581,7 @@ nextFile:
}
// Handle the file normally, by coping and pulling, etc.
f.handleFile(fi, copyChan, finisherChan)
f.handleFile(fi, copyChan, finisherChan, dbUpdateChan)
}
// Signal copy and puller routines that we are done with the in data for
@@ -572,24 +599,24 @@ nextFile:
for _, file := range fileDeletions {
l.Debugln(f, "Deleting file", file.Name)
f.deleteFile(file)
f.deleteFile(file, dbUpdateChan)
}
for i := range dirDeletions {
dir := dirDeletions[len(dirDeletions)-i-1]
l.Debugln(f, "Deleting dir", dir.Name)
f.deleteDir(dir, ignores)
f.handleDeleteDir(dir, ignores, dbUpdateChan, scanChan)
}
// Wait for db updates to complete
close(f.dbUpdates)
// Wait for db updates and scan scheduling to complete
close(dbUpdateChan)
updateWg.Wait()
return changed
}
// handleDir creates or updates the given directory
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo) {
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -657,7 +684,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo) {
}
if err = osutil.InWritableDir(mkdir, f.fs, file.Name); err == nil {
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleDir}
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else {
f.newError("dir mkdir", file.Name, err)
}
@@ -673,16 +700,16 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo) {
// don't handle modification times on directories, because that sucks...)
// It's OK to change mode bits on stuff within non-writable directories.
if f.ignorePermissions(file) {
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleDir}
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err == nil {
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleDir}
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else {
f.newError("dir chmod", file.Name, err)
}
}
// handleSymlink creates or updates the given symlink
func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo) {
func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -735,14 +762,14 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo) {
}
if err = osutil.InWritableDir(createLink, f.fs, file.Name); err == nil {
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleSymlink}
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleSymlink}
} else {
f.newError("symlink create", file.Name, err)
}
}
// deleteDir attempts to delete the given directory
func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, matcher *ignore.Matcher) {
// handleDeleteDir attempts to remove a directory that was deleted on a remote
func (f *sendReceiveFolder) handleDeleteDir(file protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -764,33 +791,18 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, matcher *ignore.Ma
})
}()
// Delete any temporary files lying around in the directory
err = f.deleteDir(file.Name, ignores, scanChan)
files, _ := f.fs.DirNames(file.Name)
for _, dirFile := range files {
fullDirFile := filepath.Join(file.Name, dirFile)
if fs.IsTemporary(dirFile) || (matcher != nil && matcher.Match(fullDirFile).IsDeletable()) {
f.fs.RemoveAll(fullDirFile)
}
}
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
if err == nil || fs.IsNotExist(err) {
// It was removed or it doesn't exist to start with
f.dbUpdates <- dbUpdateJob{file, dbUpdateDeleteDir}
} else if _, serr := f.fs.Lstat(file.Name); serr != nil && !fs.IsPermission(serr) {
// We get an error just looking at the directory, and it's not a
// permission problem. Lets assume the error is in fact some variant
// of "file does not exist" (possibly expressed as some parent being a
// file and not a directory etc) and that the delete is handled.
f.dbUpdates <- dbUpdateJob{file, dbUpdateDeleteDir}
} else {
if err != nil {
f.newError("delete dir", file.Name, err)
return
}
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteDir}
}
// deleteFile attempts to delete the given file
func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo) {
func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -829,13 +841,13 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo) {
if err == nil || fs.IsNotExist(err) {
// It was removed or it doesn't exist to start with
f.dbUpdates <- dbUpdateJob{file, dbUpdateDeleteFile}
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
} else if _, serr := f.fs.Lstat(file.Name); serr != nil && !fs.IsPermission(serr) {
// We get an error just looking at the file, and it's not a permission
// problem. Lets assume the error is in fact some variant of "file
// does not exist" (possibly expressed as some parent being a file and
// not a directory etc) and that the delete is handled.
f.dbUpdates <- dbUpdateJob{file, dbUpdateDeleteFile}
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
} else {
f.newError("delete file", file.Name, err)
}
@@ -843,7 +855,7 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo) {
// renameFile attempts to rename an existing file to a destination
// and set the right attributes on it.
func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -899,7 +911,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
// of the source and the creation of the target. Fix-up the metadata,
// and update the local index of the target file.
f.dbUpdates <- dbUpdateJob{source, dbUpdateDeleteFile}
dbUpdateChan <- dbUpdateJob{source, dbUpdateDeleteFile}
err = f.shortcutFile(target)
if err != nil {
@@ -908,7 +920,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
return
}
f.dbUpdates <- dbUpdateJob{target, dbUpdateHandleFile}
dbUpdateChan <- dbUpdateJob{target, dbUpdateHandleFile}
} else {
// We failed the rename so we have a source file that we still need to
// get rid of. Attempt to delete it instead so that we make *some*
@@ -921,7 +933,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
return
}
f.dbUpdates <- dbUpdateJob{source, dbUpdateDeleteFile}
dbUpdateChan <- dbUpdateJob{source, dbUpdateDeleteFile}
}
}
@@ -961,7 +973,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState) {
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, dbUpdateChan chan<- dbUpdateJob) {
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
have, need := scanner.BlockDiff(curFile.Blocks, file.Blocks)
@@ -993,7 +1005,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
if err != nil {
f.newError("shortcut", file.Name, err)
} else {
f.dbUpdates <- dbUpdateJob{file, dbUpdateShortcutFile}
dbUpdateChan <- dbUpdateJob{file, dbUpdateShortcutFile}
}
return
@@ -1352,7 +1364,7 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *
}
}
func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, state *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
// Set the correct permission bits on the new file
if !f.ignorePermissions(state.file) {
if err := f.fs.Chmod(state.tempName, fs.FileMode(state.file.Permissions&0777)); err != nil {
@@ -1406,14 +1418,7 @@ func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
}
if changed {
// Scan() is synchronous (i.e. blocks until the scan is
// completed and returns an error), but a scan can't happen
// while we're in the puller routine. Request the scan in the
// background and it'll be handled when the current pulling
// sweep is complete. As we do retries, we'll queue the scan
// for this file up to ten times, but the last nine of those
// scans will be cheap...
go f.Scan([]string{state.curFile.Name})
scanChan <- state.curFile.Name
return fmt.Errorf("file modified but not rescanned; will try again later")
}
@@ -1423,11 +1428,7 @@ func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
// archived for conflicts, only removed (which of course fails for
// non-empty directories).
// TODO: This is the place where we want to remove temporary files
// and future hard ignores before attempting a directory delete.
// Should share code with f.deletDir().
if err = osutil.InWritableDir(f.fs.Remove, f.fs, state.file.Name); err != nil {
if err = f.deleteDir(state.file.Name, ignores, scanChan); err != nil {
return err
}
@@ -1466,11 +1467,11 @@ func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
f.fs.Chtimes(state.file.Name, state.file.ModTime(), state.file.ModTime()) // never fails
// Record the updated file in the index
f.dbUpdates <- dbUpdateJob{state.file, dbUpdateHandleFile}
dbUpdateChan <- dbUpdateJob{state.file, dbUpdateHandleFile}
return nil
}
func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState) {
func (f *sendReceiveFolder) finisherRoutine(ignores *ignore.Matcher, in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
for state := range in {
if closed, err := state.finalClose(); closed {
l.Debugln(f, "closing", state.file.Name)
@@ -1478,7 +1479,7 @@ func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState) {
f.queue.Done(state.file.Name)
if err == nil {
err = f.performFinish(state)
err = f.performFinish(ignores, state, dbUpdateChan, scanChan)
}
if err != nil {
@@ -1522,7 +1523,7 @@ func (f *sendReceiveFolder) Jobs() ([]string, []string) {
// dbUpdaterRoutine aggregates db updates and commits them in batches no
// larger than 1000 items, and no more delayed than 2 seconds.
func (f *sendReceiveFolder) dbUpdaterRoutine() {
func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
const maxBatchTime = 2 * time.Second
batch := make([]dbUpdateJob, 0, maxBatchSizeFiles)
@@ -1591,7 +1592,7 @@ func (f *sendReceiveFolder) dbUpdaterRoutine() {
loop:
for {
select {
case job, ok := <-f.dbUpdates:
case job, ok := <-dbUpdateChan:
if !ok {
break loop
}
@@ -1618,6 +1619,25 @@ loop:
}
}
// pullScannerRoutine aggregates paths to be scanned after pulling. The scan is
// scheduled once when scanChan is closed (scanning can not happen during pulling).
func (f *sendReceiveFolder) pullScannerRoutine(scanChan <-chan string) {
toBeScanned := make(map[string]struct{})
for path := range scanChan {
toBeScanned[path] = struct{}{}
}
if len(toBeScanned) != 0 {
scanList := make([]string, 0, len(toBeScanned))
for path := range toBeScanned {
l.Debugln(f, "scheduling scan after pulling for", path)
scanList = append(scanList, path)
}
f.Scan(scanList)
}
}
func (f *sendReceiveFolder) inConflict(current, replacement protocol.Vector) bool {
if current.Concurrent(replacement) {
// Obvious case
@@ -1698,7 +1718,7 @@ func (f *sendReceiveFolder) newError(context, path string, err error) {
if _, ok := f.errors[path]; ok {
return
}
l.Infof("Puller (folder %q, file %q): %s: %v", f.Description(), path, context, err)
l.Infof("Puller (folder %s, file %q): %s: %v", f.Description(), path, context, err)
f.errors[path] = fmt.Sprintf("%s: %s", context, err.Error())
}
@@ -1731,6 +1751,66 @@ func (f *sendReceiveFolder) IgnoresUpdated() {
f.SchedulePull()
}
// deleteDir attempts to delete a directory. It checks for files/dirs inside
// the directory and removes them if possible or returns an error if it fails
func (f *sendReceiveFolder) deleteDir(dir string, ignores *ignore.Matcher, scanChan chan<- string) error {
files, _ := f.fs.DirNames(dir)
toBeDeleted := make([]string, 0, len(files))
hasIgnored := false
hasKnown := false
hasToBeScanned := false
for _, dirFile := range files {
fullDirFile := filepath.Join(dir, dirFile)
if fs.IsTemporary(dirFile) || ignores.Match(fullDirFile).IsDeletable() {
toBeDeleted = append(toBeDeleted, fullDirFile)
} else if ignores != nil && ignores.Match(fullDirFile).IsIgnored() {
hasIgnored = true
} else if cf, ok := f.model.CurrentFolderFile(f.ID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() {
// Something appeared in the dir that we either are not
// aware of at all, that we think should be deleted or that
// is invalid, but not currently ignored -> schedule scan
scanChan <- fullDirFile
hasToBeScanned = true
} else {
// Dir contains file that is valid according to db and
// not ignored -> something weird is going on
hasKnown = true
}
}
if hasToBeScanned {
return errDirHasToBeScanned
}
if hasIgnored {
return errDirHasIgnored
}
if hasKnown {
return errDirNotEmpty
}
for _, del := range toBeDeleted {
f.fs.RemoveAll(del)
}
err := osutil.InWritableDir(f.fs.Remove, f.fs, dir)
if err == nil || fs.IsNotExist(err) {
// It was removed or it doesn't exist to start with
return nil
}
if _, serr := f.fs.Lstat(dir); serr != nil && !fs.IsPermission(serr) {
// We get an error just looking at the directory, and it's not a
// permission problem. Lets assume the error is in fact some variant
// of "file does not exist" (possibly expressed as some parent being a
// file and not a directory etc) and that the delete is handled.
return nil
}
return err
}
// A []fileError is sent as part of an event and will be JSON serialized.
type fileError struct {
Path string `json:"path"`

View File

@@ -7,9 +7,11 @@
package model
import (
"bytes"
"context"
"crypto/rand"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -17,6 +19,7 @@ import (
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/sync"
@@ -116,8 +119,9 @@ func TestHandleFile(t *testing.T) {
m := setUpModel(existingFile)
f := setUpSendReceiveFolder(m)
copyChan := make(chan copyBlocksState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.handleFile(requiredFile, copyChan, nil)
f.handleFile(requiredFile, copyChan, nil, dbUpdateChan)
// Receive the results
toCopy := <-copyChan
@@ -157,8 +161,9 @@ func TestHandleFileWithTemp(t *testing.T) {
m := setUpModel(existingFile)
f := setUpSendReceiveFolder(m)
copyChan := make(chan copyBlocksState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.handleFile(requiredFile, copyChan, nil)
f.handleFile(requiredFile, copyChan, nil, dbUpdateChan)
// Receive the results
toCopy := <-copyChan
@@ -207,11 +212,12 @@ func TestCopierFinder(t *testing.T) {
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState, 4)
finisherChan := make(chan *sharedPullerState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
// Run a single fetcher routine
go f.copierRoutine(copyChan, pullChan, finisherChan)
f.handleFile(requiredFile, copyChan, finisherChan)
f.handleFile(requiredFile, copyChan, finisherChan, dbUpdateChan)
pulls := []pullBlockState{<-pullChan, <-pullChan, <-pullChan, <-pullChan}
finish := <-finisherChan
@@ -331,13 +337,14 @@ func TestWeakHash(t *testing.T) {
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState, expectBlocks)
finisherChan := make(chan *sharedPullerState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
// Run a single fetcher routine
go fo.copierRoutine(copyChan, pullChan, finisherChan)
// Test 1 - no weak hashing, file gets fully repulled (`expectBlocks` pulls).
fo.WeakHashThresholdPct = 101
fo.handleFile(desiredFile, copyChan, finisherChan)
fo.handleFile(desiredFile, copyChan, finisherChan, dbUpdateChan)
var pulls []pullBlockState
for len(pulls) < expectBlocks {
@@ -365,7 +372,7 @@ func TestWeakHash(t *testing.T) {
// Test 2 - using weak hash, expectPulls blocks pulled.
fo.WeakHashThresholdPct = -1
fo.handleFile(desiredFile, copyChan, finisherChan)
fo.handleFile(desiredFile, copyChan, finisherChan, dbUpdateChan)
pulls = pulls[:0]
for len(pulls) < expectPulls {
@@ -444,11 +451,12 @@ func TestLastResortPulling(t *testing.T) {
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState, 1)
finisherChan := make(chan *sharedPullerState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
// Run a single copier routine
go f.copierRoutine(copyChan, pullChan, finisherChan)
f.handleFile(file, copyChan, finisherChan)
f.handleFile(file, copyChan, finisherChan, dbUpdateChan)
// Copier should hash empty file, realise that the region it has read
// doesn't match the hash which was advertised by the block map, fix it
@@ -491,11 +499,12 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
pullChan := make(chan pullBlockState)
finisherBufferChan := make(chan *sharedPullerState)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
go f.copierRoutine(copyChan, pullChan, finisherBufferChan)
go f.finisherRoutine(finisherChan)
go f.finisherRoutine(ignore.New(defaultFs), finisherChan, dbUpdateChan, make(chan string))
f.handleFile(file, copyChan, finisherChan)
f.handleFile(file, copyChan, finisherChan, dbUpdateChan)
// Receive a block at puller, to indicate that at least a single copier
// loop has been performed.
@@ -564,12 +573,13 @@ func TestDeregisterOnFailInPull(t *testing.T) {
pullChan := make(chan pullBlockState)
finisherBufferChan := make(chan *sharedPullerState)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
go f.copierRoutine(copyChan, pullChan, finisherBufferChan)
go f.pullerRoutine(pullChan, finisherBufferChan)
go f.finisherRoutine(finisherChan)
go f.finisherRoutine(ignore.New(defaultFs), finisherChan, dbUpdateChan, make(chan string))
f.handleFile(file, copyChan, finisherChan)
f.handleFile(file, copyChan, finisherChan, dbUpdateChan)
// Receive at finisher, we should error out as puller has nowhere to pull
// from.
@@ -607,3 +617,37 @@ func TestDeregisterOnFailInPull(t *testing.T) {
t.Fatal("Didn't get anything to the finisher")
}
}
func TestIssue3164(t *testing.T) {
m := setUpModel(protocol.FileInfo{})
f := setUpSendReceiveFolder(m)
defaultFs.RemoveAll("issue3164")
defer defaultFs.RemoveAll("issue3164")
if err := defaultFs.MkdirAll("issue3164/oktodelete/foobar", 0777); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/issue3164/oktodelete/foobar/file", []byte("Hello"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/issue3164/oktodelete/file", []byte("Hello"), 0644); err != nil {
t.Fatal(err)
}
file := protocol.FileInfo{
Name: "issue3164",
}
matcher := ignore.New(defaultFs)
if err := matcher.Parse(bytes.NewBufferString("(?d)oktodelete"), ""); err != nil {
t.Fatal(err)
}
dbUpdateChan := make(chan dbUpdateJob, 1)
f.handleDeleteDir(file, matcher, dbUpdateChan, make(chan string))
if _, err := defaultFs.Stat("testdata/issue3164"); !fs.IsNotExist(err) {
t.Fatal(err)
}
}

View File

@@ -96,24 +96,8 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
// here.
dir := filepath.Dir(s.tempName)
if info, err := s.fs.Stat(dir); err != nil {
if fs.IsNotExist(err) {
// XXX: This works around a bug elsewhere, a race condition when
// things are deleted while being synced. However that happens, we
// end up with a directory for "foo" with the delete bit, but a
// file "foo/bar" that we want to sync. We never create the
// directory, and hence fail to create the file and end up looping
// forever on it. This breaks that by creating the directory; on
// next scan it'll be found and the delete bit on it is removed.
// The user can then clean up as they like...
l.Infoln("Resurrecting directory", dir)
if err := s.fs.MkdirAll(dir, 0755); err != nil {
s.failLocked("resurrect dir", err)
return nil, err
}
} else {
s.failLocked("dst stat dir", err)
return nil, err
}
s.failLocked("dst stat dir", err)
return nil, err
} else if info.Mode()&0200 == 0 {
err := s.fs.Chmod(dir, 0755)
if !s.ignorePerms && err == nil {
@@ -174,8 +158,27 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
// Truncate sets the size of the file. This creates a sparse file or a
// space reservation, depending on the underlying filesystem.
if err := fd.Truncate(s.file.Size); err != nil {
s.failLocked("dst truncate", err)
return nil, err
// The truncate call failed. That can happen in some cases when
// space reservation isn't possible or over some network
// filesystems... This generally doesn't matter.
if s.reused > 0 {
// ... but if we are attempting to reuse a file we have a
// corner case when the old file is larger than the new one
// and we can't just overwrite blocks and let the old data
// linger at the end. In this case we attempt a delete of
// the file and hope for better luck next time, when we
// should come around with s.reused == 0.
fd.Close()
if remErr := s.fs.Remove(s.tempName); remErr != nil {
l.Debugln("failed to remove temporary file:", remErr)
}
s.failLocked("dst truncate", err)
return nil, err
}
}
}
@@ -185,26 +188,6 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
return lockedWriterAt{&s.mut, s.fd}, nil
}
// sourceFile opens the existing source file for reading
func (s *sharedPullerState) sourceFile() (fs.File, error) {
s.mut.Lock()
defer s.mut.Unlock()
// If we've already hit an error, return early
if s.err != nil {
return nil, s.err
}
// Attempt to open the existing file
fd, err := s.fs.Open(s.realName)
if err != nil {
s.failLocked("src open", err)
return nil, err
}
return fd, nil
}
// fail sets the error on the puller state compose of error, and marks the
// sharedPullerState as failed. Is a no-op when called on an already failed state.
func (s *sharedPullerState) fail(context string, err error) {

View File

@@ -14,58 +14,6 @@ import (
"github.com/syncthing/syncthing/lib/sync"
)
func TestSourceFileOK(t *testing.T) {
s := sharedPullerState{
fs: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
realName: "foo",
mut: sync.NewRWMutex(),
}
fd, err := s.sourceFile()
if err != nil {
t.Fatal(err)
}
if fd == nil {
t.Fatal("Unexpected nil fd")
}
bs := make([]byte, 6)
n, err := fd.Read(bs)
if err != nil {
t.Fatal(err)
}
if n != len(bs) {
t.Fatalf("Wrong read length %d != %d", n, len(bs))
}
if string(bs) != "foobar" {
t.Fatalf("Wrong contents %s != foobar", string(bs))
}
if err := s.failed(); err != nil {
t.Fatal(err)
}
}
func TestSourceFileBad(t *testing.T) {
s := sharedPullerState{
fs: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
realName: "nonexistent",
mut: sync.NewRWMutex(),
}
fd, err := s.sourceFile()
if err == nil {
t.Fatal("Unexpected nil error")
}
if fd != nil {
t.Fatal("Unexpected non-nil fd")
}
if err := s.failed(); err == nil {
t.Fatal("Unexpected nil failed()")
}
}
// Test creating temporary file inside read-only directory
func TestReadOnlyDir(t *testing.T) {
// Create a read only directory, clean it up afterwards.

View File

@@ -7,9 +7,8 @@
package nat
import (
"sync"
"time"
"github.com/syncthing/syncthing/lib/sync"
)
type DiscoverFunc func(renewal, timeout time.Duration) []Device
@@ -21,7 +20,7 @@ func Register(provider DiscoverFunc) {
}
func discoverAll(renewal, timeout time.Duration) map[string]Device {
wg := sync.NewWaitGroup()
wg := &sync.WaitGroup{}
wg.Add(len(providers))
c := make(chan Device)

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