Compare commits

..

139 Commits

Author SHA1 Message Date
Jakob Borg
ee000dabfd Translation update 2015-02-09 23:03:31 +01:00
Jakob Borg
a73a011ee0 Merge pull request #1323 from AudriusButkevicius/finished
Add ItemFinished event (fixes #1258)
2015-02-09 15:24:10 +01:00
Jakob Borg
2a8e5e2c14 Merge pull request #1304 from AudriusButkevicius/pprof
Add STBLOCKPROFILE
2015-02-09 15:18:42 +01:00
Jakob Borg
5d9a41f712 Merge pull request #1319 from AudriusButkevicius/renames
Fix issues with renames
2015-02-09 15:14:47 +01:00
Jakob Borg
ebcf4b60f6 Merge pull request #1320 from AudriusButkevicius/cache
Remove fd cache (ref #1308)
2015-02-09 15:10:42 +01:00
Jakob Borg
f976b78917 Merge pull request #1322 from AudriusButkevicius/browser
Opening a browser happens in it's own routine (fixes #1273)
2015-02-09 15:06:56 +01:00
Audrius Butkevicius
b88c5a89a8 Add rumpelsepp 2015-02-03 00:15:57 +00:00
Audrius Butkevicius
57028e3acc Merge pull request #1325 from rumpelsepp/master
Added exit code definitions to systemd service files (fixes #1324)
2015-02-02 08:04:27 +00:00
Stefan Tatschner
c586a17926 Added exit code definitions to systemd service files (fixes #1324) 2015-02-01 23:55:54 +01:00
Audrius Butkevicius
9d078bac54 Add STBLOCKPROFILE 2015-02-01 19:00:24 +00:00
Audrius Butkevicius
38eaefcabd Add ItemFinished event (fixes #1258) 2015-02-01 18:59:29 +00:00
Audrius Butkevicius
ba8cadc2f1 Opening a browser happens in it's own routine (fixes #1273) 2015-02-01 18:59:28 +00:00
Audrius Butkevicius
380d5dfa6d Remove fd cache (ref #1308) 2015-02-01 18:59:24 +00:00
Audrius Butkevicius
32af626630 Fix issues with renames (fixes #1302)
Extra comments explain current issues.
2015-02-01 18:58:27 +00:00
Audrius Butkevicius
8358fedaf4 Fix failing integration tests 2015-02-01 18:57:46 +00:00
Audrius Butkevicius
ec82b0c648 Merge pull request #1309 from uok/fix-override
Fix button "override changes" line break (fixes #1144)
2015-01-29 10:40:14 +00:00
Ben Schulz
81a87f873f Fix button "override changes" line break 2015-01-29 11:28:59 +01:00
Audrius Butkevicius
d91b8ac444 Merge pull request #1299 from krozycki/master
Show information in folder panel if ignore patterns are active fixes #1279
2015-01-27 19:54:09 +00:00
Karol Różycki
952e51ac75 Show information in folder panel if ignore patterns are active, fixes #1279 2015-01-27 15:27:44 +01:00
Audrius Butkevicius
11267cd44f Merge pull request #1298 from uok/link-usage
Make links to usage report clickable
2015-01-26 20:59:20 +00:00
Ben Schulz
0e59e0aebd Make links to usage report clickable 2015-01-26 17:20:55 +01:00
Audrius Butkevicius
ae1d3b3dd3 Merge pull request #1293 from uok/move-panels
Move panels to top of page (ref #1270)
2015-01-25 09:53:03 +00:00
Ben Schulz
1a91dbee5f Move panels to top of page
- Move panels (new device, new folder, notice) to top of page
- Add icons to panel headers (restart, new folder, notice)
2015-01-23 16:28:30 +01:00
Jakob Borg
fd507e3e41 Merge pull request #1290 from krozycki/master
Ensuring path separator at the end of the folder path. (fixes #1262)
2015-01-22 15:36:32 -08:00
Jakob Borg
9c1a67cf47 Add krozycki 2015-01-22 15:35:58 -08:00
Jakob Borg
69e3824840 Add test for #1262 2015-01-22 15:34:22 -08:00
Karol Różycki
fcb1a98129 Ensuring path separator at the end of the folder path. (fixes #1262) 2015-01-23 00:22:30 +01:00
Audrius Butkevicius
6d942635af Merge pull request #1269 from uok/master
Small improvements for job queue
2015-01-22 22:58:33 +00:00
Audrius Butkevicius
cda2c5d459 Fix integration tests 2015-01-22 22:42:39 +00:00
Jakob Borg
969bb5a742 Fix protocol dependency hash 2015-01-22 13:29:54 -08:00
Jakob Borg
4bccc611c3 Update dependencies 2015-01-22 12:53:10 -08:00
Jakob Borg
d18c4ece0c Merge pull request #1282 from syncthing/integ
Improvements to integration tests
2015-01-22 08:22:46 -08:00
Audrius Butkevicius
25c664b13a Improvements to integration tests 2015-01-22 00:18:08 +00:00
Audrius Butkevicius
7c680c955f Merge pull request #1274 from calmh/minor-tweaks
Integer type policy
2015-01-19 20:40:36 +00:00
Ben Schulz
f037d1b6ca improve job queue 2015-01-19 20:49:19 +01:00
Jakob Borg
2c8b627008 Integer type policy
Integers are for numbers, enabling arithmetic like subtractions and for
loops without getting shot in the foot. Unsigneds are for bitfields.

- "int" for numbers that will always be laughably smaller than four
  billion, and where we don't care about the serialization format.

- "int32" for numbers that will always be laughably smaller than four
  billion, and will be serialized to four bytes.

- "int64" for numbers that may approach four billion or will be
  serialized to eight bytes.

- "uint32" and "uint64" for bitfields, depending on required number of
  bits and serialization format. Likewise "uint8" and "uint16", although
  rare in this project since they don't exist in XDR.

- "int8", "int16" and plain "uint" are almost never useful.
2015-01-19 10:34:36 -08:00
Jakob Borg
221e3eddd5 Remove leveldb panic workaround
Haven't seen this triggered for a long time...
2015-01-19 10:23:00 -08:00
Jakob Borg
74c39c677b Actually remove test file after test run 2015-01-19 10:23:00 -08:00
Jakob Borg
e6558832bf check-contrib 2015-01-19 10:18:28 -08:00
Jakob Borg
9c50625c55 Merge pull request #1267 from rumpelsepp/doc
Fix documentation link
2015-01-19 10:16:43 -08:00
Jakob Borg
d372435e92 Merge pull request #1264 from AudriusButkevicius/tempclean
Put temporary files in the OS temp directory (fixes #1239)
2015-01-19 10:12:32 -08:00
Audrius Butkevicius
a53facf709 Cleanup temporary files (fixes #1239) 2015-01-18 20:34:47 +00:00
Stefan Tatschner
ffc39dfbcb Fix documentation link 2015-01-18 18:17:55 +01:00
Audrius Butkevicius
cba38b15a9 Check for deleted files 2015-01-18 13:44:10 +00:00
Audrius Butkevicius
5ac7564bfe Merge pull request #1259 from calmh/check-folder-shared
Verify folder<->device permission in Request
2015-01-16 12:07:37 +00:00
Jakob Borg
53cd289b90 Verify folder<->device permission in Request
Requests from valid devices for valid folders should be rejected if the
folder is not shared with that device.
2015-01-16 12:50:51 +01:00
Jakob Borg
8dc13bcf1a go1.4.1 2015-01-16 10:18:54 +01:00
Audrius Butkevicius
261825a89b Merge pull request #1256 from calmh/fix-963
Adds File Versioning to folder info (fixes #963)
2015-01-15 17:25:28 +00:00
Jakob Borg
f47a5a309d Add File Versioning to folder info (fixes #963)
Could potentially use shorter value strings ("Simple" vs "Simple File
Versioning"), but this avoid introducing unnecessary strings to
translate - can always be changed in the future.
2015-01-15 15:50:49 +01:00
Jakob Borg
a40f2b9fa0 Merge pull request #1237 from syncthing/renamer
Efficient renames (fixes #1217)
2015-01-14 00:28:58 +01:00
Jakob Borg
cfcd3892f7 More concise changelog 2015-01-14 00:27:29 +01:00
Jakob Borg
703987f61c Changelog should read in chronological order from the top 2015-01-13 23:11:46 +01:00
Audrius Butkevicius
e50a8917ec Add renames to integration tests 2015-01-13 22:07:14 +00:00
Audrius Butkevicius
74d7c8e625 Efficient renames (fixes #1217) 2015-01-13 22:06:13 +00:00
Jakob Borg
a5d1383fe8 Translation update 2015-01-13 17:27:47 +01:00
Jakob Borg
bf2bcf515c Merge pull request #1247 from Rewt0r/master
Change Bottom Bar links to a new tab/window
2015-01-13 17:26:48 +01:00
Jakob Borg
4c5e94c64b Add Rewt0r 2015-01-13 17:25:51 +01:00
Jakob Borg
b4043216b6 Use bytes.Reader instead of bytes.Buffer for compiled in assets 2015-01-13 16:05:03 +01:00
Michael Jephcote
4371014667 Change Bottom Bar links to a new tab/window
Was getting kinda annoying clicking links in the footer and it navigating away from the page, I've added "_blank" as the target to those links.
2015-01-13 14:24:08 +00:00
Jakob Borg
fbb3222d29 Update dependencies 2015-01-13 13:55:35 +01:00
Audrius Butkevicius
4ca3889bed Merge pull request #1246 from syncthing/break-out-proto
Refactor out protocol and luhn (protocol dependency) packages
2015-01-13 12:37:46 +00:00
Jakob Borg
eef1aebe8c Refactor out protocol and luhn (protocol dependency) packages 2015-01-13 13:22:56 +01:00
Audrius Butkevicius
48382c4b59 Merge pull request #1245 from syncthing/indexclean-v2
Also filter out some other obviously invalid filenames (ref #1243)
2015-01-13 11:35:04 +00:00
Jakob Borg
9a45f0b31c Also filter out some other obviously invalid filenames (ref #1243) 2015-01-13 12:28:35 +01:00
Audrius Butkevicius
25c26e2f81 Merge pull request #1244 from syncthing/indexclean
Remove nil filenames from database and indexes (fixes #1243)
2015-01-13 08:36:49 +00:00
Jakob Borg
e4837f14b1 Remove nil filenames from database and indexes (fixes #1243) 2015-01-13 09:20:14 +01:00
Audrius Butkevicius
ce86131d12 Merge pull request #1242 from syncthing/rename-set
Renaming of package internal/files and type files.Set
2015-01-12 21:10:27 +00:00
Jakob Borg
e6c9baf6ef Rename db.Set to db.FileSet 2015-01-12 20:57:39 +01:00
Jakob Borg
8d6db7be31 Rename package internal/files to internal/db 2015-01-12 20:57:22 +01:00
Audrius Butkevicius
a2548b1fd0 Merge pull request #1241 from syncthing/fix-1143
Don't start a new refresh() loop on each UIOnline (fixes #1143)
2015-01-12 11:29:53 +00:00
Jakob Borg
e4658bb99d Don't start a new refresh() loop on each UIOnline (fixes #1143)
Separate out the stuff that should run on each UIOnline from the stuff
that should only run on init.
2015-01-12 12:15:58 +01:00
Jakob Borg
bf2e4a561a Changelog prints output on two lines per commit 2015-01-11 21:58:19 +01:00
Jakob Borg
1816320124 One more translation update 2015-01-11 21:19:42 +01:00
Jakob Borg
f09bfe293d Translation update 2015-01-11 20:31:30 +01:00
Jakob Borg
7b4e8fda4b Modal dialog titles must be manually translated 2015-01-11 14:48:40 +01:00
Audrius Butkevicius
c95812353f Merge pull request #1236 from syncthing/flag-safe
Reject Index and Request messages with unexpected flags
2015-01-11 12:40:40 +00:00
Jakob Borg
b622ec7a28 Reject Index and Request messages with unexpected flags 2015-01-11 13:29:01 +01:00
Jakob Borg
d8fbe7b77f Refactor readerLoop to switch on message type directly 2015-01-11 13:24:56 +01:00
Jakob Borg
dbcac37d91 Can run integration tests between different versions 2015-01-11 09:55:44 +01:00
Jakob Borg
d4d391b34f Change integration test "log" field to "instance" 2015-01-11 09:55:17 +01:00
Jakob Borg
571cf7d490 Merge pull request #1182 from AudriusButkevicius/autoauto
Connecting to a newer node triggers autoupgrade check (fixes #1177)
2015-01-11 09:16:12 +01:00
Jakob Borg
e18b19ca5a Translation base & assets update 2015-01-10 18:15:08 +01:00
Jakob Borg
48651bf482 Merge pull request #1234 from uok/master
Small improvements for footer navbar (ref #1154)
2015-01-10 18:14:47 +01:00
Audrius Butkevicius
5034a41c08 Connecting to a newer node triggers autoupgrade check (fixes #1177) 2015-01-10 17:05:19 +00:00
Ben Schulz
6795173e77 Small improvements for footer navbar 2015-01-10 18:02:27 +01:00
Jakob Borg
219ef996f5 Merge pull request #1226 from syncthing/deregister-fix
All roads lead to Finisher (fixes #1201)
2015-01-10 17:53:01 +01:00
Jakob Borg
00af1db275 Translation base & assets update 2015-01-10 17:51:18 +01:00
Jakob Borg
5935ea896f Merge pull request #1232 from facastagnini/master
"Quick guide to supported patterns" link updated
2015-01-10 17:50:23 +01:00
Jakob Borg
459983c05e Add facastagnini 2015-01-10 17:47:41 +01:00
Jakob Borg
ebf4f029ac Merge pull request #1229 from AudriusButkevicius/cfg-hasher
Make parallel hasher configurable, remove finisher setting (fixes #1199)
2015-01-10 17:45:15 +01:00
Jakob Borg
0eec945df1 Merge pull request #1230 from AudriusButkevicius/separator
Expose and use path separator (fixes #1163)
2015-01-10 17:43:41 +01:00
Jakob Borg
8824b9d68f Merge pull request #1231 from AudriusButkevicius/text
Rename "Last File Synced" to "Last File Received" (fixes #1145)
2015-01-10 17:43:06 +01:00
Jakob Borg
d2862814c5 Merge pull request #1233 from AudriusButkevicius/disco-log
Make discovery logging a bit better (fixes #1188)
2015-01-10 17:42:13 +01:00
Audrius Butkevicius
25fece2d50 Make discovery logging a bit better (fixes #1188) 2015-01-10 16:15:16 +00:00
Federico Castagnini
beb4239d1b The "Quick guide to supported patterns" link now points to the wiki article and will open in a new page/tab to avoid disrupting the settings page. 2015-01-10 10:42:45 -05:00
Audrius Butkevicius
2b78e37d92 Rename "Last File Synced" to "Last File Received" (fixes #1145)
IMHO this is more clear
2015-01-10 14:55:08 +00:00
Audrius Butkevicius
a2070d9ce4 Expose and use path separator (fixes #1163) 2015-01-10 14:51:29 +00:00
Audrius Butkevicius
5827a686b8 Make parallel hasher configurable, remove finisher setting (fixes #1199) 2015-01-10 14:32:20 +00:00
Audrius Butkevicius
dec479532e All roads lead to Finisher (fixes #1201) 2015-01-10 13:45:48 +00:00
Jakob Borg
5d173168cc Merge pull request #1214 from bigbear2nd/master
Added colored status indicator for narrow screens. (Fixes #1084)
2015-01-09 16:23:24 +01:00
bigbear2nd
2aac1cde04 Added colored status indicator for narrow screens. (Fixes #1084) 2015-01-09 20:54:00 +09:00
Audrius Butkevicius
3676f0268f Merge pull request #1220 from syncthing/arm-build
Only build ARMv5 (fixes #1218)
2015-01-09 10:25:50 +00:00
Audrius Butkevicius
a7b75a54bb Merge pull request #1219 from syncthing/refactor-truncated
Refactor stuff around FileInfoTruncated
2015-01-09 10:12:48 +00:00
Jakob Borg
961a87b743 Only build ARMv5 (fixes #1218)
With this change, the build system only builds one ARM variant - ARMv5.
We call the build architecture simply "arm", as this is what
runtime.GOARCH says.
2015-01-09 10:45:15 +01:00
Jakob Borg
e03d59e381 The protocol specs moved again 2015-01-09 08:54:19 +01:00
Jakob Borg
d46ce5003c Implement GetGlobalTruncated 2015-01-09 08:41:02 +01:00
Jakob Borg
4c4143d9be Move FileInfoTruncated to files package
This is where it's used, and it clarifies that it's never used over the
wire.
2015-01-09 08:28:24 +01:00
Jakob Borg
8bc7d259f4 Move FileIntf to files package, expose Iterator type
This is where FileIntf is used, so it should be defined here (it's not
a protocol thing, really).
2015-01-09 08:18:42 +01:00
Jakob Borg
2d047fa428 Remove unused types 2015-01-09 08:14:02 +01:00
Audrius Butkevicius
735d420d40 Merge pull request #1215 from syncthing/new-proto-bc
Add some new protocol fields
2015-01-08 21:54:01 +00:00
Jakob Borg
bc9fc1aece Actually close connection based on unknown protocol version 2015-01-08 22:11:26 +01:00
Jakob Borg
b88e3c99c1 Add fields for future extensibility
This adds a number of fields to the end of existing messages. This is a
backwards compatible change.
2015-01-08 22:11:26 +01:00
Jakob Borg
ce3e6e084c Ensure backwards compatibility before modifying protocol
This change makes sure that things work smoothly when "we" are a newer
version than our peer and have more fields in our messages than they do.
Missing fields will be left at zero/nil.

(The other side will ignore our extra fields, for the same effect.)
2015-01-08 14:25:11 +01:00
Jakob Borg
2a58ca7697 Merge pull request #1212 from cqcallaw/upnp
Properly handle absolute URLs when parsing UPnP service control URLs
2015-01-08 08:50:30 +01:00
Caleb Callaway
af96f7a0cd Properly handle absolute URLs when parsing UPnP service control URLs
Fixes #1187
2015-01-07 21:23:20 -08:00
Jakob Borg
7d39d1a925 Make it possible to include extra external files into binary packages 2015-01-07 16:15:50 +01:00
Jakob Borg
1b6c700e18 Merge remote-tracking branch 'origin/pr/1198'
* origin/pr/1198:
  Fix rendering issue on firefox when zoomed (fixes #1197)
2015-01-07 14:24:49 +01:00
Tim Abell
6304bd60ee Fix rendering issue on firefox when zoomed (fixes #1197)
issue #1197
2015-01-07 09:27:17 +00:00
Jakob Borg
4ad4417740 Add timabell 2015-01-07 08:36:54 +01:00
Jakob Borg
6a4c259a73 Merge pull request #1196 from AudriusButkevicius/finddevice
Add device finder utility
2015-01-07 08:31:54 +01:00
Audrius Butkevicius
12eabb220d Add device finder utility 2015-01-06 23:12:12 +00:00
Jakob Borg
d68ce2d68c Translation update 2015-01-06 23:12:40 +01:00
Jakob Borg
8e02c040eb Update key ID for signed releases in README (fixes #1180) 2015-01-06 23:06:16 +01:00
Jakob Borg
a7a317c284 The predictableRandom test can only run once successfully (fixes #1184) 2015-01-06 23:03:35 +01:00
Audrius Butkevicius
9d6ef24660 Merge pull request #1194 from syncthing/fix-1186
Use comma-ok idiom to signal files missing in database (fixes #1186)
2015-01-06 21:54:13 +00:00
Jakob Borg
14014408fb Merge pull request #1181 from kozec/stnoupgrade-disable-button
Return HTTP/500 from /rest/upgrade if STNOUPGRADE is defined
2015-01-06 22:54:08 +01:00
kozec
b933e9666a /rest/upgrade returns HTTP/500 if STNOUPGRADE is defined 2015-01-06 22:50:56 +01:00
Jakob Borg
7aff59bcce Add brendanlong 2015-01-06 22:48:01 +01:00
Jakob Borg
8e2760cb3d Merge pull request #1183 from brendanlong/fix-tests-on-go-1.3
Don't use Go 1.4 range syntax in queue_test.go
2015-01-06 22:47:23 +01:00
Brendan Long
7a9fc6dbd3 Don't use Go 1.4 range syntax in queue_test.go, since the listed requirement is Go 1.3. 2015-01-06 15:45:58 -06:00
Jakob Borg
75d0dc251e Use comma-ok idiom to signal files missing in database (fixes #1186)
Prevents us from doing stupid things to the folder root (empty file
path) when nodes disconnect...
2015-01-06 22:40:20 +01:00
Jakob Borg
9a50c4d93f Don't unnecessarily chmod directories when renaming 2015-01-06 22:10:44 +01:00
Audrius Butkevicius
010d5a0192 Merge pull request #1179 from syncthing/httperror
Handle HTTP errors on non-event requests (fixes #1120)
2015-01-05 17:45:18 +00:00
Jakob Borg
cf1594829a Handle HTTP errors on non-event requests (fixes #1120, fixes #807) 2015-01-05 16:03:00 +01:00
Jakob Borg
854d720ce0 Merge pr/988
* commit 'b9817ac':
  add README
  on-failure instead of always as we cannot otherwise kill the service
  systemd units for system/user
2015-01-05 15:14:33 +01:00
Jakob Borg
2f43c74ece Add peterhoeg 2015-01-05 15:14:22 +01:00
Peter Hoeg
b9817ac6b4 add README 2015-01-05 18:29:13 +08:00
Peter Hoeg
1e8da0d494 on-failure instead of always as we cannot otherwise kill the service 2015-01-05 18:29:13 +08:00
Peter Hoeg
c47be7b415 systemd units for system/user 2015-01-05 18:29:13 +08:00
155 changed files with 3579 additions and 2609 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
syncthing
./syncthing
syncthing.exe
*.tar.gz
*.zip

View File

@@ -8,6 +8,7 @@ Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
Ben Sidhom <bsidhom@gmail.com>
Brandon Philips <brandon@ifup.org>
Brendan Long <self@brendanlong.com>
Caleb Callaway <enlightened.despot@gmail.com>
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
Chris Joel <chris@scriptolo.gy>
@@ -15,6 +16,7 @@ Daniel Martí <mvdan@mvdan.cc>
Dennis Wilson <dw@risu.io>
Dominik Heidler <dominik@heidler.eu>
Emil Hessman <emil@hessman.se>
Federico Castagnini <federico.castagnini@gmail.com>
Felix Ableitner <me@nutomic.com>
Felix Unterpaintner <bigbear2nd@gmail.com>
Gilli Sigurdsson <gilli@vx.is>
@@ -22,13 +24,18 @@ Jakob Borg <jakob@nym.se>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
Jochen Voss <voss@seehuhn.de>
Karol Różycki <rozycki.karol@gmail.com>
Lode Hoste <zillode@zillode.be>
Marcin Dziadus <dziadus.marcin@gmail.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com>
Peter Hoeg <peter@speartail.com>
Philippe Schommers <philippe@schommers.be>
Phill Luby <phill.luby@newredo.com>
Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Stefan Tatschner <stefan@sevenbyte.org>
Tim Abell <tim@timwise.co.uk>
Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org>
Veeti Paananen <veeti.paananen@rojekti.fi>

View File

@@ -33,8 +33,8 @@ latest info on Transifex.
Every contribution is welcome. If you want to contribute but are unsure
where to start, any open issues are fair game! Be prepared for a
[certain amount of review](https://discourse.syncthing.net/t/733); it's
all in the name of quality. :) Following the points below will make this
[certain amount of review](https://github.com/syncthing/syncthing/wiki/FAQ#why-are-you-being-so-hard-on-my-pull-request);
it's all in the name of quality. :) Following the points below will make this
a smoother process.
Individuals making significant and valuable contributions are given
@@ -105,8 +105,8 @@ add yourself as a separate commit in your first pull request.
## Building
[See the documentation](http://discourse.syncthing.net/t/44) on how to
get started with a build environment.
[See the documentation](https://github.com/syncthing/syncthing/wiki/Building)
on how to get started with a build environment.
## Branches
@@ -133,7 +133,7 @@ Yes please!
## Documentation
[Over here!](http://discourse.syncthing.net/category/documentation)
[Over here!](https://github.com/syncthing/syncthing/wiki)
## License

22
Godeps/Godeps.json generated
View File

@@ -5,10 +5,6 @@
"./cmd/..."
],
"Deps": [
{
"ImportPath": "github.com/AudriusButkevicius/lfu-go",
"Rev": "164bcecceb92fd6037f4d18a8d97b495ec6ef669"
},
{
"ImportPath": "github.com/bkaradzic/go-lz4",
"Rev": "93a831dcee242be64a9cc9803dda84af25932de7"
@@ -17,18 +13,26 @@
"ImportPath": "github.com/calmh/logger",
"Rev": "f50d32b313bec2933a3e1049f7416a29f3413d29"
},
{
"ImportPath": "github.com/calmh/luhn",
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
},
{
"ImportPath": "github.com/calmh/osext",
"Rev": "9bf61584e5f1f172e8766ddc9022d9c401faaa5e"
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "45c46b7db7ff83b8b9ee09bbd95f36ab50043ece"
"Rev": "ff948d7666c5e0fd18d398f6278881724d36a90b"
},
{
"ImportPath": "github.com/juju/ratelimit",
"Rev": "f9f36d11773655c0485207f0ad30dc2655f69d56"
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "442e93d3fc7728d7560c8d039a4199a77879b9f6"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "63c9e642efad852f49e20a6f90194cae112fd2ac"
@@ -51,19 +55,19 @@
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "731db29863ea7213d9556d0170afb38987f401d4"
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "731db29863ea7213d9556d0170afb38987f401d4"
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/text/transform",
"Rev": "985ee5acfaf1ff6712c7c99438752f8e09416ccb"
"Rev": "c980adc4a823548817b9c47d38c6ca6b7d7d8b6a"
},
{
"ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "985ee5acfaf1ff6712c7c99438752f8e09416ccb"
"Rev": "c980adc4a823548817b9c47d38c6ca6b7d7d8b6a"
}
]
}

View File

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

View File

@@ -1,19 +0,0 @@
A simple LFU cache for golang. Based on the paper [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf).
Usage:
```go
import "github.com/dgrijalva/lfu-go"
// Make a new thing
c := lfu.New()
// Set some values
c.Set("myKey", myValue)
// Retrieve some values
myValue = c.Get("myKey")
// Evict some values
c.Evict(1)
```

View File

@@ -1,156 +0,0 @@
package lfu
import (
"container/list"
"sync"
)
type Eviction struct {
Key string
Value interface{}
}
type Cache struct {
// If len > UpperBound, cache will automatically evict
// down to LowerBound. If either value is 0, this behavior
// is disabled.
UpperBound int
LowerBound int
values map[string]*cacheEntry
freqs *list.List
len int
lock *sync.Mutex
EvictionChannel chan<- Eviction
}
type cacheEntry struct {
key string
value interface{}
freqNode *list.Element
}
type listEntry struct {
entries map[*cacheEntry]byte
freq int
}
func New() *Cache {
c := new(Cache)
c.values = make(map[string]*cacheEntry)
c.freqs = list.New()
c.lock = new(sync.Mutex)
return c
}
func (c *Cache) Get(key string) interface{} {
c.lock.Lock()
defer c.lock.Unlock()
if e, ok := c.values[key]; ok {
c.increment(e)
return e.value
}
return nil
}
func (c *Cache) Set(key string, value interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if e, ok := c.values[key]; ok {
// value already exists for key. overwrite
e.value = value
c.increment(e)
} else {
// value doesn't exist. insert
e := new(cacheEntry)
e.key = key
e.value = value
c.values[key] = e
c.increment(e)
c.len++
// bounds mgmt
if c.UpperBound > 0 && c.LowerBound > 0 {
if c.len > c.UpperBound {
c.evict(c.len - c.LowerBound)
}
}
}
}
func (c *Cache) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
return c.len
}
func (c *Cache) Evict(count int) int {
c.lock.Lock()
defer c.lock.Unlock()
return c.evict(count)
}
func (c *Cache) evict(count int) int {
// No lock here so it can be called
// from within the lock (during Set)
var evicted int
for i := 0; i < count; {
if place := c.freqs.Front(); place != nil {
for entry, _ := range place.Value.(*listEntry).entries {
if i < count {
if c.EvictionChannel != nil {
c.EvictionChannel <- Eviction{
Key: entry.key,
Value: entry.value,
}
}
delete(c.values, entry.key)
c.remEntry(place, entry)
evicted++
c.len--
i++
}
}
}
}
return evicted
}
func (c *Cache) increment(e *cacheEntry) {
currentPlace := e.freqNode
var nextFreq int
var nextPlace *list.Element
if currentPlace == nil {
// new entry
nextFreq = 1
nextPlace = c.freqs.Front()
} else {
// move up
nextFreq = currentPlace.Value.(*listEntry).freq + 1
nextPlace = currentPlace.Next()
}
if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq {
// create a new list entry
li := new(listEntry)
li.freq = nextFreq
li.entries = make(map[*cacheEntry]byte)
if currentPlace != nil {
nextPlace = c.freqs.InsertAfter(li, currentPlace)
} else {
nextPlace = c.freqs.PushFront(li)
}
}
e.freqNode = nextPlace
nextPlace.Value.(*listEntry).entries[e] = 1
if currentPlace != nil {
// remove from current position
c.remEntry(currentPlace, e)
}
}
func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) {
entries := place.Value.(*listEntry).entries
delete(entries, entry)
if len(entries) == 0 {
c.freqs.Remove(place)
}
}

View File

@@ -1,68 +0,0 @@
package lfu
import (
"fmt"
"testing"
)
func TestLFU(t *testing.T) {
c := New()
c.Set("a", "a")
if v := c.Get("a"); v != "a" {
t.Errorf("Value was not saved: %v != 'a'", v)
}
if l := c.Len(); l != 1 {
t.Errorf("Length was not updated: %v != 1", l)
}
c.Set("b", "b")
if v := c.Get("b"); v != "b" {
t.Errorf("Value was not saved: %v != 'b'", v)
}
if l := c.Len(); l != 2 {
t.Errorf("Length was not updated: %v != 2", l)
}
c.Get("a")
evicted := c.Evict(1)
if v := c.Get("a"); v != "a" {
t.Errorf("Value was improperly evicted: %v != 'a'", v)
}
if v := c.Get("b"); v != nil {
t.Errorf("Value was not evicted: %v", v)
}
if l := c.Len(); l != 1 {
t.Errorf("Length was not updated: %v != 1", l)
}
if evicted != 1 {
t.Errorf("Number of evicted items is wrong: %v != 1", evicted)
}
}
func TestBoundsMgmt(t *testing.T) {
c := New()
c.UpperBound = 10
c.LowerBound = 5
for i := 0; i < 100; i++ {
c.Set(fmt.Sprintf("%v", i), i)
}
if c.Len() > 10 {
t.Errorf("Bounds management failed to evict properly: %v", c.Len())
}
}
func TestEviction(t *testing.T) {
ch := make(chan Eviction, 1)
c := New()
c.EvictionChannel = ch
c.Set("a", "b")
c.Evict(1)
ev := <-ch
if ev.Key != "a" || ev.Value.(string) != "b" {
t.Error("Incorrect item")
}
}

View File

@@ -1,194 +1,194 @@
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package lz4
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrCorrupt indicates the input was corrupt
ErrCorrupt = errors.New("corrupt input")
)
const (
mlBits = 4
mlMask = (1 << mlBits) - 1
runBits = 8 - mlBits
runMask = (1 << runBits) - 1
)
type decoder struct {
src []byte
dst []byte
spos uint32
dpos uint32
ref uint32
}
func (d *decoder) readByte() (uint8, error) {
if int(d.spos) == len(d.src) {
return 0, io.EOF
}
b := d.src[d.spos]
d.spos++
return b, nil
}
func (d *decoder) getLen() (uint32, error) {
length := uint32(0)
ln, err := d.readByte()
if err != nil {
return 0, ErrCorrupt
}
for ln == 255 {
length += 255
ln, err = d.readByte()
if err != nil {
return 0, ErrCorrupt
}
}
length += uint32(ln)
return length, nil
}
func (d *decoder) cp(length, decr uint32) {
if int(d.ref+length) < int(d.dpos) {
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
} else {
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
}
}
d.dpos += length
d.ref += length - decr
}
func (d *decoder) finish(err error) error {
if err == io.EOF {
return nil
}
return err
}
// Decode returns the decoded form of src. The returned slice may be a
// subslice of dst if it was large enough to hold the entire decoded block.
func Decode(dst, src []byte) ([]byte, error) {
if len(src) < 4 {
return nil, ErrCorrupt
}
uncompressedLen := binary.LittleEndian.Uint32(src)
if uncompressedLen == 0 {
return nil, nil
}
if uncompressedLen > MaxInputSize {
return nil, ErrTooLarge
}
if dst == nil || len(dst) < int(uncompressedLen) {
dst = make([]byte, uncompressedLen)
}
d := decoder{src: src, dst: dst[:uncompressedLen], spos: 4}
decr := []uint32{0, 3, 2, 3}
for {
code, err := d.readByte()
if err != nil {
return d.dst, d.finish(err)
}
length := uint32(code >> mlBits)
if length == runMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
if int(d.spos+length) > len(d.src) {
return nil, ErrCorrupt
}
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.src[d.spos+ii]
}
d.spos += length
d.dpos += length
if int(d.spos) == len(d.src) {
return d.dst, nil
}
if int(d.spos+2) >= len(d.src) {
return nil, ErrCorrupt
}
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
if back > d.dpos {
return nil, ErrCorrupt
}
d.spos += 2
d.ref = d.dpos - back
length = uint32(code & mlMask)
if length == mlMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
literal := d.dpos - d.ref
if literal < 4 {
d.cp(4, decr[literal])
} else {
length += 4
}
if d.dpos+length > uncompressedLen {
return nil, ErrCorrupt
}
d.cp(length, 0)
}
}
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package lz4
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrCorrupt indicates the input was corrupt
ErrCorrupt = errors.New("corrupt input")
)
const (
mlBits = 4
mlMask = (1 << mlBits) - 1
runBits = 8 - mlBits
runMask = (1 << runBits) - 1
)
type decoder struct {
src []byte
dst []byte
spos uint32
dpos uint32
ref uint32
}
func (d *decoder) readByte() (uint8, error) {
if int(d.spos) == len(d.src) {
return 0, io.EOF
}
b := d.src[d.spos]
d.spos++
return b, nil
}
func (d *decoder) getLen() (uint32, error) {
length := uint32(0)
ln, err := d.readByte()
if err != nil {
return 0, ErrCorrupt
}
for ln == 255 {
length += 255
ln, err = d.readByte()
if err != nil {
return 0, ErrCorrupt
}
}
length += uint32(ln)
return length, nil
}
func (d *decoder) cp(length, decr uint32) {
if int(d.ref+length) < int(d.dpos) {
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
} else {
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
}
}
d.dpos += length
d.ref += length - decr
}
func (d *decoder) finish(err error) error {
if err == io.EOF {
return nil
}
return err
}
// Decode returns the decoded form of src. The returned slice may be a
// subslice of dst if it was large enough to hold the entire decoded block.
func Decode(dst, src []byte) ([]byte, error) {
if len(src) < 4 {
return nil, ErrCorrupt
}
uncompressedLen := binary.LittleEndian.Uint32(src)
if uncompressedLen == 0 {
return nil, nil
}
if uncompressedLen > MaxInputSize {
return nil, ErrTooLarge
}
if dst == nil || len(dst) < int(uncompressedLen) {
dst = make([]byte, uncompressedLen)
}
d := decoder{src: src, dst: dst[:uncompressedLen], spos: 4}
decr := []uint32{0, 3, 2, 3}
for {
code, err := d.readByte()
if err != nil {
return d.dst, d.finish(err)
}
length := uint32(code >> mlBits)
if length == runMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
if int(d.spos+length) > len(d.src) {
return nil, ErrCorrupt
}
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.src[d.spos+ii]
}
d.spos += length
d.dpos += length
if int(d.spos) == len(d.src) {
return d.dst, nil
}
if int(d.spos+2) >= len(d.src) {
return nil, ErrCorrupt
}
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
if back > d.dpos {
return nil, ErrCorrupt
}
d.spos += 2
d.ref = d.dpos - back
length = uint32(code & mlMask)
if length == mlMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
literal := d.dpos - d.ref
if literal < 4 {
d.cp(4, decr[literal])
} else {
length += 4
}
if d.dpos+length > uncompressedLen {
return nil, ErrCorrupt
}
d.cp(length, 0)
}
}

View File

@@ -1,188 +1,188 @@
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package lz4
import "encoding/binary"
import "errors"
const (
minMatch = 4
hashLog = 17
hashTableSize = 1 << hashLog
hashShift = (minMatch * 8) - hashLog
incompressible uint32 = 128
uninitHash = 0x88888888
// MaxInputSize is the largest buffer than can be compressed in a single block
MaxInputSize = 0x7E000000
)
var (
// ErrTooLarge indicates the input buffer was too large
ErrTooLarge = errors.New("input too large")
)
type encoder struct {
src []byte
dst []byte
hashTable []uint32
pos uint32
anchor uint32
dpos uint32
}
// CompressBound returns the maximum length of a lz4 block, given it's uncompressed length
func CompressBound(isize int) int {
if isize > MaxInputSize {
return 0
}
return isize + ((isize) / 255) + 16 + 4
}
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
ln := length
var code byte
if ln > runMask-1 {
code = runMask
} else {
code = byte(ln)
}
if mlLen > mlMask-1 {
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
} else {
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
}
e.dpos++
if code == runMask {
ln -= runMask
for ; ln > 254; ln -= 255 {
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(ln)
e.dpos++
}
for ii := uint32(0); ii < length; ii++ {
e.dst[e.dpos+ii] = e.src[pos+ii]
}
e.dpos += length
}
// Encode returns the encoded form of src. The returned array may be a
// sub-slice of dst if it was large enough to hold the entire output.
func Encode(dst, src []byte) ([]byte, error) {
if len(src) >= MaxInputSize {
return nil, ErrTooLarge
}
if n := CompressBound(len(src)); len(dst) < n {
dst = make([]byte, n)
}
e := encoder{src: src, dst: dst, hashTable: make([]uint32, hashTableSize)}
binary.LittleEndian.PutUint32(dst, uint32(len(src)))
e.dpos = 4
var (
step uint32 = 1
limit = incompressible
)
for {
if int(e.pos)+12 >= len(e.src) {
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
return e.dst[:e.dpos], nil
}
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
hash := (sequence * 2654435761) >> hashShift
ref := e.hashTable[hash] + uninitHash
e.hashTable[hash] = e.pos - uninitHash
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
if e.pos-e.anchor > limit {
limit <<= 1
step += 1 + (step >> 2)
}
e.pos += step
continue
}
if step > 1 {
e.hashTable[hash] = ref - uninitHash
e.pos -= step - 1
step = 1
continue
}
limit = incompressible
ln := e.pos - e.anchor
back := e.pos - ref
anchor := e.anchor
e.pos += minMatch
ref += minMatch
e.anchor = e.pos
for int(e.pos) < len(e.src)-5 && e.src[e.pos] == e.src[ref] {
e.pos++
ref++
}
mlLen := e.pos - e.anchor
e.writeLiterals(ln, mlLen, anchor)
e.dst[e.dpos] = uint8(back)
e.dst[e.dpos+1] = uint8(back >> 8)
e.dpos += 2
if mlLen > mlMask-1 {
mlLen -= mlMask
for mlLen > 254 {
mlLen -= 255
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(mlLen)
e.dpos++
}
e.anchor = e.pos
}
}
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package lz4
import "encoding/binary"
import "errors"
const (
minMatch = 4
hashLog = 17
hashTableSize = 1 << hashLog
hashShift = (minMatch * 8) - hashLog
incompressible uint32 = 128
uninitHash = 0x88888888
// MaxInputSize is the largest buffer than can be compressed in a single block
MaxInputSize = 0x7E000000
)
var (
// ErrTooLarge indicates the input buffer was too large
ErrTooLarge = errors.New("input too large")
)
type encoder struct {
src []byte
dst []byte
hashTable []uint32
pos uint32
anchor uint32
dpos uint32
}
// CompressBound returns the maximum length of a lz4 block, given it's uncompressed length
func CompressBound(isize int) int {
if isize > MaxInputSize {
return 0
}
return isize + ((isize) / 255) + 16 + 4
}
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
ln := length
var code byte
if ln > runMask-1 {
code = runMask
} else {
code = byte(ln)
}
if mlLen > mlMask-1 {
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
} else {
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
}
e.dpos++
if code == runMask {
ln -= runMask
for ; ln > 254; ln -= 255 {
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(ln)
e.dpos++
}
for ii := uint32(0); ii < length; ii++ {
e.dst[e.dpos+ii] = e.src[pos+ii]
}
e.dpos += length
}
// Encode returns the encoded form of src. The returned array may be a
// sub-slice of dst if it was large enough to hold the entire output.
func Encode(dst, src []byte) ([]byte, error) {
if len(src) >= MaxInputSize {
return nil, ErrTooLarge
}
if n := CompressBound(len(src)); len(dst) < n {
dst = make([]byte, n)
}
e := encoder{src: src, dst: dst, hashTable: make([]uint32, hashTableSize)}
binary.LittleEndian.PutUint32(dst, uint32(len(src)))
e.dpos = 4
var (
step uint32 = 1
limit = incompressible
)
for {
if int(e.pos)+12 >= len(e.src) {
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
return e.dst[:e.dpos], nil
}
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
hash := (sequence * 2654435761) >> hashShift
ref := e.hashTable[hash] + uninitHash
e.hashTable[hash] = e.pos - uninitHash
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
if e.pos-e.anchor > limit {
limit <<= 1
step += 1 + (step >> 2)
}
e.pos += step
continue
}
if step > 1 {
e.hashTable[hash] = ref - uninitHash
e.pos -= step - 1
step = 1
continue
}
limit = incompressible
ln := e.pos - e.anchor
back := e.pos - ref
anchor := e.anchor
e.pos += minMatch
ref += minMatch
e.anchor = e.pos
for int(e.pos) < len(e.src)-5 && e.src[e.pos] == e.src[ref] {
e.pos++
ref++
}
mlLen := e.pos - e.anchor
e.writeLiterals(ln, mlLen, anchor)
e.dst[e.dpos] = uint8(back)
e.dst[e.dpos+1] = uint8(back >> 8)
e.dpos += 2
if mlLen > mlMask-1 {
mlLen -= mlMask
for mlLen > 254 {
mlLen -= 255
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(mlLen)
e.dpos++
}
e.anchor = e.pos
}
}

19
Godeps/_workspace/src/github.com/calmh/luhn/LICENSE generated vendored Normal file
View File

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

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 Jakob Borg
// Package luhn generates and validates Luhn mod N check digits.
package luhn

View File

@@ -1,24 +1,11 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 Jakob Borg
package luhn_test
import (
"testing"
"github.com/syncthing/syncthing/internal/luhn"
"github.com/calmh/luhn"
)
func TestGenerate(t *testing.T) {

View File

@@ -321,10 +321,10 @@ func generateDiagram(output io.Writer, s structInfo) {
case "bool":
fmt.Fprintf(output, "| %s |V|\n", center(name+" (V=0 or 1)", 59))
fmt.Fprintln(output, line)
case "uint16":
case "int16", "uint16":
fmt.Fprintf(output, "| %s | %s |\n", center("0x0000", 29), center(name, 29))
fmt.Fprintln(output, line)
case "uint32":
case "int32", "uint32":
fmt.Fprintf(output, "| %s |\n", center(name, 61))
fmt.Fprintln(output, line)
case "int64", "uint64":
@@ -374,6 +374,8 @@ func generateXdr(output io.Writer, s structInfo) {
}
switch tn {
case "int16", "int32":
fmt.Fprintf(output, "\tint %s%s;\n", fn, suf)
case "uint16", "uint32":
fmt.Fprintf(output, "\tunsigned int %s%s;\n", fn, suf)
case "int64":

View File

@@ -154,6 +154,10 @@ func (e XDRError) Error() string {
return "xdr " + e.op + ": " + e.err.Error()
}
func (e XDRError) IsEOF() bool {
return e.err == io.EOF
}
func (r *Reader) Error() error {
if r.err == nil {
return nil

View File

@@ -0,0 +1,4 @@
# This is the official list of Protocol Authors for copyright purposes.
Audrius Butkevicius <audrius.butkevicius@gmail.com>
Jakob Borg <jakob@nym.se>

View File

@@ -0,0 +1,76 @@
## Reporting Bugs
Please file bugs in the [Github Issue
Tracker](https://github.com/syncthing/protocol/issues).
## Contributing Code
Every contribution is welcome. Following the points below will make this
a smoother process.
Individuals making significant and valuable contributions are given
commit-access to the project. If you make a significant contribution and
are not considered for commit-access, please contact any of the
Syncthing core team members.
All nontrivial contributions should go through the pull request
mechanism for internal review. Determining what is "nontrivial" is left
at the discretion of the contributor.
### Authorship
All code authors are listed in the AUTHORS file. Commits must be made
with the same name and email as listed in the AUTHORS file. To
accomplish this, ensure that your git configuration is set correctly
prior to making your first commit;
$ git config --global user.name "Jane Doe"
$ git config --global user.email janedoe@example.com
You must be reachable on the given email address. If you do not wish to
use your real name for whatever reason, using a nickname or pseudonym is
perfectly acceptable.
## Coding Style
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
as much as makes sense.
- All text files use Unix line endings.
- Each commit should be `go fmt` clean.
- The commit message subject should be a single short sentence
describing the change, starting with a capital letter.
- Commits that resolve an existing issue must include the issue number
as `(fixes #123)` at the end of the commit message subject.
- Imports are grouped per `goimports` standard; that is, standard
library first, then third party libraries after a blank line.
- A contribution solving a single issue or introducing a single new
feature should probably be a single commit based on the current
`master` branch. You may be asked to "rebase" or "squash" your pull
request to make sure this is the case, especially if there have been
amendments during review.
## Licensing
All contributions are made under the same MIT license as the rest of the
project, except documentation, user interface text and translation
strings which are licensed under the Creative Commons Attribution 4.0
International License. You retain the copyright to code you have
written.
When accepting your first contribution, the maintainer of the project
will ensure that you are added to the AUTHORS file. You are welcome to
add yourself as a separate commit in your first pull request.
## Tests
Yes please!
## License
MIT

View File

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

View File

@@ -0,0 +1,13 @@
The BEPv1 Protocol
==================
[![Latest Build](http://img.shields.io/jenkins/s/http/build.syncthing.net/protocol.svg?style=flat-square)](http://build.syncthing.net/job/protocol/lastBuild/)
[![API Documentation](http://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](http://godoc.org/github.com/syncthing/protocol)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](http://opensource.org/licenses/MIT)
This is the protocol implementation used by Syncthing.
License
=======
MIT

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2014 The Protocol Authors.
package protocol
import (
"io"
"sync/atomic"
"time"
)
type countingReader struct {
io.Reader
tot int64 // bytes
last int64 // unix nanos
}
var (
totalIncoming int64
totalOutgoing int64
)
func (c *countingReader) Read(bs []byte) (int, error) {
n, err := c.Reader.Read(bs)
atomic.AddInt64(&c.tot, int64(n))
atomic.AddInt64(&totalIncoming, int64(n))
atomic.StoreInt64(&c.last, time.Now().UnixNano())
return n, err
}
func (c *countingReader) Tot() int64 {
return atomic.LoadInt64(&c.tot)
}
func (c *countingReader) Last() time.Time {
return time.Unix(0, atomic.LoadInt64(&c.last))
}
type countingWriter struct {
io.Writer
tot int64 // bytes
last int64 // unix nanos
}
func (c *countingWriter) Write(bs []byte) (int, error) {
n, err := c.Writer.Write(bs)
atomic.AddInt64(&c.tot, int64(n))
atomic.AddInt64(&totalOutgoing, int64(n))
atomic.StoreInt64(&c.last, time.Now().UnixNano())
return n, err
}
func (c *countingWriter) Tot() int64 {
return atomic.LoadInt64(&c.tot)
}
func (c *countingWriter) Last() time.Time {
return time.Unix(0, atomic.LoadInt64(&c.last))
}
func TotalInOut() (int64, int64) {
return atomic.LoadInt64(&totalIncoming), atomic.LoadInt64(&totalOutgoing)
}

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2014 The Protocol Authors.
package protocol
import (
"os"
"strings"
"github.com/calmh/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "protocol") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol
@@ -24,7 +11,7 @@ import (
"regexp"
"strings"
"github.com/syncthing/syncthing/internal/luhn"
"github.com/calmh/luhn"
)
type DeviceID [32]byte

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol

View File

@@ -0,0 +1,4 @@
// Copyright (C) 2014 The Protocol Authors.
// Package protocol implements the Block Exchange Protocol.
package protocol

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol

View File

@@ -0,0 +1,122 @@
// Copyright (C) 2014 The Protocol Authors.
//go:generate genxdr -o message_xdr.go message.go
package protocol
import "fmt"
type IndexMessage struct {
Folder string // max:64
Files []FileInfo
Flags uint32
Options []Option // max:64
}
type FileInfo struct {
Name string // max:8192
Flags uint32
Modified int64
Version int64
LocalVersion int64
Blocks []BlockInfo
}
func (f FileInfo) String() string {
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, Blocks:%v}",
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.Blocks)
}
func (f FileInfo) Size() (bytes int64) {
if f.IsDeleted() || f.IsDirectory() {
return 128
}
for _, b := range f.Blocks {
bytes += int64(b.Size)
}
return
}
func (f FileInfo) IsDeleted() bool {
return f.Flags&FlagDeleted != 0
}
func (f FileInfo) IsInvalid() bool {
return f.Flags&FlagInvalid != 0
}
func (f FileInfo) IsDirectory() bool {
return f.Flags&FlagDirectory != 0
}
func (f FileInfo) IsSymlink() bool {
return f.Flags&FlagSymlink != 0
}
func (f FileInfo) HasPermissionBits() bool {
return f.Flags&FlagNoPermBits == 0
}
type BlockInfo struct {
Offset int64 // noencode (cache only)
Size int32
Hash []byte // max:64
}
func (b BlockInfo) String() string {
return fmt.Sprintf("Block{%d/%d/%x}", b.Offset, b.Size, b.Hash)
}
type RequestMessage struct {
Folder string // max:64
Name string // max:8192
Offset int64
Size int32
Hash []byte // max:64
Flags uint32
Options []Option // max:64
}
type ResponseMessage struct {
Data []byte
Error int32
}
type ClusterConfigMessage struct {
ClientName string // max:64
ClientVersion string // max:64
Folders []Folder // max:64
Options []Option // max:64
}
func (o *ClusterConfigMessage) GetOption(key string) string {
for _, option := range o.Options {
if option.Key == key {
return option.Value
}
}
return ""
}
type Folder struct {
ID string // max:64
Devices []Device
}
type Device struct {
ID []byte // max:32
Flags uint32
MaxLocalVersion int64
}
type Option struct {
Key string // max:64
Value string // max:1024
}
type CloseMessage struct {
Reason string // max:1024
Code int32
}
type EmptyMessage struct{}

View File

@@ -30,11 +30,21 @@ IndexMessage Structure:
\ Zero or more FileInfo Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Options |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Option Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct IndexMessage {
string Folder<64>;
FileInfo Files<>;
unsigned int Flags;
Option Options<64>;
}
*/
@@ -75,6 +85,17 @@ func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), err
}
}
xw.WriteUint32(o.Flags)
if l := len(o.Options); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
}
return xw.Tot(), xw.Error()
}
@@ -96,6 +117,15 @@ func (o *IndexMessage) decodeXDR(xr *xdr.Reader) error {
for i := range o.Files {
(&o.Files[i]).decodeXDR(xr)
}
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -138,8 +168,8 @@ struct FileInfo {
string Name<8192>;
unsigned int Flags;
hyper Modified;
unsigned hyper Version;
unsigned hyper LocalVersion;
hyper Version;
hyper LocalVersion;
BlockInfo Blocks<>;
}
@@ -176,8 +206,8 @@ func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteString(o.Name)
xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified))
xw.WriteUint64(o.Version)
xw.WriteUint64(o.LocalVersion)
xw.WriteUint64(uint64(o.Version))
xw.WriteUint64(uint64(o.LocalVersion))
xw.WriteUint32(uint32(len(o.Blocks)))
for i := range o.Blocks {
_, err := o.Blocks[i].encodeXDR(xw)
@@ -203,8 +233,8 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
o.Name = xr.ReadStringMax(8192)
o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64())
o.Version = xr.ReadUint64()
o.LocalVersion = xr.ReadUint64()
o.Version = int64(xr.ReadUint64())
o.LocalVersion = int64(xr.ReadUint64())
_BlocksSize := int(xr.ReadUint32())
o.Blocks = make([]BlockInfo, _BlocksSize)
for i := range o.Blocks {
@@ -215,106 +245,6 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
/*
FileInfoTruncated Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Modified (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Local Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Num Blocks |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct FileInfoTruncated {
string Name<8192>;
unsigned int Flags;
hyper Modified;
unsigned hyper Version;
unsigned hyper LocalVersion;
unsigned int NumBlocks;
}
*/
func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.encodeXDR(xw)
}
func (o FileInfoTruncated) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o FileInfoTruncated) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o FileInfoTruncated) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Name); l > 8192 {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
}
xw.WriteString(o.Name)
xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified))
xw.WriteUint64(o.Version)
xw.WriteUint64(o.LocalVersion)
xw.WriteUint32(o.NumBlocks)
return xw.Tot(), xw.Error()
}
func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.decodeXDR(xr)
}
func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.decodeXDR(xr)
}
func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error {
o.Name = xr.ReadStringMax(8192)
o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64())
o.Version = xr.ReadUint64()
o.LocalVersion = xr.ReadUint64()
o.NumBlocks = xr.ReadUint32()
return xr.Error()
}
/*
BlockInfo Structure:
0 1 2 3
@@ -331,7 +261,7 @@ BlockInfo Structure:
struct BlockInfo {
unsigned int Size;
int Size;
opaque Hash<64>;
}
@@ -362,7 +292,7 @@ func (o BlockInfo) AppendXDR(bs []byte) ([]byte, error) {
}
func (o BlockInfo) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteUint32(o.Size)
xw.WriteUint32(uint32(o.Size))
if l := len(o.Hash); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
}
@@ -382,7 +312,7 @@ func (o *BlockInfo) UnmarshalXDR(bs []byte) error {
}
func (o *BlockInfo) decodeXDR(xr *xdr.Reader) error {
o.Size = xr.ReadUint32()
o.Size = int32(xr.ReadUint32())
o.Hash = xr.ReadBytesMax(64)
return xr.Error()
}
@@ -412,13 +342,30 @@ RequestMessage Structure:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Hash |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Hash (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Options |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Option Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct RequestMessage {
string Folder<64>;
string Name<8192>;
unsigned hyper Offset;
unsigned int Size;
hyper Offset;
int Size;
opaque Hash<64>;
unsigned int Flags;
Option Options<64>;
}
*/
@@ -456,8 +403,23 @@ func (o RequestMessage) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
}
xw.WriteString(o.Name)
xw.WriteUint64(o.Offset)
xw.WriteUint32(o.Size)
xw.WriteUint64(uint64(o.Offset))
xw.WriteUint32(uint32(o.Size))
if l := len(o.Hash); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
}
xw.WriteBytes(o.Hash)
xw.WriteUint32(o.Flags)
if l := len(o.Options); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
}
return xw.Tot(), xw.Error()
}
@@ -475,8 +437,18 @@ func (o *RequestMessage) UnmarshalXDR(bs []byte) error {
func (o *RequestMessage) decodeXDR(xr *xdr.Reader) error {
o.Folder = xr.ReadStringMax(64)
o.Name = xr.ReadStringMax(8192)
o.Offset = xr.ReadUint64()
o.Size = xr.ReadUint32()
o.Offset = int64(xr.ReadUint64())
o.Size = int32(xr.ReadUint32())
o.Hash = xr.ReadBytesMax(64)
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -493,10 +465,13 @@ ResponseMessage Structure:
\ Data (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Error |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct ResponseMessage {
opaque Data<>;
int Error;
}
*/
@@ -527,6 +502,7 @@ func (o ResponseMessage) AppendXDR(bs []byte) ([]byte, error) {
func (o ResponseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteBytes(o.Data)
xw.WriteUint32(uint32(o.Error))
return xw.Tot(), xw.Error()
}
@@ -543,6 +519,7 @@ func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
func (o *ResponseMessage) decodeXDR(xr *xdr.Reader) error {
o.Data = xr.ReadBytes()
o.Error = int32(xr.ReadUint32())
return xr.Error()
}
@@ -789,7 +766,7 @@ Device Structure:
struct Device {
opaque ID<32>;
unsigned int Flags;
unsigned hyper MaxLocalVersion;
hyper MaxLocalVersion;
}
*/
@@ -824,7 +801,7 @@ func (o Device) encodeXDR(xw *xdr.Writer) (int, error) {
}
xw.WriteBytes(o.ID)
xw.WriteUint32(o.Flags)
xw.WriteUint64(o.MaxLocalVersion)
xw.WriteUint64(uint64(o.MaxLocalVersion))
return xw.Tot(), xw.Error()
}
@@ -842,7 +819,7 @@ func (o *Device) UnmarshalXDR(bs []byte) error {
func (o *Device) decodeXDR(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32)
o.Flags = xr.ReadUint32()
o.MaxLocalVersion = xr.ReadUint64()
o.MaxLocalVersion = int64(xr.ReadUint64())
return xr.Error()
}
@@ -940,10 +917,13 @@ CloseMessage Structure:
\ Reason (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct CloseMessage {
string Reason<1024>;
int Code;
}
*/
@@ -977,6 +957,7 @@ func (o CloseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("Reason", l, 1024)
}
xw.WriteString(o.Reason)
xw.WriteUint32(uint32(o.Code))
return xw.Tot(), xw.Error()
}
@@ -993,6 +974,7 @@ func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
func (o *CloseMessage) decodeXDR(xr *xdr.Reader) error {
o.Reason = xr.ReadStringMax(1024)
o.Code = int32(xr.ReadUint32())
return xr.Error()
}

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
// +build darwin

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
// +build !windows,!darwin

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
// +build windows

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol
@@ -71,6 +58,10 @@ var (
ErrClosed = errors.New("connection closed")
)
// Specific variants of empty messages...
type pingMessage struct{ EmptyMessage }
type pongMessage struct{ EmptyMessage }
type Model interface {
// An index was received from the peer device
Index(deviceID DeviceID, folder string, files []FileInfo)
@@ -133,6 +124,10 @@ type encodable interface {
AppendXDR([]byte) ([]byte, error)
}
type isEofer interface {
IsEOF() bool
}
const (
pingTimeout = 30 * time.Second
pingIdleTime = 60 * time.Second
@@ -183,7 +178,10 @@ func (c *rawConnection) Index(folder string, idx []FileInfo) error {
default:
}
c.idxMut.Lock()
c.send(-1, messageTypeIndex, IndexMessage{folder, idx})
c.send(-1, messageTypeIndex, IndexMessage{
Folder: folder,
Files: idx,
})
c.idxMut.Unlock()
return nil
}
@@ -196,7 +194,10 @@ func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo) error {
default:
}
c.idxMut.Lock()
c.send(-1, messageTypeIndexUpdate, IndexMessage{folder, idx})
c.send(-1, messageTypeIndexUpdate, IndexMessage{
Folder: folder,
Files: idx,
})
c.idxMut.Unlock()
return nil
}
@@ -218,7 +219,12 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
c.awaiting[id] = rc
c.awaitingMut.Unlock()
ok := c.send(id, messageTypeRequest, RequestMessage{folder, name, uint64(offset), uint32(size)})
ok := c.send(id, messageTypeRequest, RequestMessage{
Folder: folder,
Name: name,
Offset: offset,
Size: int32(size),
})
if !ok {
return nil, ErrClosed
}
@@ -274,48 +280,60 @@ func (c *rawConnection) readerLoop() (err error) {
return err
}
switch hdr.msgType {
case messageTypeIndex:
if c.state < stateCCRcvd {
return fmt.Errorf("protocol error: index message in state %d", c.state)
switch msg := msg.(type) {
case IndexMessage:
if msg.Flags != 0 {
// We don't currently support or expect any flags.
return fmt.Errorf("protocol error: unknown flags 0x%x in Index(Update) message", msg.Flags)
}
c.handleIndex(msg.(IndexMessage))
c.state = stateIdxRcvd
case messageTypeIndexUpdate:
if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: index update message in state %d", c.state)
switch hdr.msgType {
case messageTypeIndex:
if c.state < stateCCRcvd {
return fmt.Errorf("protocol error: index message in state %d", c.state)
}
c.handleIndex(msg)
c.state = stateIdxRcvd
case messageTypeIndexUpdate:
if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: index update message in state %d", c.state)
}
c.handleIndexUpdate(msg)
}
c.handleIndexUpdate(msg.(IndexMessage))
case messageTypeRequest:
case RequestMessage:
if msg.Flags != 0 {
// We don't currently support or expect any flags.
return fmt.Errorf("protocol error: unknown flags 0x%x in Request message", msg.Flags)
}
if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: request message in state %d", c.state)
}
// Requests are handled asynchronously
go c.handleRequest(hdr.msgID, msg.(RequestMessage))
go c.handleRequest(hdr.msgID, msg)
case messageTypeResponse:
case ResponseMessage:
if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: response message in state %d", c.state)
}
c.handleResponse(hdr.msgID, msg.(ResponseMessage))
c.handleResponse(hdr.msgID, msg)
case messageTypePing:
c.send(hdr.msgID, messageTypePong, EmptyMessage{})
case pingMessage:
c.send(hdr.msgID, messageTypePong, pongMessage{})
case messageTypePong:
case pongMessage:
c.handlePong(hdr.msgID)
case messageTypeClusterConfig:
case ClusterConfigMessage:
if c.state != stateInitial {
return fmt.Errorf("protocol error: cluster config message in state %d", c.state)
}
go c.receiver.ClusterConfig(c.id, msg.(ClusterConfigMessage))
go c.receiver.ClusterConfig(c.id, msg)
c.state = stateCCRcvd
case messageTypeClose:
return errors.New(msg.(CloseMessage).Reason)
case CloseMessage:
return errors.New(msg.Reason)
default:
return fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
@@ -341,6 +359,11 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
l.Debugf("read header %v (msglen=%d)", hdr, msglen)
}
if hdr.version != 0 {
err = fmt.Errorf("unknown protocol version 0x%x", hdr.version)
return
}
if cap(c.rdbuf0) < msglen {
c.rdbuf0 = make([]byte, msglen)
} else {
@@ -376,33 +399,58 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
}
}
// We check each returned error for the XDRError.IsEOF() method.
// IsEOF()==true here means that the message contained fewer fields than
// expected. It does not signify an EOF on the socket, because we've
// successfully read a size value and that many bytes already. New fields
// we expected but the other peer didn't send should be interpreted as
// zero/nil, and if that's not valid we'll verify it somewhere else.
switch hdr.msgType {
case messageTypeIndex, messageTypeIndexUpdate:
var idx IndexMessage
err = idx.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = idx
case messageTypeRequest:
var req RequestMessage
err = req.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = req
case messageTypeResponse:
var resp ResponseMessage
err = resp.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = resp
case messageTypePing, messageTypePong:
msg = EmptyMessage{}
case messageTypePing:
msg = pingMessage{}
case messageTypePong:
msg = pongMessage{}
case messageTypeClusterConfig:
var cc ClusterConfigMessage
err = cc.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = cc
case messageTypeClose:
var cm CloseMessage
err = cm.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = cm
default:
@@ -416,20 +464,48 @@ func (c *rawConnection) handleIndex(im IndexMessage) {
if debug {
l.Debugf("Index(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
}
c.receiver.Index(c.id, im.Folder, im.Files)
c.receiver.Index(c.id, im.Folder, filterIndexMessageFiles(im.Files))
}
func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
if debug {
l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
}
c.receiver.IndexUpdate(c.id, im.Folder, im.Files)
c.receiver.IndexUpdate(c.id, im.Folder, filterIndexMessageFiles(im.Files))
}
func filterIndexMessageFiles(fs []FileInfo) []FileInfo {
var out []FileInfo
for i, f := range fs {
switch f.Name {
case "", ".", "..", "/": // A few obviously invalid filenames
l.Infof("Dropping invalid filename %q from incoming index", f.Name)
if out == nil {
// Most incoming updates won't contain anything invalid, so we
// delay the allocation and copy to output slice until we
// really need to do it, then copy all the so var valid files
// to it.
out = make([]FileInfo, i, len(fs)-1)
copy(out, fs)
}
default:
if out != nil {
out = append(out, f)
}
}
}
if out != nil {
return out
}
return fs
}
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
data, _ := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), int(req.Size))
c.send(msgID, messageTypeResponse, ResponseMessage{data})
c.send(msgID, messageTypeResponse, ResponseMessage{
Data: data,
})
}
func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
@@ -630,8 +706,8 @@ func (c *rawConnection) pingerLoop() {
type Statistics struct {
At time.Time
InBytesTotal uint64
OutBytesTotal uint64
InBytesTotal int64
OutBytesTotal int64
}
func (c *rawConnection) Statistics() Statistics {

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol
@@ -189,7 +176,7 @@ func TestVersionErr(t *testing.T) {
msgID: 0,
msgType: 0,
}))
w.WriteUint32(0)
w.WriteUint32(0) // Avoids reader closing due to EOF
if !m1.isClosed() {
t.Error("Connection should close due to unknown version")
@@ -212,7 +199,7 @@ func TestTypeErr(t *testing.T) {
msgID: 0,
msgType: 42,
}))
w.WriteUint32(0)
w.WriteUint32(0) // Avoids reader closing due to EOF
if !m1.isClosed() {
t.Error("Connection should close due to unknown message type")

View File

@@ -1,17 +1,4 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Copyright (C) 2014 The Protocol Authors.
package protocol

7
NICKS
View File

@@ -4,32 +4,39 @@ AudriusButkevicius <audrius.butkevicius@gmail.com>
Cathryne <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
KayoticSully <kayoticsully@gmail.com>
Nutomic <me@nutomic.com>
Rewt0r <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Vilbrekin <vilbrekin@gmail.com>
Zillode <zillode@zillode.be>
alex2108 <register-github@alex-graf.de>
andrew-d <andrew@du.nham.ca>
asdil12 <dominik@heidler.eu>
bigbear2nd <bigbear2nd@gmail.com>
brendanlong <self@brendanlong.com>
bsidhom <bsidhom@gmail.com>
calmh <jakob@nym.se>
cdata <chris@scriptolo.gy>
ceh <emil@hessman.se>
cqcallaw <enlightened.despot@gmail.com>
facastagnini <federico.castagnini@gmail.com>
filoozoom <philippe@schommers.be>
frioux <frew@afoolishmanifesto.com> <frioux@gmail.com>
gillisig <gilli@vx.is>
jedie <github.com@jensdiemer.de> <git@jensdiemer.de>
jpjp <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
kozec <kozec@kozec.com>
krozycki <rozycki.karol@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
mvdan <mvdan@mvdan.cc>
peterhoeg <peter@speartail.com>
philips <brandon@ifup.org>
piobpl <piotrb10@gmail.com>
pluby <phill.luby@newredo.com>
pyfisch <pyfisch@gmail.com>
rumpelsepp <stefan@sevenbyte.org>
qbit <qbit@deftly.net>
seehuhn <voss@seehuhn.de>
snnd <dw@risu.io>
timabell <tim@timwise.co.uk>
tojrobinson <tully@tojr.org>
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>

View File

@@ -11,7 +11,7 @@ This is the `syncthing` project. The following are the project goals:
collaborating devices. The protocol should be well defined, unambiguous,
easily understood, free to use, efficient, secure and language neutral.
This is the [Block Exchange
Protocol](https://github.com/syncthing/protocol/blob/master/BEPv1.md).
Protocol](https://github.com/syncthing/specs/blob/master/BEPv1.md).
2. Provide the reference implementation to demonstrate the usability of
said protocol. This is the `syncthing` utility. It is the hope that
@@ -44,10 +44,10 @@ that describes it for both Unix and Windows.
Signed Releases
---------------
As of v0.7.0 and onwards, git tags and release binaries are GPG signed with
the key BCE524C7 (http://nym.se/gpg.txt). For release binaries, MD5 and
SHA1 checksums are calculated and signed, available in the
md5sum.txt.asc and sha1sum.txt.asc files.
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
with the key D26E6ED000654A3E (see http://syncthing.net/security.html).
For release binaries, MD5 and SHA1 checksums are calculated and signed,
available in the md5sum.txt.asc and sha1sum.txt.asc files.
Documentation
=============

View File

@@ -73,7 +73,7 @@ func main() {
flag.Parse()
switch goarch {
case "386", "amd64", "arm", "armv5", "armv6", "armv7":
case "386", "amd64", "arm":
break
default:
log.Printf("Unknown goarch %q; proceed with caution!", goarch)
@@ -226,15 +226,20 @@ func buildTar() {
build("./cmd/syncthing", tags)
filename := name + ".tar.gz"
files := []archiveFile{
{"README.md", name + "/README.txt"},
{"LICENSE", name + "/LICENSE.txt"},
{"AUTHORS", name + "/AUTHORS.txt"},
{"syncthing", name + "/syncthing"},
{"syncthing.md5", name + "/syncthing.md5"},
{src: "README.md", dst: name + "/README.txt"},
{src: "LICENSE", dst: name + "/LICENSE.txt"},
{src: "AUTHORS", dst: name + "/AUTHORS.txt"},
{src: "syncthing", dst: name + "/syncthing"},
{src: "syncthing.md5", dst: name + "/syncthing.md5"},
}
for _, file := range listFiles("etc") {
files = append(files, archiveFile{file, name + "/" + file})
files = append(files, archiveFile{src: file, dst: name + "/" + file})
}
for _, file := range listFiles("extra") {
files = append(files, archiveFile{src: file, dst: name + "/" + filepath.Base(file)})
}
tarGz(filename, files)
log.Println(filename)
}
@@ -249,12 +254,17 @@ func buildZip() {
build("./cmd/syncthing", tags)
filename := name + ".zip"
files := []archiveFile{
{"README.md", name + "/README.txt"},
{"LICENSE", name + "/LICENSE.txt"},
{"AUTHORS", name + "/AUTHORS.txt"},
{"syncthing.exe", name + "/syncthing.exe"},
{"syncthing.exe.md5", name + "/syncthing.exe.md5"},
{src: "README.md", dst: name + "/README.txt"},
{src: "LICENSE", dst: name + "/LICENSE.txt"},
{src: "AUTHORS", dst: name + "/AUTHORS.txt"},
{src: "syncthing.exe", dst: name + "/syncthing.exe"},
{src: "syncthing.exe.md5", dst: name + "/syncthing.exe.md5"},
}
for _, file := range listFiles("extra") {
files = append(files, archiveFile{src: file, dst: name + "/" + filepath.Base(file)})
}
zipFile(filename, files)
log.Println(filename)
}
@@ -275,15 +285,7 @@ func listFiles(dir string) []string {
func setBuildEnv() {
os.Setenv("GOOS", goos)
if strings.HasPrefix(goarch, "armv") {
os.Setenv("GOARCH", "arm")
os.Setenv("GOARM", goarch[4:])
} else {
os.Setenv("GOARCH", goarch)
}
if goarch == "386" {
os.Setenv("GO386", "387")
}
os.Setenv("GOARCH", goarch)
wd, err := os.Getwd()
if err != nil {
log.Println("Warning: can't determine current dir:", err)
@@ -299,7 +301,7 @@ func assets() {
}
func xdr() {
runPrint("go", "generate", "./internal/discover", "./internal/files", "./internal/protocol")
runPrint("go", "generate", "./internal/discover", "./internal/db")
}
func translate() {

View File

@@ -2,7 +2,8 @@
set -euo pipefail
IFS=$'\n\t'
DOCKERIMGV=1.4-4
DOCKERIMGV=1.4.1-1
STTRACE=${STTRACE:-}
case "${1:-default}" in
default)
@@ -52,9 +53,7 @@ case "${1:-default}" in
all)
go run build.go -goos linux -goarch amd64 tar
go run build.go -goos linux -goarch 386 tar
go run build.go -goos linux -goarch armv5 tar
go run build.go -goos linux -goarch armv6 tar
go run build.go -goos linux -goarch armv7 tar
go run build.go -goos linux -goarch arm tar
go run build.go -goos freebsd -goarch amd64 tar
go run build.go -goos freebsd -goarch 386 tar
@@ -112,6 +111,7 @@ case "${1:-default}" in
docker run --rm -h syncthing-builder -u $(id -u) -t \
-v $(pwd):/go/src/github.com/syncthing/syncthing \
-w /go/src/github.com/syncthing/syncthing \
-e "STTRACE=$STTRACE" \
syncthing/build:$DOCKERIMGV \
sh -c './build.sh clean \
&& go vet ./cmd/... ./internal/... \
@@ -124,6 +124,7 @@ case "${1:-default}" in
docker run --rm -h syncthing-builder -u $(id -u) -t \
-v $(pwd):/go/src/github.com/syncthing/syncthing \
-w /go/src/github.com/syncthing/syncthing \
-e "STTRACE=$STTRACE" \
syncthing/build:$DOCKERIMGV \
sh -euxc './build.sh clean \
&& go run build.go -race \

View File

@@ -5,5 +5,5 @@ if [[ -z $since ]] ; then
since="$(git describe --abbrev=0 HEAD^).."
fi
git log --pretty=format:'* %s (%h, @%aN)' "$since" | egrep 'fixes #\d|ref #\d'
git log --reverse --pretty=format:'* %s, @%aN)' "$since" | egrep 'fixes #\d|ref #\d' | sed 's/),/,/' | sed 's/fixes #/#/g' | sed 's/ref #/#/g'

View File

@@ -16,7 +16,8 @@ no-docs-typos() {
grep -v f1120d7aa936c0658429edef0037792520b46334 |\
grep -v a9339d0627fff439879d157c75077f02c9fac61b |\
grep -v 254c63763a3ad42fd82259f1767db526cff94a14 |\
grep -v 4b76ec40c07078beaa2c5e250ed7d9bd6276a718
grep -v 4b76ec40c07078beaa2c5e250ed7d9bd6276a718 |\
grep -v ffc39dfbcb34eacc3ea12327a02b6e7741a2c207
}
print-missing-authors() {

View File

@@ -45,7 +45,7 @@ func Assets() map[string][]byte {
var gr *gzip.Reader
{{range $asset := .assets}}
bs, _ = base64.StdEncoding.DecodeString("{{$asset.Data}}")
gr, _ = gzip.NewReader(bytes.NewBuffer(bs))
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
assets["{{$asset.Name}}"] = bs
{{end}}

View File

@@ -21,7 +21,7 @@ import (
"os"
"path/filepath"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/scanner"
)

52
cmd/stfinddevice/main.go Normal file
View File

@@ -0,0 +1,52 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"flag"
"log"
"os"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/discover"
)
func main() {
log.SetFlags(0)
log.SetOutput(os.Stdout)
var server string
flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22026", "Announce server")
flag.Parse()
if len(flag.Args()) != 1 || server == "" {
log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22026\"] <device>", os.Args[0])
os.Exit(64)
}
id, err := protocol.DeviceIDFromString(flag.Args()[0])
if err != nil {
log.Println(err)
os.Exit(1)
}
discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil)
discoverer.StartGlobal([]string{server}, 1)
for _, addr := range discoverer.Lookup(id) {
log.Println(addr)
}
}

View File

@@ -21,8 +21,8 @@ import (
"log"
"os"
"github.com/syncthing/syncthing/internal/files"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/db"
"github.com/syndtr/goleveldb/leveldb"
)
@@ -34,17 +34,17 @@ func main() {
device := flag.String("device", "", "Device ID (blank for global)")
flag.Parse()
db, err := leveldb.OpenFile(flag.Arg(0), nil)
ldb, err := leveldb.OpenFile(flag.Arg(0), nil)
if err != nil {
log.Fatal(err)
}
fs := files.NewSet(*folder, db)
fs := db.NewFileSet(*folder, ldb)
if *device == "" {
log.Printf("*** Global index for folder %q", *folder)
fs.WithGlobalTruncated(func(fi protocol.FileIntf) bool {
f := fi.(protocol.FileInfoTruncated)
fs.WithGlobalTruncated(func(fi db.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
fmt.Println(f)
fmt.Println("\t", fs.Availability(f.Name))
return true
@@ -55,8 +55,8 @@ func main() {
log.Fatal(err)
}
log.Printf("*** Have index for folder %q device %q", *folder, n)
fs.WithHaveTruncated(n, func(fi protocol.FileIntf) bool {
f := fi.(protocol.FileInfoTruncated)
fs.WithHaveTruncated(n, func(fi db.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
fmt.Println(f)
return true
})

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"os"
"runtime"
"runtime/pprof"
"syscall"
"time"
)
func init() {
if innerProcess && os.Getenv("STBLOCKPROFILE") != "" {
profiler := pprof.Lookup("block")
if profiler == nil {
panic("Couldn't find block profiler")
}
l.Debugln("Starting block profiling")
go saveBlockingProfiles(profiler)
}
}
func saveBlockingProfiles(profiler *pprof.Profile) {
runtime.SetBlockProfileRate(1)
t0 := time.Now()
for t := range time.NewTicker(20 * time.Second).C {
startms := int(t.Sub(t0).Seconds() * 1000)
fd, err := os.Create(fmt.Sprintf("block-%05d-%07d.pprof", syscall.Getpid(), startms))
if err != nil {
panic(err)
}
err = profiler.WriteTo(fd, 0)
if err != nil {
panic(err)
}
err = fd.Close()
if err != nil {
panic(err)
}
}
}

View File

@@ -32,13 +32,14 @@ import (
"time"
"github.com/calmh/logger"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/auto"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/discover"
"github.com/syncthing/syncthing/internal/events"
"github.com/syncthing/syncthing/internal/model"
"github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syncthing/syncthing/internal/upgrade"
"github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt"
@@ -301,6 +302,15 @@ func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
res["state"], res["stateChanged"] = m.State(folder)
res["version"] = m.CurrentLocalVersion(folder) + m.RemoteLocalVersion(folder)
ignorePatterns, _, _ := m.GetIgnores(folder)
res["ignorePatterns"] = false
for _, line := range ignorePatterns {
if len(line) > 0 && !strings.HasPrefix(line, "//") {
res["ignorePatterns"] = true
break
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
@@ -448,6 +458,7 @@ func restGetSystem(w http.ResponseWriter, r *http.Request) {
}
cpuUsageLock.RUnlock()
res["cpuPercent"] = cpusum / 10
res["pathSeparator"] = string(filepath.Separator)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
@@ -571,6 +582,10 @@ func restGetEvents(w http.ResponseWriter, r *http.Request) {
}
func restGetUpgrade(w http.ResponseWriter, r *http.Request) {
if noUpgrade {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return
}
rel, err := upgrade.LatestRelease(strings.Contains(Version, "-beta"))
if err != nil {
http.Error(w, err.Error(), 500)
@@ -778,9 +793,9 @@ func mimeTypeForFile(file string) string {
}
}
func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} {
output := make([]map[string]interface{}, len(files))
for i, file := range files {
func toNeedSlice(fs []db.FileInfoTruncated) []map[string]interface{} {
output := make([]map[string]interface{}, len(fs))
for i, file := range fs {
output[i] = map[string]interface{}{
"Name": file.Name,
"Flags": file.Flags,
@@ -788,7 +803,7 @@ func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} {
"Version": file.Version,
"LocalVersion": file.LocalVersion,
"NumBlocks": file.NumBlocks,
"Size": protocol.BlocksToSize(file.NumBlocks),
"Size": db.BlocksToSize(int(file.NumBlocks)),
}
}
return output

View File

@@ -37,13 +37,13 @@ import (
"github.com/calmh/logger"
"github.com/juju/ratelimit"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/discover"
"github.com/syncthing/syncthing/internal/events"
"github.com/syncthing/syncthing/internal/files"
"github.com/syncthing/syncthing/internal/model"
"github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syncthing/syncthing/internal/symlinks"
"github.com/syncthing/syncthing/internal/upgrade"
"github.com/syncthing/syncthing/internal/upnp"
@@ -142,38 +142,41 @@ Development Settings
The following environment variables modify syncthing's behavior in ways that
are mostly useful for developers. Use with care.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.
STTRACE A comma separated string of facilities to trace. The valid
facility strings are:
STTRACE A comma separated string of facilities to trace. The valid
facility strings are:
- "beacon" (the beacon package)
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)
- "beacon" (the beacon package)
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
heap usage increases.
STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
heap usage increases.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
STBLOCKPROFILE Write block profiles to block-$pid-$timestamp.pprof every 20
seconds.
STNOUPGRADE Disable automatic upgrades.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.`
STNOUPGRADE Disable automatic upgrades.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.`
)
// Command line and environment options
@@ -489,21 +492,21 @@ func syncthingMain() {
readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
}
db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{OpenFilesCacheCapacity: 100})
ldb, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{OpenFilesCacheCapacity: 100})
if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
}
// Remove database entries for folders that no longer exist in the config
folders := cfg.Folders()
for _, folder := range files.ListFolders(db) {
for _, folder := range db.ListFolders(ldb) {
if _, ok := folders[folder]; !ok {
l.Infof("Cleaning data for dropped folder %q", folder)
files.DropFolder(db, folder)
db.DropFolder(ldb, folder)
}
}
m := model.NewModel(cfg, myName, "syncthing", Version, db)
m := model.NewModel(cfg, myName, "syncthing", Version, ldb)
sanityCheckFolders(cfg, m)
@@ -658,7 +661,9 @@ func setupGUI(cfg *config.Wrapper, m *model.Model) {
}
if opts.StartBrowser && !noBrowser && !stRestarting {
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
openURL(urlOpen)
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(urlOpen)
}
}
}
@@ -874,8 +879,10 @@ func resetFolders() {
suffix := fmt.Sprintf(".syncthing-reset-%d", time.Now().UnixNano())
for _, folder := range cfg.Folders() {
if _, err := os.Stat(folder.Path); err == nil {
l.Infof("Reset: Moving %s -> %s", folder.Path, folder.Path+suffix)
os.Rename(folder.Path, folder.Path+suffix)
base := filepath.Base(folder.Path)
dir := filepath.Dir(filepath.Join(folder.Path, ".."))
l.Infof("Reset: Moving %s -> %s", folder.Path, filepath.Join(dir, base+suffix))
os.Rename(folder.Path, filepath.Join(dir, base+suffix))
}
}
@@ -1259,28 +1266,35 @@ func standbyMonitor() {
}
func autoUpgrade() {
var skipped bool
interval := time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour
timer := time.NewTimer(0)
sub := events.Default.Subscribe(events.DeviceConnected)
for {
if skipped {
time.Sleep(interval)
} else {
skipped = true
select {
case event := <-sub.C():
data, ok := event.Data.(map[string]string)
if !ok || data["clientName"] != "syncthing" || upgrade.CompareVersions(data["clientVersion"], Version) != upgrade.Newer {
continue
}
l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], Version, data["clientVersion"])
case <-timer.C:
}
rel, err := upgrade.LatestRelease(IsBeta)
if err == upgrade.ErrUpgradeUnsupported {
events.Default.Unsubscribe(sub)
return
}
if err != nil {
// Don't complain too loudly here; we might simply not have
// internet connectivity, or the upgrade server might be down.
l.Infoln("Automatic upgrade:", err)
timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour)
continue
}
if upgrade.CompareVersions(rel.Tag, Version) != upgrade.Newer {
// Skip equal, older or majorly newer (incompatible) versions
timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour)
continue
}
@@ -1288,8 +1302,10 @@ func autoUpgrade() {
err = upgrade.To(rel)
if err != nil {
l.Warnln("Automatic upgrade:", err)
timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour)
continue
}
events.Default.Unsubscribe(sub)
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
time.Sleep(time.Minute)
stop <- exitUpgrading

View File

@@ -19,10 +19,10 @@ import (
"os"
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/files"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/model"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
@@ -44,11 +44,11 @@ func TestSanityCheck(t *testing.T) {
}
}
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
// Case 1 - new folder, directory and marker created
m := model.NewModel(cfg, "device", "syncthing", "dev", db)
m := model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if cfg.Folders()["folder"].Invalid != "" {
@@ -75,7 +75,7 @@ func TestSanityCheck(t *testing.T) {
Folders: []config.FolderConfiguration{fcfg},
})
m = model.NewModel(cfg, "device", "syncthing", "dev", db)
m = model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if cfg.Folders()["folder"].Invalid != "" {
@@ -91,12 +91,12 @@ func TestSanityCheck(t *testing.T) {
// Case 3 - marker missing
set := files.NewSet("folder", db)
set := db.NewFileSet("folder", ldb)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile"},
})
m = model.NewModel(cfg, "device", "syncthing", "dev", db)
m = model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if cfg.Folders()["folder"].Invalid != "folder marker missing" {
@@ -110,7 +110,7 @@ func TestSanityCheck(t *testing.T) {
Folders: []config.FolderConfiguration{fcfg},
})
m = model.NewModel(cfg, "device", "syncthing", "dev", db)
m = model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if cfg.Folders()["folder"].Invalid != "folder path missing" {

View File

@@ -22,7 +22,7 @@ import (
"strings"
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
cmd := exec.Command("sysctl", "hw.memsize")
out, err := cmd.Output()
if err != nil {
@@ -32,7 +32,7 @@ func memorySize() (uint64, error) {
if len(fs) != 2 {
return 0, errors.New("sysctl parse error")
}
bytes, err := strconv.ParseUint(fs[1], 10, 64)
bytes, err := strconv.ParseInt(fs[1], 10, 64)
if err != nil {
return 0, err
}

View File

@@ -23,7 +23,7 @@ import (
"strings"
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
f, err := os.Open("/proc/meminfo")
if err != nil {
return 0, err
@@ -40,7 +40,7 @@ func memorySize() (uint64, error) {
return 0, errors.New("/proc/meminfo parse error 2")
}
kb, err := strconv.ParseUint(fs[1], 10, 64)
kb, err := strconv.ParseInt(fs[1], 10, 64)
if err != nil {
return 0, err
}

View File

@@ -22,14 +22,14 @@ import (
"strconv"
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
cmd := exec.Command("prtconf", "-m")
out, err := cmd.CombinedOutput()
if err != nil {
return 0, err
}
mb, err := strconv.ParseUint(string(out), 10, 64)
mb, err := strconv.ParseInt(string(out), 10, 64)
if err != nil {
return 0, err
}

View File

@@ -19,6 +19,6 @@ package main
import "errors"
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
return 0, errors.New("not implemented")
}

View File

@@ -26,7 +26,7 @@ var (
globalMemoryStatusEx, _ = syscall.GetProcAddress(kernel32, "GlobalMemoryStatusEx")
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
var memoryStatusEx [64]byte
binary.LittleEndian.PutUint32(memoryStatusEx[:], 64)
p := uintptr(unsafe.Pointer(&memoryStatusEx[0]))
@@ -36,5 +36,5 @@ func memorySize() (uint64, error) {
return 0, callErr
}
return binary.LittleEndian.Uint64(memoryStatusEx[8:]), nil
return int64(binary.LittleEndian.Uint64(memoryStatusEx[8:])), nil
}

View File

@@ -24,7 +24,7 @@ import (
"syscall"
"time"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syncthing/protocol"
)
func init() {
@@ -43,7 +43,7 @@ func savePerfStats(file string) {
var prevTime int64
var rusage syscall.Rusage
var memstats runtime.MemStats
var prevIn, prevOut uint64
var prevIn, prevOut int64
t0 := time.Now()
for t := range time.NewTicker(250 * time.Millisecond).C {

View File

@@ -15,14 +15,21 @@
package main
import "testing"
import (
"sync"
"testing"
)
var predictableRandomTest sync.Once
func TestPredictableRandom(t *testing.T) {
// predictable random sequence is predictable
e := 3440579354231278675
if v := predictableRandom.Int(); v != e {
t.Errorf("Unexpected random value %d != %d", v, e)
}
predictableRandomTest.Do(func() {
// predictable random sequence is predictable
e := 3440579354231278675
if v := predictableRandom.Int(); v != e {
t.Errorf("Unexpected random value %d != %d", v, e)
}
})
}
func TestSeedFromBytes(t *testing.T) {

View File

@@ -1,7 +1,7 @@
FROM debian:squeeze
MAINTAINER Jakob Borg <jakob@nym.se>
ENV GOLANG_VERSION 1.4
ENV GOLANG_VERSION 1.4.1
# SCMs for "go get", gcc for cgo
RUN apt-get update && apt-get install -y \
@@ -17,9 +17,12 @@ RUN curl -sSL https://golang.org/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \
| tar -v -C /usr/local -xz
ENV PATH /usr/local/go/bin:$PATH
RUN mkdir /go
ENV GOPATH /go
ENV GO386 387
ENV GOARM 5
ENV PATH /go/bin:$PATH
RUN mkdir /go
WORKDIR /go
# Use gonative to install native Go for most arch/OS combos
@@ -31,22 +34,20 @@ RUN go get github.com/calmh/gonative \
# Rebuild the special and missing versions, using patches as appropriate
RUN mkdir /tmp/patches
ADD *.patch /tmp/patches/
RUN bash -xec '\
cd /usr/local/go ; \
for patch in /tmp/patches/*.patch ; do \
patch -p0 < "$patch" ; \
done \
'
# RUN mkdir /tmp/patches
# ADD *.patch /tmp/patches/
# RUN bash -xec '\
# cd /usr/local/go ; \
# for patch in /tmp/patches/*.patch ; do \
# patch -p0 < "$patch" ; \
# done \
# '
RUN bash -xec '\
cd /usr/local/go/src; \
for platform in linux/386 freebsd/386 windows/386 linux/arm openbsd/amd64 openbsd/386; do \
GOOS=${platform%/*} \
GOARCH=${platform##*/} \
GOARM=5 \
GO386=387 \
CGO_ENABLED=0 \
./make.bash --no-clean 2>&1; \
done \

View File

@@ -1,22 +0,0 @@
--- src/syscall/route_openbsd.go.orig Fri Jul 25 23:38:47 2014
+++ src/syscall/route_openbsd.go Fri Jul 25 23:39:20 2014
@@ -12,16 +12,16 @@ func (any *anyMessage) toRoutingMessage(b []byte) Rout
switch any.Type {
case RTM_ADD, RTM_DELETE, RTM_CHANGE, RTM_GET, RTM_LOSING, RTM_REDIRECT, RTM_MISS, RTM_LOCK, RTM_RESOLVE:
p := (*RouteMessage)(unsafe.Pointer(any))
- return &RouteMessage{Header: p.Header, Data: b[SizeofRtMsghdr:any.Msglen]}
+ return &RouteMessage{Header: p.Header, Data: b[p.Header.Hdrlen:any.Msglen]}
case RTM_IFINFO:
p := (*InterfaceMessage)(unsafe.Pointer(any))
- return &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}
+ return &InterfaceMessage{Header: p.Header, Data: b[p.Header.Hdrlen:any.Msglen]}
case RTM_IFANNOUNCE:
p := (*InterfaceAnnounceMessage)(unsafe.Pointer(any))
return &InterfaceAnnounceMessage{Header: p.Header}
case RTM_NEWADDR, RTM_DELADDR:
p := (*InterfaceAddrMessage)(unsafe.Pointer(any))
- return &InterfaceAddrMessage{Header: p.Header, Data: b[SizeofIfaMsghdr:any.Msglen]}
+ return &InterfaceAddrMessage{Header: p.Header, Data: b[p.Header.Hdrlen:any.Msglen]}
}
return nil
}

View File

@@ -0,0 +1,27 @@
This directory contains a configuration for running syncthing under the
"systemd" service manager on Linux both under either a systemd system service or
systemd user service.
1. Install systemd.
2. If you are running this as a system level service:
1. Create the user you will be running the service as (foo in this example).
2. Copy the syncthing@.service files to /etc/systemd/system
3. Enable and start the service
systemctl enable syncthing@foo.service
systemctl start syncthing@foo.service
3. If you are running this as a user level service:
1. Log in as the user you will be running the service as
2. Copy the syncthing.service files to /etc/systemd/user
3. Enable and start the service
systemctl --user enable syncthing.service
systemctl --user start syncthing.service
Log output is sent to the journal.

View File

@@ -0,0 +1,17 @@
[Unit]
Description=Syncthing service for %i
After=network.target
[Service]
User=%i
Environment=STARGS=
EnvironmentFile=-/etc/default/syncthing
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing ${STARGS}
Restart=on-failure
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,15 @@
[Unit]
Description=Syncthing service
[Service]
Environment=STARGS=
EnvironmentFile=-%h/.config/syncthing/environment
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing ${STARGS}
Restart=on-failure
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]
WantedBy=cmdline.target

View File

@@ -39,6 +39,8 @@ ul+h5 {
.panel-title {
position: relative;
text-overflow: ellipsis;
overflow: hidden;
}
identicon {
@@ -54,6 +56,10 @@ identicon {
margin-top: 0px;
}
.checkbox input[type="checkbox"], .radio input[type="radio"] {
float: none; /* issue #1197 */
}
.popover {
max-width: none;
}
@@ -69,6 +75,10 @@ identicon {
top: 1px;
}
.panel-heading .glyphicon {
margin-right: 15px;
}
.panel-heading {
position: relative;
overflow: hidden;
@@ -195,3 +205,15 @@ ul.three-columns li, ul.two-columns li {
padding-left: 0.5em;
text-indent: -0.5em;
}
/** Footer nav on small devices **/
@media (max-width: 767px) {
body {
padding-bottom: 0;
}
.navbar-fixed-bottom {
position: static;
}
}

View File

@@ -1,38 +1,40 @@
{
"API Key": "Ключ API",
"About": "Аб праграме",
"Add": "Add",
"Add": "Дадаць",
"Add Device": "Дадаць прыладу",
"Add Folder": "Дадаць каталёг",
"Add new folder?": "Add new folder?",
"Add new folder?": "Дадаць новы каталёг ?",
"Address": "Адрас",
"Addresses": "Адрасы",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Automatic upgrades": "Automatic upgrades",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilization",
"Close": "Close",
"Bugs": "Памылкі",
"CPU Utilization": "Выкарыстаньне працэсара",
"Changelog": "Сьпіс зьменаў",
"Close": "Зачыніць",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Delete",
"Device ID": "Device ID",
"Delete": "Выдаліць",
"Device ID": "ID прылады",
"Device Identification": "Device Identification",
"Device Name": "Device Name",
"Device Name": "Назва прылады",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Прылады",
"Disconnected": "Disconnected",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Documentation": "Дакумэнтацыя",
"Download Rate": "Хуткасьць спампоўваньня",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Edit": "Edit",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Edit": "Зьмяніць",
"Edit Device": "Зьмяніць прыладу",
"Edit Folder": "Зьмяніць каталёг",
"Editing": "Editing",
"Enable UPnP": "Enable UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
@@ -42,31 +44,32 @@
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Folder ID": "Folder ID",
"Folder ID": "ID каталёгу",
"Folder Master": "Folder Master",
"Folder Path": "Folder Path",
"Folders": "Folders",
"Folder Path": "Шлях каталёгу",
"Folders": "Каталёгі",
"GUI Authentication Password": "GUI Authentication Password",
"GUI Authentication User": "GUI Authentication User",
"GUI Listen Addresses": "GUI Listen Addresses",
"Generate": "Generate",
"Global Discovery": "Global Discovery",
"Global Discovery Server": "Global Discovery Server",
"Global State": "Global State",
"Global State": "Глябальны стан",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
"Ignore Permissions": "Ignore Permissions",
"Ignore Permissions": "Ігнараваць правы",
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Synced": "Last File Synced",
"Last File Received": "Last File Received",
"Last File Synced": "Апошні файл",
"Last seen": "Last seen",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Local State": "Лякальны стан",
"Maximum Age": "Maximum Age",
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
"Never": "Never",
@@ -78,7 +81,8 @@
"OK": "OK",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out Of Sync",
"Out Of Sync": "Несынхранізавана",
"Out of Sync Items": "Несынхранізаваныя складнікі",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
"Override Changes": "Override Changes",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
@@ -87,18 +91,18 @@
"Preview": "Preview",
"Preview Usage Report": "Preview Usage Report",
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Rescan": "Rescan",
"Rescan Interval": "Rescan Interval",
"Restart": "Restart",
"Restart Needed": "Restart Needed",
"Restarting": "Restarting",
"RAM Utilization": "Выкарыстаньне памяці",
"Rescan": "Перачытаць",
"Rescan Interval": "Інтэрвал перачытваньня",
"Restart": "Перастартаваць",
"Restart Needed": "Патрэбна перастартоўваньне",
"Restarting": "Перастартоўваньне",
"Reused": "Reused",
"Save": "Save",
"Save": "Захаваць",
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Settings": "Settings",
"Settings": "Налады",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
@@ -106,25 +110,28 @@
"Share this folder?": "Share this folder?",
"Shared With": "Shared With",
"Short identifier for the folder. Must be the same on all cluster devices.": "Short identifier for the folder. Must be the same on all cluster devices.",
"Show ID": "Show ID",
"Show ID": "Паказаць ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
"Shutdown": "Shutdown",
"Shutdown": "Выключыць",
"Shutdown Complete": "Выключэньне завершанае",
"Simple File Versioning": "Simple File Versioning",
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Source Code": "Source Code",
"Source Code": "Зыходнікі",
"Staggered File Versioning": "Staggered File Versioning",
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support / Forum": "Support / Forum",
"Support": "Падтрымка",
"Support / Forum": "Падтрымка / Форум",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Synchronization",
"Synchronization": "Сынхранізацыя",
"Syncing": "Syncing",
"Syncthing has been shut down.": "Syncthing has been shut down.",
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
"Syncthing is restarting.": "Syncthing is restarting.",
"Syncthing is restarting.": "Syncthing перастартоўвае.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The device ID cannot be blank.": "The device ID cannot be blank.",
@@ -145,13 +152,13 @@
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unused": "Unused",
"Up to Date": "Up to Date",
"Up to Date": "Найноўшае",
"Upgrade To {%version%}": "Upgrade To {{version}}",
"Upgrading": "Upgrading",
"Upload Rate": "Upload Rate",
"Upload Rate": "Хуткасьць запампоўваньня",
"Use Compression": "Use Compression",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Version",
"Version": "Вэрсія",
"Versions Path": "Versions Path",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
@@ -159,6 +166,6 @@
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"full documentation": "full documentation",
"items": "items",
"items": "складнікаў",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API Ключ",
"About": "За Програмата",
"Add": "Add",
"Add": "Добави",
"Add Device": "Добави устройство",
"Add Folder": "Добави папка",
"Add new folder?": "Add new folder?",
"Add new folder?": "Добави нова папка?",
"Address": "Адрес",
"Addresses": "Адреси",
"Allow Anonymous Usage Reporting?": "Разреши анонимен доклад за ползване на програмата?",
@@ -13,6 +13,7 @@
"Automatic upgrades": "Автоматични ъпдейти",
"Bugs": "Бъгове",
"CPU Utilization": "Натоварване на Процесора",
"Changelog": "Сипъск с промени",
"Close": "Затвори",
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
"Compression is recommended in most setups.": "Компресията е препоръчителна в повечето конфигурации.",
@@ -24,7 +25,8 @@
"Device ID": "Идентификатор на устройство",
"Device Identification": "Идентификация на устройство",
"Device Name": "Име на устройство",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) желае да се свърже. Добави ново устройство?",
"Devices": "Устройства",
"Disconnected": "Прекрати Връзката",
"Documentation": "Документация",
"Download Rate": "Скорост на Теглене",
@@ -54,24 +56,25 @@
"Global Discovery Server": "Сървър за Глобално Откриване",
"Global State": "Глобално състояние",
"Idle": "Без Работа",
"Ignore": "Ignore",
"Ignore": "Игнорирай",
"Ignore Patterns": "Шаблони за Игнориране",
"Ignore Permissions": "Игнорирай Права за Достъп",
"Incoming Rate Limit (KiB/s)": "Входящ Лимит на Скоростта (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
"Keep Versions": "Пази Версии",
"Last File Received": "Последния получен файл",
"Last File Synced": "Последния синхронизиран файл",
"Last seen": "Последно видян",
"Later": "Later",
"Later": "По-късно",
"Latest Release": "Най-новата Версия",
"Local Discovery": "Локално Откриване",
"Local State": "Локално състояние",
"Maximum Age": "Максимална Възраст",
"Multi level wildcard (matches multiple directory levels)": "Маска на много нива (покрива папки с много нива)",
"Never": "Никога",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ново устройство",
"New Folder": "Нова папка",
"No": "Не",
"No File Versioning": "Няма Файлови Версии",
"Notice": "Известие",
@@ -79,6 +82,7 @@
"Offline": "Не е на линия",
"Online": "На линия",
"Out Of Sync": "Не Синхронизиран",
"Out of Sync Items": "Несинхронизирани елементи",
"Outgoing Rate Limit (KiB/s)": "Лимит на Изходящата Скорост (KiB/s)",
"Override Changes": "Замени Промените",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Пътят до папката на този компютър. Ще бъде създадена ако не съществува. Символът тилда (~) може да бъде използван като заместител на",
@@ -99,32 +103,35 @@
"Select the devices to share this folder with.": "Избери устройствата, с които да споделиш тази папка.",
"Select the folders to share with this device.": "Изберете папките за споделяне с това устройство.",
"Settings": "Настройки",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Сподели",
"Share Folder": "Сподели папка",
"Share Folders With Device": "Сподели папки с това устройство",
"Share With Devices": "Сподели с устройства",
"Share this folder?": "Share this folder?",
"Share this folder?": "Сподели тази папка?",
"Shared With": "Споделена със",
"Short identifier for the folder. Must be the same on all cluster devices.": "Кратък идентификатор на папката. Трябва да бъде същият на всички компютри.",
"Show ID": "Покажи Идентификатора",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Покажи вместо идентификатор на устройството в статус на клъстъра. Ще бъде предлагано на други комютри като име по подразбиране.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Покажи вместо идентификатор на устройството в статус на клъстъра. Ще бъде обновено с името по подразбиране изпратено от другия компютър.",
"Shutdown": "Спри Програмата",
"Shutdown Complete": "Спирането завършено",
"Simple File Versioning": "Опростени Файлови Версии",
"Single level wildcard (matches within a directory only)": "Маска на едно ниво (покрива само в папка)",
"Source Code": "Сорс Код",
"Staggered File Versioning": "Наслагващи се Файлови Версии",
"Start Browser": "Стартирай Браузъра",
"Stopped": "Спряна",
"Support": "Помощ",
"Support / Forum": "Помощ / Форум",
"Sync Protocol Listen Addresses": "Адрес за слушане на синхронизиращия протокол",
"Synchronization": "Синхронизация",
"Syncing": "Синхронизиране",
"Syncthing has been shut down.": "Syncthing е спрян.",
"Syncthing includes the following software or portions thereof:": "Syncthing включва следният софтуер пълно или частично:",
"Syncthing is restarting.": "Syncthing се рестартирва",
"Syncthing is restarting.": "Syncthing се рестартира",
"Syncthing is upgrading.": "Syncthing се обновява.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing изглежда не е включен, или има проблем с интерент връзката. Повторен опит...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing има проблем при обработването на заявката. Моля, презаредете браузъра или рестартирайте Syncthing ако проблемът продължи.",
"The aggregated statistics are publicly available at {%url%}.": "Сумарната статистика е публично достъпна на {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурацията е запазена, но не е активирана. Syncthing трябва да рестартира, за да се активира новата конфигурация.",
"The device ID cannot be blank.": "Полето идентификатор на устройство не може да бъде празно.",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
"full documentation": "пълна документация",
"items": "артикула",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папка \"{{folder}}\"."
}

View File

@@ -0,0 +1,171 @@
{
"API Key": "Clau API",
"About": "Sobre",
"Add": "Afegir",
"Add Device": "Afegir dispositiu",
"Add Folder": "Afegir carpeta",
"Add new folder?": "Afegir nova carpeta?",
"Address": "Adreça",
"Addresses": "Adreces",
"Allow Anonymous Usage Reporting?": "Permetre l'enviament anònim d'informes d'ús?",
"Anonymous Usage Reporting": "Informe anònim d'ús",
"Any devices configured on an introducer device will be added to this device as well.": "Qualsevol dispositiu configurat com a dispositiu introductor s'afegirà a aquest dispositiu tambè.",
"Automatic upgrades": "Actualitzacions automàtiques",
"Bugs": "Bugs",
"CPU Utilization": "Utilització del CPU",
"Changelog": "Historial de canvis",
"Close": "Tancar",
"Comment, when used at the start of a line": "Comentari quan és usat al principi d'una línia",
"Compression is recommended in most setups.": "Generalment, la compressió és recomanada.",
"Connection Error": "Error de connexió",
"Copied from elsewhere": "Copiat d'un altre lloc",
"Copied from original": "Copiat de l'original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Drets d'autor © 2014 Jakob Borg i els següents contribuïdors:",
"Delete": "Esborrar",
"Device ID": "ID del dispositiu",
"Device Identification": "Identificació del dispositiu",
"Device Name": "Nom del dispositiu",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol conectar-se. Afegir nou dispositiu?",
"Devices": "Dispositius",
"Disconnected": "Desconnectat",
"Documentation": "Documentació",
"Download Rate": "Tasca de descarrega",
"Downloaded": "Descarregat",
"Downloading": "Descarregant",
"Edit": "Editar",
"Edit Device": "Modificar dispositiu",
"Edit Folder": "Modificar carpeta",
"Editing": "Modificant",
"Enable UPnP": "Habilitat UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduir, separat per comes, adreces \"ip:port\" o \"dynamic\" per descobrir automàticament les adreces.",
"Enter ignore patterns, one per line.": "Introduïx els patrons d'ignoració, un per línia.",
"Error": "Error",
"File Versioning": "Versionat de Fitxers",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Els bits de permisos dels fitxers son ignorats quan es cerquen canvis. Utilitzar en sistemes de fitxers FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Els fitxers es mouen amb l'estampat de la data a la carpeta .stversions quan son substituïts o esborrats per syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Els fitxers estan protegits de canvis fets per altres dispositius, però els canvis fets en aquest dispositiu seran enviats a la resta del cluster.",
"Folder ID": "ID de carpeta",
"Folder Master": "Carpeta mestre",
"Folder Path": "Camí de carpeta",
"Folders": "Carpetes",
"GUI Authentication Password": "Contrasenya d'autenticació GUI",
"GUI Authentication User": "Usuari d'autenticació GUI",
"GUI Listen Addresses": "Adreça d'escolta del GUI",
"Generate": "Generar",
"Global Discovery": "Descobriment Global",
"Global Discovery Server": "Servidor de Descobriment Global",
"Global State": "Estat global",
"Idle": "Inactiu",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrons d'ignoració",
"Ignore Permissions": "Ignora Permisos",
"Incoming Rate Limit (KiB/s)": "Tasca Límit d'Entrada (KiB/s)",
"Introducer": "Introductor",
"Inversion of the given condition (i.e. do not exclude)": "Inversió del patrò introduït",
"Keep Versions": "Mantenir Versions",
"Last File Received": "Últim fitxer rebut",
"Last File Synced": "Últim fitxer sincronitzat",
"Last seen": "Vist per última vegada",
"Later": "Després",
"Latest Release": "Última publicació",
"Local Discovery": "Descobriment Local",
"Local State": "Estat local",
"Maximum Age": "Antiguitat Màxima",
"Multi level wildcard (matches multiple directory levels)": "Caràcter comodí de nivell múltiple (aparella en carpetes de nivells múltiples)",
"Never": "Mai",
"New Device": "Nou dispositiu",
"New Folder": "Nova carpeta",
"No": "No",
"No File Versioning": "Sense Versionat de Fitxer",
"Notice": "Avís",
"OK": "OK",
"Offline": "Desconnectat",
"Online": "Connectat",
"Out Of Sync": "Fora de la Sincronització",
"Out of Sync Items": "Arxius encara no sincronitzats",
"Outgoing Rate Limit (KiB/s)": "Tasca Límit de Sortida (KiB/s)",
"Override Changes": "Sobreescriure Canvis",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta de la carpeta a l'equip local. Si no existeix serà creada. El caràcter (~) es pot fer servir com a drecera de",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta on les versions s'haurien de guardar (deixa-ho buit per fer servir el directori .stversions per defecte a la carpeta)",
"Please wait": "Si-us-plau espera",
"Preview": "Vista prèvia",
"Preview Usage Report": "Vista Prèvia de l'Informe d'Ús",
"Quick guide to supported patterns": "Guia ràpida per als possibles patrons",
"RAM Utilization": "Utilització de la RAM",
"Rescan": "Re-escanejar",
"Rescan Interval": "Interval de re-escaneig",
"Restart": "Reiniciar",
"Restart Needed": "És Necessari Reiniciar",
"Restarting": "Reiniciant",
"Reused": "Reutilitzat",
"Save": "Guardar",
"Scanning": "Escanejant",
"Select the devices to share this folder with.": "Selecciona els dispositius en els quals compartir aquesta carpeta.",
"Select the folders to share with this device.": "Selecciona la carpeta per a compartir en aquest dispositiu.",
"Settings": "Preferències",
"Share": "Compartir",
"Share Folder": "Compartir carpeta",
"Share Folders With Device": "Compartir carpetes en dispositiu",
"Share With Devices": "Compartir en dispositius",
"Share this folder?": "Compartir aquesta carpeta?",
"Shared With": "Compartir Amb",
"Short identifier for the folder. Must be the same on all cluster devices.": "Breva identificació per a la carpeta. Té que ser la mateixa per tots els dispositius del cluster.",
"Show ID": "Mostrar ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrat en comptes del ID del Node en l'estat del cluster. Serà advertit als altres dispositius com un nom opcional per defecte.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrat en comptes del ID del Node en l'estat del cluster. S'actualitzarà al nom del dispositiu si es deixa buit.",
"Shutdown": "Apagar",
"Shutdown Complete": "Apagat complet",
"Simple File Versioning": "Versionat de Fitxers Senzill",
"Single level wildcard (matches within a directory only)": "Caràcter comodí de nivell singular (aparella sóls en una carpeta)",
"Source Code": "Codi Font",
"Staggered File Versioning": "Versionat de Fitxers Esglaonat",
"Start Browser": "Arrancar Navegador",
"Stopped": "Aturat",
"Support": "Suport",
"Support / Forum": "Suport / Fòrum",
"Sync Protocol Listen Addresses": "Adreça d'escolta del Protocol Sync",
"Synchronization": "Sincronització",
"Syncing": "Synthing",
"Syncthing has been shut down.": "S'ha aturat el synthing.",
"Syncthing includes the following software or portions thereof:": "Syncthing inclou el següent programari o parts dels mateixos:",
"Syncthing is restarting.": "Reiniciant syncthing.",
"Syncthing is upgrading.": "Actualitzant syncthing.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Synthing sembla parat, o hi ha algun problema amb la connexió a Internet. Reintentant...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing sembla estar experimentant un problema en processar la teva sol·licitud. Si us plau, recarregeu el navegador o reinicieu Syncthing si el problema persisteix.",
"The aggregated statistics are publicly available at {%url%}.": "Les estadístiques agregades estan públicament disponibles a {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuració s'ha guardar però no s'ha activat. S'ha de reiniciar el synthing per activar la nova configuració.",
"The device ID cannot be blank.": "El ID del dispositiu no pot estar en blanc.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "El ID del dispositiu per introduir ací es pot trobar al diàleg \"Editar > Mostrar ID\" en l'altre dispositiu. Els espais i les barres son opcionals (s'ignoren).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "L'informe d'ús encriptat s'envia diàriament. Es fa servir per rastrejar plataformes habituals, mides de carpetes i versions de l'aplicació. Si es canvia el conjunt de dades reportades es demanarà amb aquest diàleg de nou.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "El ID del dispositiu introduït no sembla vàlid. Hauria de tenir 52 o 56 caràcters amb lletres i números, els espais i les barres son opcionals.",
"The folder ID cannot be blank.": "El ID del dispositiu no pot estar en blanc.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "El ID de la carpeta ha de ser un identificador curt (64 caràcters o menys) format només per lletres, nombres i el punt (.), barra (-) i barra baixa (_).",
"The folder ID must be unique.": "El ID de la carpeta ha de ser únic.",
"The folder path cannot be blank.": "El camí a la carpeta no pot estar en blanc.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es fan servir els següents intervals: per la primera hora es manté una versió cada 30 segons, pel primer dia es manté una versió cada hora, pel primer cada 30 dies es manté una versió cada dia, fins el màxim d'antiguitat es manté una versió cada setmana.",
"The maximum age must be a number and cannot be blank.": "La màxima antiguitat ha de ser un número i no pot estar en blanc.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Temps màxim en mantenir una versió (en dies, si es deixa en 0 es mantenen les versions per sempre).",
"The number of old versions to keep, per file.": "El nombre de versions antigues que es mantenen per fitxer.",
"The number of versions must be a number and cannot be blank.": "El nombre de versions ha de ser un número i no es pot deixar en blanc.",
"The rescan interval must be a non-negative number of seconds.": "El interval de re-escaneig ha der ser un nombre positiu de segons.",
"The rescan interval must be at least 5 seconds.": "El interval de re-escaneig ha de ser com a mínim de 5 segons.",
"Unknown": "Desconegut",
"Unshared": "No compartit",
"Unused": "No usat",
"Up to Date": "Actualitzat",
"Upgrade To {%version%}": "Actualitzar a {{version}}",
"Upgrading": "Actualitzant",
"Upload Rate": "Tasca de Pujada",
"Use Compression": "Utilitza compressió",
"Use HTTPS for GUI": "Utilitzar HTTPS pel GUI",
"Version": "Versió",
"Versions Path": "Carpeta de les Versions",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Les versions son automàticament eliminades si son més antigues que el màxim d'antiguitat o si excedeixen del nombre de fitxers permesos en un interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quan s'afegeix un nou dispositiu, recorda que aquest dispositiu tambè s'ha d'afegir a l'altre banda.",
"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.": "Quan s'afegeix una nova carpeta recorda que el ID d'aquesta s'utilitza per lligar repositoris entre els dispositius. Es distingeix entre majúscules i minúscules i ha de ser exactament iguals entre tots els dispositius.",
"Yes": "Si",
"You must keep at least one version.": "Has de mantenir com a mínim una versió.",
"full documentation": "documentació sencera",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartir la carpeta \"{{folder}}\"."
}

View File

@@ -13,6 +13,7 @@
"Automatic upgrades": "Automatický upgrade",
"Bugs": "Chyby",
"CPU Utilization": "Využití CPU",
"Changelog": "Changelog",
"Close": "Zavřít",
"Comment, when used at the start of a line": "Komentář, pokud použito na začátku řádku",
"Compression is recommended in most setups.": "Komprese je doporučena pro většinu nastavení.",
@@ -25,6 +26,7 @@
"Device Identification": "Identifikace přístroje",
"Device Name": "Jméno přístroje",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Přístroj {{device}} ({{address}}) žádá o připojení. Chcete ho přidat?",
"Devices": "Přístroje",
"Disconnected": "Odpojeno",
"Documentation": "Dokumentace",
"Download Rate": "Rychlost stahování",
@@ -61,6 +63,7 @@
"Introducer": "Zavaděč",
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
"Keep Versions": "Ponechat verze",
"Last File Received": "Poslední soubor přijat",
"Last File Synced": "Poslední soubor synchronizován",
"Last seen": "Naposledy spatřen",
"Later": "Později",
@@ -79,6 +82,7 @@
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Nesesynchronizováno",
"Out of Sync Items": "Nesesynchronizované položky",
"Outgoing Rate Limit (KiB/s)": "Omezení odchozí rychlosti (KiB/s)",
"Override Changes": "Přepsat změny",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Cesta k adresáři na lokálním počítači. Pokud neexistuje, bude vytvořen. Znak vlnovky (~) může být použit jako zkratka pro",
@@ -110,12 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Bude odesíláno ostatním přístrojům jako dodatečné výchozí jméno.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Bude aktualizováno na jméno které přístroj odesílá pokud nebylo vyplněno.",
"Shutdown": "Vypnout",
"Shutdown Complete": "Vypnutí ukončeno",
"Simple File Versioning": "Jednoduché verze souborů",
"Single level wildcard (matches within a directory only)": "Jednoúrovňový zástupný znak (shody pouze uvnitř adresáře)",
"Source Code": "Zdrojový kód",
"Staggered File Versioning": "Vícenásobné verze souborů",
"Start Browser": "Otevřít prohlížeč",
"Stopped": "Pozastaveno",
"Support": "Podpora",
"Support / Forum": "Podpora / Fórum",
"Sync Protocol Listen Addresses": "Adresa naslouchání synchronizačního protokolu",
"Synchronization": "Synchronizace",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing se restartuje.",
"Syncthing is upgrading.": "Syncthing se aktualizuje.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing se zdá být nefunkční, nebo je problém s připojením k Internetu. Opakuji...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing má nejspíše problém s provedením vašeho požadavku. Pokud problém přetrvává, načtěte znovu data v prohlížeči nebo restartujte Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Souhrnné statistiky jsou veřejně dostupné na {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurace byla uložena, ale není aktivována. Pro aktivaci nové konfigurace je třeba restartovat Syncthing.",
"The device ID cannot be blank.": "ID přístroje nemůže být prázdné.",

View File

@@ -13,6 +13,7 @@
"Automatic upgrades": "Automat. Updates",
"Bugs": "Fehler",
"CPU Utilization": "Prozessorauslastung",
"Changelog": "Versionsinfo",
"Close": "Schließen",
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
"Compression is recommended in most setups.": "Datenkomprimierung ist für die meisten Anwendungen empfohlen",
@@ -25,6 +26,7 @@
"Device Identification": "Gerät Identifikation",
"Device Name": "Gerätename",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Gerät {{device}} ({{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Devices": "Geräte",
"Disconnected": "Getrennt",
"Documentation": "Dokumentation",
"Download Rate": "Download",
@@ -61,6 +63,7 @@
"Introducer": "Verteilergerät",
"Inversion of the given condition (i.e. do not exclude)": "Umkehrung der angegebenen Bedingung (z.B. schließe nicht aus)",
"Keep Versions": "Versionen erhalten",
"Last File Received": "Letzte Datei",
"Last File Synced": "Letzte Änderung",
"Last seen": "Zuletzt online",
"Later": "Später",
@@ -74,11 +77,12 @@
"New Folder": "Neues Verzeichnis",
"No": "Nein",
"No File Versioning": "Keine Dateiversionierung",
"Notice": "Benachrichtigung",
"OK": "Ok",
"Notice": "Hinweis",
"OK": "OK",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Nicht synchronisiert",
"Out of Sync Items": "Nicht synchronisierte Objekte",
"Outgoing Rate Limit (KiB/s)": "Ausgehendes Datenratelimit (KiB/s)",
"Override Changes": "Änderungen überschreiben",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
@@ -101,7 +105,7 @@
"Settings": "Einstellungen",
"Share": "Teilen",
"Share Folder": "Teile Verzeichnis",
"Share Folders With Device": "Teile Verzeichnisse mit Gerät",
"Share Folders With Device": "Teile Verzeichnisse mit diesem Gerät",
"Share With Devices": "Teile mit diesen Geräten",
"Share this folder?": "Dieses Verzeichnis teilen?",
"Shared With": "Geteilt mit",
@@ -110,12 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird als optionaler Standardname an andere Geräte bekannt gegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird auf den Namen aktualisiert, den das Gerät angibt.",
"Shutdown": "Herunterfahren",
"Shutdown Complete": "Vollständig Heruntergefahren",
"Simple File Versioning": "Einfache Dateiversionierung",
"Single level wildcard (matches within a directory only)": "Einzelnes Maskenzeichen (wird für ein einzelnes Verzeichnis verwendet)",
"Source Code": "Quellcode",
"Staggered File Versioning": "Stufenweise Dateiversionierung",
"Start Browser": "Browser starten",
"Stopped": "Gestoppt",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Adresse(n) für das Synchronisierungsprotokoll",
"Synchronization": "Synchronisierung",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing wird neu gestartet",
"Syncthing is upgrading.": "Syncthing wird aktualisiert",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Es scheint als ob Syncthing ein Problem mit der Verarbeitung ihrer Eingabe hat. Bitte laden sie die Seite neu oder führen sie einen Neustart von Syncthing durch, falls das Problem weiterhin besteht.",
"The aggregated statistics are publicly available at {%url%}.": "Die gesammelten Statistiken sind öffentlich verfügbar unter {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu aktivieren.",
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",

View File

@@ -13,6 +13,7 @@
"Automatic upgrades": "Automatic upgrades",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilization",
"Changelog": "Changelog",
"Close": "Close",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
@@ -25,6 +26,7 @@
"Device Identification": "Device Identification",
"Device Name": "Device Name",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Disconnected",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
@@ -61,6 +63,7 @@
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last seen": "Last seen",
"Later": "Later",
@@ -79,6 +82,7 @@
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out Of Sync",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
"Override Changes": "Override Changes",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
@@ -110,12 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
"Shutdown": "Shutdown",
"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)",
"Source Code": "Source Code",
"Staggered File Versioning": "Staggered File Versioning",
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Synchronization",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing is restarting.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The device ID cannot be blank.": "The device ID cannot be blank.",

View File

@@ -1,10 +1,10 @@
{
"API Key": "Clave API",
"About": "Acerca de",
"Add": "Add",
"Add": "Agregar",
"Add Device": "Agregar dispositivo",
"Add Folder": "Agregar repositorio",
"Add new folder?": "Add new folder?",
"Add new folder?": "¿Agregar nuevo repositorio?",
"Address": "Dirección",
"Addresses": "Direcciones",
"Allow Anonymous Usage Reporting?": "Permitir reporte anónimo de uso?",
@@ -13,18 +13,20 @@
"Automatic upgrades": "Actualizaciones automáticas",
"Bugs": "Errores",
"CPU Utilization": "Uso de la CPU",
"Changelog": "Registro de cambios",
"Close": "Cerrar",
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
"Compression is recommended in most setups.": "La compresión de datos es recomendada para la mayoría de las configuraciones.",
"Connection Error": "Error de conexión",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copied from elsewhere": "Copiado de otra parte.",
"Copied from original": "Copiado del original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Derechos de autor © 2014 Jakob Borg y los siguientes colaboradores:",
"Delete": "Suprimir",
"Device ID": "ID del dispositivo",
"Device Identification": "Identificación del dispositivo",
"Device Name": "Nombre del dispositivo",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) se quiere conectar. ¿Agregar nuevo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Documentation": "Documentación",
"Download Rate": "Tasa de descarga",
@@ -52,26 +54,27 @@
"Generate": "Generar",
"Global Discovery": "Búsqueda en internet",
"Global Discovery Server": "Servidor global de identificación",
"Global State": "Estado Global",
"Global State": "Estado global",
"Idle": "Inactivo",
"Ignore": "Ignore",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrones de exclusión",
"Ignore Permissions": "Ignorar permisos",
"Incoming Rate Limit (KiB/s)": "Límite de velocidad de entrada (KiB/s)",
"Introducer": "Introductor",
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (es decir, no excluir)",
"Keep Versions": "Conservar versiones",
"Last File Received": "Último archivo recibido",
"Last File Synced": "Último archivo sincronizado.",
"Last seen": "Visto por ultima vez",
"Later": "Later",
"Later": "Más tarde",
"Latest Release": "Última versión",
"Local Discovery": "Búsqueda en red local",
"Local State": "Estado Local",
"Local State": "Estado local",
"Maximum Age": "Edad máxima",
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
"Multi level wildcard (matches multiple directory levels)": "Carácter comodín multinivel (coincide en el directorio y sus subdirectorios)",
"Never": "Nunca",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Nuevo dispositivo",
"New Folder": "Nuevo repositorio",
"No": "No",
"No File Versioning": "Sin control de versiones de archivos",
"Notice": "Aviso",
@@ -79,6 +82,7 @@
"Offline": "Fuera de linea",
"Online": "En linea",
"Out Of Sync": "Fuera de sincronización",
"Out of Sync Items": "Ítems no sincronizados",
"Outgoing Rate Limit (KiB/s)": "Tasa máxima de envío (KiB/s)",
"Override Changes": "Reemplazar los cambios",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta del repositorio en el equipo local. Será creado si no existe. El carácter tilde (~) puede ser utilizado como atajo de ",
@@ -89,33 +93,35 @@
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
"RAM Utilization": "Utilización de RAM",
"Rescan": "Reescanear",
"Rescan Interval": "Intervalo de Reescaneo",
"Rescan Interval": "Intervalo de reescaneo",
"Restart": "Reiniciar",
"Restart Needed": "Es necesario reiniciar",
"Restarting": "Reiniciando",
"Reused": "Reused",
"Reused": "Reutilizado",
"Save": "Guardar",
"Scanning": "Actualización",
"Select the devices to share this folder with.": "Seleccione los dispositivos con los cuales compartir este repositorio.",
"Select the folders to share with this device.": "Seleccione los repositorios para compartir con este dispositivo.",
"Settings": "Configuración",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Compartir",
"Share Folder": "Compartir repositorio",
"Share Folders With Device": "Compartir repositorios con dispositivo",
"Share With Devices": "Compartir con los dispositivos",
"Share this folder?": "Share this folder?",
"Share this folder?": "¿Compartir este repositorio?",
"Shared With": "Compartido con",
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador corto para el repositorio. Debe ser el mismo en todos los dispositivos del grupo.",
"Show ID": "Mostrar ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado en vez del ID del dispositivo en el estado del grupo. Si dejado en blanco, será usado el nombre sugerido por el dispositivo.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Será sugerido a otros dispositivos como nombre predeterminado opcional.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Si se deja en blanco, será usado el nombre sugerido por el dispositivo.",
"Shutdown": "Apagar",
"Shutdown Complete": "Apagado completado",
"Simple File Versioning": "Versiones simple de archivos",
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Single level wildcard (matches within a directory only)": "Carácter comodín de un solo nivel (coincide sólo dentro de un directorio)",
"Source Code": "Código fuente",
"Staggered File Versioning": "Versiones del archivo escalonado",
"Start Browser": "Iniciar navegador",
"Stopped": "Parado",
"Support": "Soporte",
"Support / Forum": "Soporte / Foro",
"Sync Protocol Listen Addresses": "Dirección de escucha del protocolo de sincronización",
"Synchronization": "Sincronización",
@@ -125,9 +131,10 @@
"Syncthing is restarting.": "Syncthing está reiniciando.",
"Syncthing is upgrading.": "Syncthing se está actualizando.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing parece estar apagado, o hay un problema con su conexión de Internet. Reintentando...",
"The aggregated statistics are publicly available at {%url%}.": "Las estadísticas acumuladas están públicamente disponibles en {{url}}.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing parece estar experimentando un problema al procesar su solicitud. Por favor, recargue el navegador o reinicie Syncthing si el problema persiste.",
"The aggregated statistics are publicly available at {%url%}.": "Las estadísticas acumuladas están disponibles públicamente en {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuración ha sido guardada pero no activada.\nSyncthing debe reiniciarse para activar la nueva configuración.",
"The device ID cannot be blank.": "El ID del dispositivo no puede estar en blanco.",
"The device ID cannot be blank.": "La ID del dispositivo no puede estar en blanco.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "La ID del dispositivo a introducir se puede encontrar en la opción de menú \"Edición > Mostrar ID\" en el otro dispositivo. Espacios y guiones son opcionales (ignorados).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "El informe de uso se envía encriptado diariamente. Se utiliza para hacer un seguimiento de plataformas comunes, tamaño de repositorios y versiones de la aplicación. Si el conjunto de datos cambia será notificado mediante este dialogo nuevamente.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "La ID del dispositivo introducida no es válida. Debe ser una cadena de 52 o 56 caracteres consistente en letras y números, con espacios y guiones opcionales.",
@@ -144,7 +151,7 @@
"The rescan interval must be at least 5 seconds.": "El intervalo de reescaneo debe ser al menos de 5 segundos.",
"Unknown": "Desconocido",
"Unshared": "No compartido",
"Unused": "Unused",
"Unused": "No utilizado",
"Up to Date": "Actualizado",
"Upgrade To {%version%}": "Actualizar a {{version}}",
"Upgrading": "Actualizando",
@@ -159,6 +166,6 @@
"Yes": "Sí",
"You must keep at least one version.": "Debe mantener al menos una versión",
"full documentation": "documentación completa",
"items": "Artículos",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"items": "ítems",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "Clé API",
"About": "À propos",
"Add": "Add",
"Add": "Ajouter",
"Add Device": "Ajouter un périphérique",
"Add Folder": "Ajouter un répertoire",
"Add new folder?": "Add new folder?",
"Add new folder?": "Ajouter un nouveau répertoire ?",
"Address": "Adresse",
"Addresses": "Adresses",
"Allow Anonymous Usage Reporting?": "Autoriser le rapport anonyme de statistiques d'utilisation ?",
@@ -13,6 +13,7 @@
"Automatic upgrades": "Mises à jour automatiques",
"Bugs": "Bugs",
"CPU Utilization": "Utilisation du CPU",
"Changelog": "Nouveautés",
"Close": "Fermer",
"Comment, when used at the start of a line": "Commentaire, lorsque utilisé en début de ligne",
"Compression is recommended in most setups.": "La compression est recommandée pour la plupart des configurations.",
@@ -24,7 +25,8 @@
"Device ID": "ID du périphérique",
"Device Identification": "Identification de l'appareil",
"Device Name": "Nom du périphérique",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "La machine {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette machine ?",
"Devices": "Machines",
"Disconnected": "Déconnecté",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
@@ -45,33 +47,34 @@
"Folder ID": "ID du répertoire",
"Folder Master": "Répertoire maître",
"Folder Path": "Chemin du répertoire",
"Folders": "Dossiers",
"Folders": "Répertoires",
"GUI Authentication Password": "Mot de passe d'authentification GUI",
"GUI Authentication User": "Utilistateur autorisé GUI",
"GUI Authentication User": "Utilisateur autorisé GUI",
"GUI Listen Addresses": "Adresse du GUI",
"Generate": "Générer",
"Global Discovery": "Recherche globale",
"Global Discovery Server": "Serveur global de recherche",
"Global State": "État global",
"Idle": "Au repos",
"Ignore": "Ignore",
"Ignore": "Ignorer",
"Ignore Patterns": "Modèles à éviter",
"Ignore Permissions": "Ignorer les permissions",
"Incoming Rate Limit (KiB/s)": "Limite du débit entrant (KiB/s)",
"Introducer": "Initiateur",
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
"Keep Versions": "Conserver les versions",
"Last File Received": "Dernier fichier reçu",
"Last File Synced": "Dernier fichier synchronisé",
"Last seen": "Dernière apparition",
"Later": "Later",
"Later": "Plus tard",
"Latest Release": "Dernière version",
"Local Discovery": "Recherche locale",
"Local State": "État local",
"Maximum Age": "Ancienneté maximum",
"Multi level wildcard (matches multiple directory levels)": "Astérisque à plusieurs niveaux (correspond aux répertoires et sous-répertoires)",
"Never": "Jamais",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Nouvelle machine",
"New Folder": "Nouveau répertoire",
"No": "Non",
"No File Versioning": "Pas de version de fichier",
"Notice": "Notification",
@@ -79,6 +82,7 @@
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Non synchronisé",
"Out of Sync Items": "Objets non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
"Override Changes": "Écraser les changements",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin du répertoire sur l'ordinateur local. Il sera créé si il n'existe pas. Le caractère tilde (~) peut être utilisé comme raccourci vers",
@@ -97,25 +101,27 @@
"Save": "Sauver",
"Scanning": "En cours de scan",
"Select the devices to share this folder with.": "Sélectionner les machines avec qui partager ce répertoire.",
"Select the folders to share with this device.": "Sélectionner les dossiers à partager avec cette machine.",
"Select the folders to share with this device.": "Sélectionner les répertoires à partager avec cette machine.",
"Settings": "Configuration",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Partage du dossier avec les machines",
"Share With Devices": "Partager avec les machines",
"Share this folder?": "Share this folder?",
"Share": "Partager",
"Share Folder": "Partager le répertoire",
"Share Folders With Device": "Partager des répertoires avec des machines",
"Share With Devices": "Partage avec des machines",
"Share this folder?": "Voulez-vous partager ce répertoire ?",
"Shared With": "Partagé avec",
"Short identifier for the folder. Must be the same on all cluster devices.": "Court identifiant du répertoire. Il doit être le même sur l'ensemble des machines du groupe.",
"Show ID": "Montrer l'ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de la machine dans le groupe. Sera proposé aux autres machines comme nom optionnel par défaut.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Affiché à la place de l'ID de la machine dans le groupe. Si laissé vide, il sera mis à jour par le nom proposé par la machine distante.",
"Shutdown": "Éteindre",
"Shutdown Complete": "Extinction terminée",
"Simple File Versioning": "Suivi simple des versions de fichier",
"Single level wildcard (matches within a directory only)": "Astérisque à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Source Code": "Code source",
"Staggered File Versioning": "Versions échelonnées de fichier",
"Start Browser": "Démarrer le navigateur web",
"Stopped": "Arrêté",
"Support": "Aide",
"Support / Forum": "Aide / Forum",
"Sync Protocol Listen Addresses": "Adresse du protocole de synchronisation",
"Synchronization": "Synchronisation",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing est cours de redémarrage.",
"Syncthing is upgrading.": "Syncthing est cours de mise à jour.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être éteint, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble éprouver des difficultés à appliquer votre requête. Si le problème persiste, veuillez rafraîchir votre navigateur ou redémarrer Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Les statistiques agrégées sont disponibles publiquement à l'adresse {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuration a été sauvée mais pas activée. Syncthing doit redémarrer afin d'activer la nouvelle configuration.",
"The device ID cannot be blank.": "L'ID de l'appareil ne peut être vide.",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le répertoire \"{{folder}}\"."
}

View File

@@ -13,6 +13,7 @@
"Automatic upgrades": "Automatikus frissítés",
"Bugs": "Hibák",
"CPU Utilization": "Processzor használat",
"Changelog": "Changelog",
"Close": "Bezárás",
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
"Compression is recommended in most setups.": "A tömörítés a a legtöbb esetben ajánlott",
@@ -25,6 +26,7 @@
"Device Identification": "Eszköz azonosító",
"Device Name": "Eszköz neve",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Kapcsolat bontva",
"Documentation": "Dokumentáció",
"Download Rate": "Letöltési sebesség",
@@ -61,6 +63,7 @@
"Introducer": "Bevezető",
"Inversion of the given condition (i.e. do not exclude)": "A feltétel ellentéte (pl. ki nem hagyás)",
"Keep Versions": "Megtartott verziók",
"Last File Received": "Last File Received",
"Last File Synced": "Utolsó szinkronizált fájl",
"Last seen": "Utoljára látva",
"Later": "Later",
@@ -79,6 +82,7 @@
"Offline": "Nincs kapcsolat",
"Online": "Kapcsolódva",
"Out Of Sync": "Nincs szinkronban",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
"Override Changes": "Változtatások felülbírálása",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "A mappa elérési útja az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
@@ -110,12 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Az eszköz azonosító helyett jelenik meg. A többi eszközön alapértelmezett névként használható. ",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Az eszköz azonosító helyett jelenik meg. Üresen hagyva az eszköz saját neve lesz használva ",
"Shutdown": "Leállítás",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Egyszerű fájl verziókövetés",
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
"Source Code": "Forráskód",
"Staggered File Versioning": "Többszintű fájl verziókövetés",
"Start Browser": "Böngésző indítása",
"Stopped": "Leállítva",
"Support": "Support",
"Support / Forum": "Támogatás / Fórum",
"Sync Protocol Listen Addresses": "Szinkronizációs protokoll címe",
"Synchronization": "Szinkronizálás",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing újraindul",
"Syncthing is upgrading.": "Syncthing frissül",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Úgy tűnik, hogy a Syncthing nem működik, vagy valami probléma van az hálózati kapcsolattal. Újra próbálom...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Az összevont statisztikák nyilvánosan elérhetők a {{url}} címen.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A beállítások elmentésre kerültek, de nem lettek aktiválva. Indítsd újra a Syncthing-et, hogy aktiváld őket.",
"The device ID cannot be blank.": "Az eszköz azonosító nem lehet üres.",

View File

@@ -13,6 +13,7 @@
"Automatic upgrades": "Aggiornamenti automatici",
"Bugs": "Bug",
"CPU Utilization": "Utilizzo CPU",
"Changelog": "Changelog",
"Close": "Chiudi",
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
"Compression is recommended in most setups.": "La compressione è raccomandata nella maggior parte delle configurazioni.",
@@ -25,6 +26,7 @@
"Device Identification": "Identificazione Dispositivo",
"Device Name": "Nome Dispositivo",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Il dispositivo {{device}} ({{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
"Devices": "Dispositivi",
"Disconnected": "Disconnesso",
"Documentation": "Documentazione",
"Download Rate": "Velocità Download",
@@ -61,6 +63,7 @@
"Introducer": "Introduttore",
"Inversion of the given condition (i.e. do not exclude)": "Inversione della condizione indicata (ad es. non escludere)",
"Keep Versions": "Versioni Mantenute",
"Last File Received": "Ultimo File Ricevuto",
"Last File Synced": "Ultimo File Sincronizzato",
"Last seen": "Ultima connessione",
"Later": "Più Tardi",
@@ -79,6 +82,7 @@
"Offline": "Non in linea",
"Online": "In linea",
"Out Of Sync": "Non Sincronizzati",
"Out of Sync Items": "Elementi Non Sincronizzati",
"Outgoing Rate Limit (KiB/s)": "Limite Velocità in Uscita (KiB/s)",
"Override Changes": "Ignora Modifiche",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Percorso della cartella nel computer locale. Verrà creata se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
@@ -110,12 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visibile al posto dell'ID Dispositivo nello stato del cluster. Negli altri dispositivi verrà presentato come nome predefinito opzionale.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visibile al posto dell'ID Dispositivo nello stato del cluster. Se viene lasciato vuoto, verrà utilizzato il nome proposto dal dispositivo.",
"Shutdown": "Arresta",
"Shutdown Complete": "Arresto Eseguito",
"Simple File Versioning": "Controllo Versione Semplice",
"Single level wildcard (matches within a directory only)": "Metacarattere di singolo livello (corrisponde solo all'interno di una cartella)",
"Source Code": "Codice Sorgente",
"Staggered File Versioning": "Controllo Versione Cadenzato",
"Start Browser": "Avvia Browser",
"Stopped": "Fermato",
"Support": "Supporto",
"Support / Forum": "Supporto / Forum",
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
"Synchronization": "Sincronizzazione",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Riavvio di Syncthing in corso.",
"Syncthing is upgrading.": "Aggiornamento di Syncthing in corso.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing sembra inattivo, oppure c'è un problema con la tua connessione a Internet. Nuovo tentativo…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Le statistiche aggregate sono disponibili pubblicamente su {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configurazione è stata salvata ma non attivata. Devi riavviare Syncthing per attivare la nuova configurazione.",
"The device ID cannot be blank.": "L'ID del dispositivo non può essere vuoto.",

View File

@@ -1,10 +1,10 @@
{
"API Key": "API raktas",
"About": "Apie programą",
"Add": "Add",
"Add": "Pridėti",
"Add Device": "Pridėti įrenginį",
"Add Folder": "Pridėti aplanką",
"Add new folder?": "Add new folder?",
"Add new folder?": "Pridėti naują aplanką?",
"Address": "Adresas",
"Addresses": "Adresai",
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
@@ -13,6 +13,7 @@
"Automatic upgrades": "Automatiniai atnaujinimai",
"Bugs": "Klaidos",
"CPU Utilization": "Procesoriaus panaudojimas",
"Changelog": "Pasikeitimai",
"Close": "Uždaryti",
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
"Compression is recommended in most setups.": "Daugumoje atvejų spaudimas rekomenduojamas.",
@@ -24,7 +25,8 @@
"Device ID": "Įrenginio ID",
"Device Identification": "Įrenginio identifikacija",
"Device Name": "Įrenginio pavadinimas",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Įrenginys {{device}} ({{address}}) nori prisijungti. Pridėti naują įrenginį?",
"Devices": "Įrenginiai",
"Disconnected": "Atsijungęs",
"Documentation": "Aprašymas",
"Download Rate": "Parsisiuntimo greitis",
@@ -54,24 +56,25 @@
"Global Discovery Server": "Visuotinio matomumo serveris",
"Global State": "Visuotinė būsena",
"Idle": "Laisvas",
"Ignore": "Ignore",
"Ignore": "Ignoruoti",
"Ignore Patterns": "Nepaisyti šablonų",
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
"Incoming Rate Limit (KiB/s)": "Įeinančio srauto maksimalus greitis (KiB/s)",
"Introducer": "Supažindintojas",
"Inversion of the given condition (i.e. do not exclude)": "Apversti sąlygas (pvz.: nenustoti naudoti)",
"Keep Versions": "Saugojamų versijų kiekis",
"Last File Received": "Paskutinis priimtas failas",
"Last File Synced": "Paskutinis gautas failas",
"Last seen": "Paskutinį kartą matytas",
"Later": "Later",
"Later": "Vėliau",
"Latest Release": "Paskutinė versija",
"Local Discovery": "Vietinis matomumas",
"Local State": "Vietinė būsena",
"Maximum Age": "Maksimalus amžius",
"Multi level wildcard (matches multiple directory levels)": "Keletos lygių pakaitos (atitinka keletą direktorijų lygių)",
"Never": "Niekada",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Naujas įrenginys",
"New Folder": "Naujas aplankas",
"No": "Ne",
"No File Versioning": "Nėra versijų valdymo",
"Notice": "Įspėjimas",
@@ -79,6 +82,7 @@
"Offline": "Atsijungęs",
"Online": "Prisijungęs",
"Out Of Sync": "Nesutikrinta",
"Out of Sync Items": "Nesutikrinta",
"Outgoing Rate Limit (KiB/s)": "Išeinančio srauto maksimalus greitis (KiB/s)",
"Override Changes": "Perrašyti pakeitimus",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Kelias iki aplanko šiame kompiuteryje. Bus sukurtas, jei neegzistuoja. Tildės simbolis (~) gali būti naudojamas kaip trumpinys",
@@ -99,23 +103,25 @@
"Select the devices to share this folder with.": "Pasirinkite įrenginius, su kuriais dalinsitės šį aplanką.",
"Select the folders to share with this device.": "Pasirinkite aplankus kuriais norite dalintis su šiuo įrenginiu.",
"Settings": "Nustatymai",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Dalintis",
"Share Folder": "Dalintis aplanku",
"Share Folders With Device": "Dalintis aplankalais su šiuo įrenginiu",
"Share With Devices": "Dalintis su įrenginiais",
"Share this folder?": "Share this folder?",
"Share this folder?": "Dalintis šiuo aplanku?",
"Shared With": "Dalinamasi su",
"Short identifier for the folder. Must be the same on all cluster devices.": "Trumpas aplanko identifikatorius. Privalo būti toks pat visuose įrenginiuose.",
"Show ID": "Rodyti ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Grupės būsenoje rodomas vietoje įrenginio vardo. Kiti įrenginiai matys kaip pasirinktinį vardą.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Grupės būsenoje rodomas vietoje įrenginio vardo. Bus atnaujintas į įrenginio vardą jei nieko neįrašysite.",
"Shutdown": "Išjungti",
"Shutdown Complete": "Sėkmingai išjungta",
"Simple File Versioning": "Supaprastintas versijų valdymas",
"Single level wildcard (matches within a directory only)": "Vieno lygio pakaitos (atitinka tik vieną direktorijos lygį)",
"Source Code": "Išeities kodas",
"Staggered File Versioning": "Pakopinis versijų valdymas",
"Start Browser": "Paleisti naršyklę",
"Stopped": "Sustabdyta",
"Support": "Pagalba",
"Support / Forum": "Palaikymas / Diskusijos",
"Sync Protocol Listen Addresses": "Sutapatinimo taisyklių adresas",
"Synchronization": "Sutapatinimas",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing perleidžiamas",
"Syncthing is upgrading.": "Syncthing atsinaujina.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing išjungta arba problemos su Interneto ryšių. Bandoma iš naujo...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing turi problemų su jūsų užklausa. Perkraukite puslapį arba perkraukite Syncthing jei problema nedings.",
"The aggregated statistics are publicly available at {%url%}.": "Naudojimosi ataskaitą galite peržiūrėti adresu: {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Nauji nustatymai išsaugoti, bet neaktyvuoti. Perleiskite Syncthing programą iš naujo norėdami įgalinti naujus nustatymus.",
"The device ID cannot be blank.": "Įrenginio ID negali būti tuščias.",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
"full documentation": "pilna dokumentacija",
"items": "įrašai",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\""
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API-nøkkel",
"About": "Om",
"Add": "Add",
"Add": "Legg til",
"Add Device": "Legg Til Enhet",
"Add Folder": "Legg Til Mappe",
"Add new folder?": "Add new folder?",
"Add new folder?": "Legg til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
@@ -13,23 +13,25 @@
"Automatic upgrades": "Automatiske oppdateringer",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnyttelse",
"Changelog": "Endringslog",
"Close": "Lukk",
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
"Compression is recommended in most setups.": "Komprimering er anbefalt i de fleste tilfeller.",
"Connection Error": "Tilkoblingsfeil",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copied from elsewhere": "Kopiert fra et annet sted",
"Copied from original": "Kopiert fra original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg og følgende Bidragsytere:",
"Delete": "Slett",
"Device ID": "Enhet ID",
"Device Identification": "Enhetskjennemerke",
"Device Name": "Navn På Enhet",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enhet {{device}} ({{address}}) ønsker å koble seg til. Legg til ny enhet?",
"Devices": "Enheter",
"Disconnected": "Frakoblet",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Lasted ned",
"Downloading": "Laster ned",
"Edit": "Rediger",
"Edit Device": "Rediger Enhet",
"Edit Folder": "Rediger Mappe",
@@ -45,7 +47,7 @@
"Folder ID": "Mappe ID",
"Folder Master": "Styrende Mappe",
"Folder Path": "Mappeplassering",
"Folders": "Folders",
"Folders": "Mapper",
"GUI Authentication Password": "GUI Passord",
"GUI Authentication User": "GUI Bruker",
"GUI Listen Addresses": "GUI Lytteadresse",
@@ -54,24 +56,25 @@
"Global Discovery Server": "Global Søkemotor",
"Global State": "Global Tilstand",
"Idle": "Pause",
"Ignore": "Ignore",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatelsesmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkommende Hastighetsbegrensning (KiB/s)",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikke ekskluder)",
"Keep Versions": "Behold Versjoner",
"Last File Synced": "Last File Synced",
"Last File Received": "Sist Mottatte Fil",
"Last File Synced": "Sist Synkroniserte Fil",
"Last seen": "Sist sett",
"Later": "Later",
"Later": "Senere",
"Latest Release": "Nyeste Versjon",
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ny Enhet",
"New Folder": "Ny Mappe",
"No": "Nei",
"No File Versioning": "Ingen Versjonskontroll",
"Notice": "Merknad",
@@ -79,6 +82,7 @@
"Offline": "Frakobla",
"Online": "Tilkobla",
"Out Of Sync": "Ikke Synkronisert",
"Out of Sync Items": "Ikke Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgående Hastighetsbegrensning (KiB/s)",
"Override Changes": "Overstyr Endringer",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Plasseringen av mappen på datamaskinen. Blir opprettet om den ikke finnes. Krøllstrektegnet (~) kan brukes som forkortelse for",
@@ -93,29 +97,31 @@
"Restart": "Omstart",
"Restart Needed": "Omstart Kreves",
"Restarting": "Starter På Ny",
"Reused": "Reused",
"Reused": "Gjenbrukt",
"Save": "Lagre",
"Scanning": "Skanner",
"Select the devices to share this folder with.": "Velg enhetene du vil dele denne mappen med.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Velg hvilke mapper som skal deles med denne enheten.",
"Settings": "Innstillinger",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share": "Del",
"Share Folder": "Del Mappe",
"Share Folders With Device": "Del Mapper Med Enhet",
"Share With Devices": "Del Med Enheter",
"Share this folder?": "Share this folder?",
"Share this folder?": "Dele denne mappen?",
"Shared With": "Del Med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappen. Må være det samme på alle enheter i en gruppe.",
"Show ID": "Vis ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vis i stedet for Eining ID i gruppestatus. Vil bli kringkastet til andre enheter som et valgfritt standardnavn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i stedet for Mappe-ID i gruppestatus. Vil bli oppdatert til navnet enheten kringkaster dersom tomt.",
"Shutdown": "Slå Av",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Enkel Versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søker kun i en mappe)",
"Source Code": "Kildekode",
"Staggered File Versioning": "Forskjøvet Versjonskontroll",
"Start Browser": "Start Nettleser",
"Stopped": "Stoppa",
"Support": "Brukerstøtte",
"Support / Forum": "Brukerstøtte / Forum",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Synchronization": "Synkronisering",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing starter på ny.",
"Syncthing is upgrading.": "Syncthing oppgraderer.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ut til å være nede, eller så er det et problem med nettforbindelsen din. Prøver på ny …",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Samlet statistikk er åpent tilgjengelig på {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Innstillingene har blitt lagret men ikke aktivert. Syncthing må starte på ny for å aktivere de nye innstillingene.",
"The device ID cannot be blank.": "Enhets-ID kan ikke være tom.",
@@ -143,8 +150,8 @@
"The rescan interval must be a non-negative number of seconds.": "Antall sekund i skanneintervallet kan ikke være negativt.",
"The rescan interval must be at least 5 seconds.": "Skanneintervallet må være minst 5 sekund.",
"Unknown": "Ukjent",
"Unshared": "Unshared",
"Unused": "Unused",
"Unshared": "Ikke delt",
"Unused": "Ikke i bruk",
"Up to Date": "Oppdatert",
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrading": "Oppgraderer",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Du må beholde minst én versjon",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API-sleutel",
"About": "Over",
"Add": "Add",
"Add": "Toevoegen",
"Add Device": "Toestel toevoegen",
"Add Folder": "Folder toevoegen",
"Add new folder?": "Add new folder?",
"Add new folder?": "Nieuwe folder toevoegen?",
"Address": "Adres",
"Addresses": "Adressen",
"Allow Anonymous Usage Reporting?": "Bijhouden van anonieme gebruikers statistieken toestaan?",
@@ -13,23 +13,25 @@
"Automatic upgrades": "Automatisch bijwerken",
"Bugs": "Fouten",
"CPU Utilization": "CPU Gebruik",
"Changelog": "Changelog",
"Close": "Sluiten",
"Comment, when used at the start of a line": "Commentaar, indien gebruikt aan het begin van de lijn",
"Compression is recommended in most setups.": "Gegevenscompressie is aan te raden in de meeste situaties.",
"Connection Error": "Verbindingsfout",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copied from elsewhere": "Van elders gekopieerd",
"Copied from original": "Gekopieerd van het origineel",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg en de onderstaande bijdragers:",
"Delete": "Verwijderen",
"Device ID": "Toestel ID",
"Device Identification": "Toestel identificatie",
"Device Name": "Naam toestel",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Het toestel {{device}} ({{address}}) wenst te verbinden. Dit toestel toevoegen?",
"Devices": "Devices",
"Disconnected": "Niet Verbonden",
"Documentation": "Documentatie",
"Download Rate": "Downloadsnelheid",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Gedownload",
"Downloading": "Bezig met downloaden",
"Edit": "Bewerk",
"Edit Device": "Toestel aanpassen",
"Edit Folder": "Folder aanpassen",
@@ -54,14 +56,15 @@
"Global Discovery Server": "Globale zoekserver",
"Global State": "Globale status",
"Idle": "Inactief",
"Ignore": "Ignore",
"Ignore": "Negeren",
"Ignore Patterns": "Te negeren patronen",
"Ignore Permissions": "Rechten negeren",
"Incoming Rate Limit (KiB/s)": "Download snelheidslimiet (KiB/s)",
"Introducer": "Introductietoestel",
"Inversion of the given condition (i.e. do not exclude)": "Inversie van de gegeven voorwaarde (bv. niet uitsluiten)",
"Keep Versions": "Versies behouden",
"Last File Synced": "Last File Synced",
"Last File Received": "Last File Received",
"Last File Synced": "Laatste Gesynchroniseerde Bestand",
"Last seen": "Laatst gezien op",
"Later": "Later",
"Latest Release": "Laatste uitgave",
@@ -70,8 +73,8 @@
"Maximum Age": "Maximum leeftijd",
"Multi level wildcard (matches multiple directory levels)": "Wildcard op meerder niveaus (toepasbaar op meerdere niveaus van folders)",
"Never": "Nooit",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Nieuw Toestel",
"New Folder": "Nieuwe Folder",
"No": "Nee",
"No File Versioning": "Geen versiebeheer",
"Notice": "Notificatie",
@@ -79,6 +82,7 @@
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Niet gesynchroniseerd",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Uitgaande snelheidslimiet (KiB/s)",
"Override Changes": "Veranderingen overschrijven",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Locatie van de folder op de lokale computer. Zal aangemaakt worden wanneer deze niet bestaat. De tilde (~) kan gebruikt in plaats van",
@@ -93,29 +97,31 @@
"Restart": "Herstart",
"Restart Needed": "Herstart nodig",
"Restarting": "Herstarten",
"Reused": "Reused",
"Reused": "Hergebruikt",
"Save": "Bewaar",
"Scanning": "Aan het zoeken",
"Select the devices to share this folder with.": "Selecteer de toestellen om deze folder mee te delen.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Selecteer de folders om met dit toestel te delen.",
"Settings": "Instellingen",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share": "Delen",
"Share Folder": "Folder Delen",
"Share Folders With Device": "Folders delen met toestellen",
"Share With Devices": "Delen met toestellen",
"Share this folder?": "Share this folder?",
"Share this folder?": "Deze folder delen?",
"Shared With": "Gedeeld met",
"Short identifier for the folder. Must be the same on all cluster devices.": "Korte aanduiding voor deze folder. Moet dezelfde zijn op alle toestellen in de cluster.",
"Show ID": "Toon ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wordt getoond in plaats van de toestel ID in de cluster staat. Wordt doorgegeven aan andere toestellen as een bijkomende standaard toestelnaam.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wordt getoond in plaats van de toestel ID in de cluster staat. Wanneer leeg wordt deze aangepast met de naam aangekondigd door het toestel.",
"Shutdown": "Sluit af",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Eenvoudig versiebeheer",
"Single level wildcard (matches within a directory only)": "Wildcard op enkel niveau (toepasbaar binnen een enkele folder)",
"Source Code": "Broncode",
"Staggered File Versioning": "Gelaagd versiebeheer",
"Start Browser": "Start browser",
"Stopped": "Gestopt",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Synchronisatie protocol luister adres",
"Synchronization": "Synchronisatie",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing is aan het herstarten.",
"Syncthing is upgrading.": "Syncthing is aan het upgraden.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing lijkt afgesloten te zijn, of er is een verbindingsprobleem met het internet. Nieuwe poging....",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing heeft problemen bij het verwerken van je vraag. Gelieve de pagina opnieuw te laden of Syncthing te herstarten wanneer het probleem blijft opduiken.",
"The aggregated statistics are publicly available at {%url%}.": "The verzamelde statistieken zijn publiek beschikbaar op {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "De configuratie is opslagen maar nog niet actief. Syncthing moet opnieuw opgestart worden om de nieuwe configuratie te activeren.",
"The device ID cannot be blank.": "Het toestel ID mag niet leeg zijn.",
@@ -143,8 +150,8 @@
"The rescan interval must be a non-negative number of seconds.": "De scanfrequentie moet een positief getal in seconden zijn.",
"The rescan interval must be at least 5 seconds.": "De scanfrequentie moet minimaal 5 seconden zijn.",
"Unknown": "Onbekend",
"Unshared": "Unshared",
"Unused": "Unused",
"Unshared": "Niet gedeeld",
"Unused": "Ongebruikt",
"Up to Date": "Gesynchroniseerd",
"Upgrade To {%version%}": "Upgrade naar {{version}}",
"Upgrading": "Bezig met upgrade",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
"full documentation": "volledige documentatie",
"items": "objecten",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de folder \"{{folder}}\" delen."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API-nøkkel",
"About": "Om",
"Add": "Add",
"Add": "Legg til",
"Add Device": "Legg Til Eining",
"Add Folder": "Legg Til Mappe",
"Add new folder?": "Add new folder?",
"Add new folder?": "Legg til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukardata?",
@@ -13,23 +13,25 @@
"Automatic upgrades": "Automatiske oppdateringar",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnytting",
"Changelog": "Endringslog",
"Close": "Lukk",
"Comment, when used at the start of a line": "Kommentar, når det vert brukt i starten av ei linje.",
"Compression is recommended in most setups.": "Komprimering er tilrådd i dei fleste høve.",
"Connection Error": "Tilkoplingsfeil",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copied from elsewhere": "Kopiert frå ein annan stad",
"Copied from original": "Kopiert frå original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg og følgjande Bidragsytarar:",
"Delete": "Slett",
"Device ID": "Eining ID",
"Device Identification": "Einingskjennemerke",
"Device Name": "Namn På Eining",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Eining {{device}} ({{address}}) vil kople seg til. Legg til eining?",
"Devices": "Einingar",
"Disconnected": "Fråkopla",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Lasta ned",
"Downloading": "Lastar ned",
"Edit": "Rediger",
"Edit Device": "Rediger Eining",
"Edit Folder": "Rediger Mappe",
@@ -45,7 +47,7 @@
"Folder ID": "Mappe ID",
"Folder Master": "Styrande Mappe",
"Folder Path": "Mappeplassering",
"Folders": "Folders",
"Folders": "Mapper",
"GUI Authentication Password": "GUI Passord",
"GUI Authentication User": "GUI Brukar",
"GUI Listen Addresses": "GUI Lytteadresse",
@@ -54,24 +56,25 @@
"Global Discovery Server": "Global Søkjemotor",
"Global State": "Global Tilstand",
"Idle": "Pause",
"Ignore": "Ignore",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatingsmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkomande Hastigheitsgrense (KiB/s)",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikkje ekskluder)",
"Keep Versions": "Behald Versjonar",
"Last File Synced": "Last File Synced",
"Last File Received": "Sist Mottatte Fil",
"Last File Synced": "Sist Synkroniserte Fil",
"Last seen": "Sist sett",
"Later": "Later",
"Later": "Seinare",
"Latest Release": "Nyaste Versjon",
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søkjer på fleire mappenivå)",
"Never": "Aldri",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ny Eining",
"New Folder": "Ny Mappe",
"No": "Nei",
"No File Versioning": "Ingen Versjonskontroll",
"Notice": "Merknad",
@@ -79,6 +82,7 @@
"Offline": "Fråkopla",
"Online": "Tilkopla",
"Out Of Sync": "Ikkje Synkronisert",
"Out of Sync Items": "Ikkje Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgåande Hastigheitsgrense (KiB/s)",
"Override Changes": "Overstyr Endringar",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Plasseringa av mappa på datamaskinen. Vert oppretta om ho ikkje finst. Krøllstrekteiknet (~) kan brukast som forkorting for",
@@ -93,29 +97,31 @@
"Restart": "Omstart",
"Restart Needed": "Omstart Trengs",
"Restarting": "Startar På Ny",
"Reused": "Reused",
"Reused": "Gjenbrukt",
"Save": "Lagre",
"Scanning": "Skannar",
"Select the devices to share this folder with.": "Vel einingane du vil dela denne mappa med.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Vel mappene du vil dela med denne eininga.",
"Settings": "Innstillingar",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share": "Del",
"Share Folder": "Del Mappe",
"Share Folders With Device": "Del Mapper Med Eining",
"Share With Devices": "Del Med Einingar",
"Share this folder?": "Share this folder?",
"Share this folder?": "Del denne mappa?",
"Shared With": "Delt Med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappa. Må vera det same på alle einingar i ei gruppe.",
"Show ID": "Vis ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vis i staden for Eining ID i gruppestatus. Vil verta kringkasta til andre einingar som eit valfritt standardnamn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i staden for Mappe-ID i gruppestatus. Vil verta oppdatert til namnet eininga kringkastar dersom tomt.",
"Shutdown": "Slå Av",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Enkel Versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søkjer kun i ei mappe)",
"Source Code": "Kildekode",
"Staggered File Versioning": "Forskyvd Versjonskontroll",
"Start Browser": "Start Nettlesar",
"Stopped": "Stoppa",
"Support": "Brukarstøtte",
"Support / Forum": "Brukarstøtte / Forum",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Synchronization": "Synkronisering",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing startar på ny.",
"Syncthing is upgrading.": "Syncthing oppgraderer.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ut til å vera nede, eller så er det eit problem med nettilkoplinga di. Prøvar på ny …",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Samla statistikk er opent tilgjengeleg på {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Instillingane har blitt lagra men ikkje aktivert. Syncthing må starta på ny for å aktivera dei nye instillingane.",
"The device ID cannot be blank.": "Eining ID kan ikkje vera tom.",
@@ -143,8 +150,8 @@
"The rescan interval must be a non-negative number of seconds.": "Talet på sekund i skanneintervallet kan ikkje vera negativt.",
"The rescan interval must be at least 5 seconds.": "Skanneintervallet må vera minst 5 sekund.",
"Unknown": "Ukjent",
"Unshared": "Unshared",
"Unused": "Unused",
"Unshared": "Ikkje delt",
"Unused": "Ubrukt",
"Up to Date": "Oppdatert",
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrading": "Oppgraderer",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vil dela mappa \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "Klucz API",
"About": "O Syncthing",
"Add": "Add",
"Add": "Dodaj",
"Add Device": "Dodaj urządzenie",
"Add Folder": "Dodaj folder",
"Add new folder?": "Add new folder?",
"Add new folder?": "Dodać nowy folder?",
"Address": "Adres",
"Addresses": "Adresy",
"Allow Anonymous Usage Reporting?": "Zezwalaj na anonimowe statystyki użycia",
@@ -13,6 +13,7 @@
"Automatic upgrades": "Automatyczne aktualizacje",
"Bugs": "Błędy",
"CPU Utilization": "Użycie CPU",
"Changelog": "Historia zmian",
"Close": "Zamknij",
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
"Compression is recommended in most setups.": "Kompresja jest zalecana w większości przypadków",
@@ -25,11 +26,12 @@
"Device Identification": "Identyfikator urządzenia",
"Device Name": "Nazwa urządzenia",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Urządzenia",
"Disconnected": "Rozłączony",
"Documentation": "Dokumentacja",
"Download Rate": "Prędkość pobierania",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Pobrane",
"Downloading": "Pobieranie",
"Edit": "Edytuj",
"Edit Device": "Edytuj urządzenie",
"Edit Folder": "Edytuj folder",
@@ -45,7 +47,7 @@
"Folder ID": "ID folderu",
"Folder Master": "Główny folder",
"Folder Path": "Ścieżka folderu",
"Folders": "Folders",
"Folders": "Foldery",
"GUI Authentication Password": "Hasło",
"GUI Authentication User": "Użytkownik",
"GUI Listen Addresses": "Adres nasłuchiwania",
@@ -61,7 +63,8 @@
"Introducer": "Wprowadzający",
"Inversion of the given condition (i.e. do not exclude)": "Odwrócenie podanego wzorca (np. nie wykluczaj)",
"Keep Versions": "Zachowuj wersje",
"Last File Synced": "Last File Synced",
"Last File Received": "Ostatni otrzymany plik",
"Last File Synced": "Ostatni zsynchronizowany plik",
"Last seen": "Ostatnio widziany",
"Later": "Later",
"Latest Release": "Najnowsza wersja",
@@ -70,8 +73,8 @@
"Maximum Age": "Maksymalny wiek",
"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",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Nowe urządzenie",
"New Folder": "Nowy folder",
"No": "Nie",
"No File Versioning": "Bez wersjonowania pliku",
"Notice": "Wskazówka",
@@ -79,6 +82,7 @@
"Offline": "Rozłączony",
"Online": "Połączony",
"Out Of Sync": "Niezsynchronizowane",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Ograniczenie prędkości wysyłania (KiB/s)",
"Override Changes": "Nadpisz zmiany",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
@@ -97,7 +101,7 @@
"Save": "Zapisz",
"Scanning": "Skanowanie",
"Select the devices to share this folder with.": "Wybierz urządzenie, któremu udostępnić folder.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Wybierz foldery do współdzielenia z tym urządzeniem.",
"Settings": "Ustawienia",
"Share": "Share",
"Share Folder": "Share Folder",
@@ -110,12 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Pokazane w statusie zamiast ID urządzenia.Zostanie wysłane do innych urządzeń jako opcjonalna domyślna nazwa.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Pokazane w statusie zamiast ID urządzenia. Zostanie zaktualizowane do nazwy urządzenia jeżeli pozostanie puste.",
"Shutdown": "Wyłącz",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Proste wersjonowanie pliku",
"Single level wildcard (matches within a directory only)": "Wieloznaczność na poziomie plików (uwzględnia nazwy plików)",
"Source Code": "Kod źródłowy",
"Staggered File Versioning": "Rozbudowane wersjonowanie pliku",
"Start Browser": "Uruchom przeglądarkę",
"Stopped": "Zatrzymany",
"Support": "Wsparcie",
"Support / Forum": "Wsparcie / Forum",
"Sync Protocol Listen Addresses": "Adres nasłuchu protokołu synchronizacji",
"Synchronization": "Synchronizacja",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Restart Syncthing",
"Syncthing is upgrading.": "Aktualizowanie Syncthing",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing wydaje się być wyłączony lub jest problem z twoim połączeniem internetowym. Próbuje ponownie...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Zebrane statystyki są publicznie dostępna pod adresem {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfiguracja została zapisana lecz nie jest aktywna. Syncthing musi zostać zrestartowany aby aktywować nową konfiguracje.",
"The device ID cannot be blank.": "ID urządzenia nie może być puste.",
@@ -140,11 +147,11 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
"The number of old versions to keep, per file.": "Liczba wersji pliku do zachowania.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być liczbą i nie może być pusta.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
"The rescan interval must be at least 5 seconds.": "Interwał skanowania musi wynosić co najmniej 5 sekund.",
"Unknown": "Nieznany",
"Unshared": "Unshared",
"Unused": "Unused",
"Unused": "Nieużywane",
"Up to Date": "Aktualny",
"Upgrade To {%version%}": "Aktualizuj do {{version}}",
"Upgrading": "Aktualizowanie",

View File

@@ -1,10 +1,10 @@
{
"API Key": "Chave da API",
"About": "Acerca da aplicação",
"Add": "Add",
"Add": "Adicionar",
"Add Device": "Adicionar dispositivo",
"Add Folder": "Adicionar pasta",
"Add new folder?": "Add new folder?",
"Add new folder?": "Adicionar nova pasta?",
"Address": "Endereço",
"Addresses": "Endereços",
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anónimos de utilização?",
@@ -13,6 +13,7 @@
"Automatic upgrades": "Actualizações automáticas",
"Bugs": "Erros",
"CPU Utilization": "Utilização da CPU",
"Changelog": "Registo de alterações",
"Close": "Fechar",
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
"Compression is recommended in most setups.": "A compressão é recomendada na maior parte dos casos.",
@@ -24,7 +25,8 @@
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer conectar-se. Adiciono este novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
@@ -54,24 +56,25 @@
"Global Discovery Server": "Servidor da busca global",
"Global State": "Estado global",
"Idle": "Em espera",
"Ignore": "Ignore",
"Ignore": "Ignorar",
"Ignore Patterns": "Padrões de exclusão",
"Ignore Permissions": "Ignorar permissões",
"Incoming Rate Limit (KiB/s)": "Limite de velocidade de recepção (KiB/s)",
"Introducer": "Apresentador",
"Inversion of the given condition (i.e. do not exclude)": "Inversão de uma dada condição (ou seja, não excluir)",
"Keep Versions": "Manter versões",
"Last File Received": "Último ficheiro recebido",
"Last File Synced": "Último ficheiro sincronizado",
"Last seen": "Última vez que foi verificado",
"Later": "Later",
"Later": "Mais tarde",
"Latest Release": "Última versão",
"Local Discovery": "Busca local",
"Local State": "Estado local",
"Maximum Age": "Idade máxima",
"Multi level wildcard (matches multiple directory levels)": "Caractere polivalente multi-nível (faz corresponder a vários níveis de pastas)",
"Never": "Nunca",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Novo dispositivo",
"New Folder": "Nova pasta",
"No": "Não",
"No File Versioning": "Sem gestão de versões de ficheiros",
"Notice": "Avisos",
@@ -79,6 +82,7 @@
"Offline": "Desconectado",
"Online": "Conectado",
"Out Of Sync": "Não sincronizado",
"Out of Sync Items": "Itens não sincronizados",
"Outgoing Rate Limit (KiB/s)": "Limite da velocidade de envio (KiB/s)",
"Override Changes": "Sobrepor alterações",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Caminho para a pasta no computador local. Será criada, caso não exista. O caractere (~) pode ser utilizado como atalho para",
@@ -99,23 +103,25 @@
"Select the devices to share this folder with.": "Seleccione os dispositivos com os quais vai partilhar esta pasta.",
"Select the folders to share with this device.": "Seleccione as pastas a partilhar com este dispositivo.",
"Settings": "Configurações",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Partilhar",
"Share Folder": "Partilhar pasta",
"Share Folders With Device": "Partilhar pastas com dispositivo",
"Share With Devices": "Partilhar com os dispositivos",
"Share this folder?": "Share this folder?",
"Share this folder?": "Partilhar esta pasta?",
"Shared With": "Partilhado com",
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curto para a pasta. Tem que ser igual em todos os dispositivos do grupo.",
"Show ID": "Mostrar ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Apresentado ao invés do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome pré-definido opcional.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Apresentado ao invés do ID do dispositivo no indicador de estado do grupo. Será actualizado para o nome que o dispositivo divulga, se for deixado em branco.",
"Shutdown": "Desligar",
"Shutdown Complete": "Encerramento completado",
"Simple File Versioning": "Gestão de versões de ficheiros simples",
"Single level wildcard (matches within a directory only)": "Caractere polivalente de um só nível (faz corresponder apenas dentro de uma pasta)",
"Source Code": "Código fonte",
"Staggered File Versioning": "Gestão de versões de ficheiros escalonada",
"Start Browser": "Iniciar navegador",
"Stopped": "Parado",
"Support": "Suporte",
"Support / Forum": "Suporte / Fórum",
"Sync Protocol Listen Addresses": "Endereços de escuta do protocolo de sincronização",
"Synchronization": "Sincronização",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "O Syncthing está a reiniciar.",
"Syncthing is upgrading.": "O Syncthing está a actualizar-se.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "O Syncthing parece estar em baixo, ou então existe um problema com a sua ligação à Internet. Tentando novamente...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "O Syncthing parece estar com problemas em processar o seu pedido. Tente recarregar a página ou reiniciar o Syncthing, se o problema persistir.",
"The aggregated statistics are publicly available at {%url%}.": "As estatísticas agrupadas estão disponíveis publicamente em {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi gravada mas não activada. O Syncthing tem que reiniciar para activar a nova configuração.",
"The device ID cannot be blank.": "O ID do dispositivo não pode estar vazio.",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "Ключ API",
"About": "О программе",
"Add": "Add",
"Add": "Добавить",
"Add Device": "Добавить устройство",
"Add Folder": "Добавить папку",
"Add new folder?": "Add new folder?",
"Add new folder?": "Добавить новую папку?",
"Address": "Адрес",
"Addresses": "Адреса",
"Allow Anonymous Usage Reporting?": "Разрешить сбор анонимной статистики использования?",
@@ -13,6 +13,7 @@
"Automatic upgrades": "Автообновление",
"Bugs": "Ошибки",
"CPU Utilization": "Загрузка ЦПУ",
"Changelog": "Журнал изменений",
"Close": "Закрыть",
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
"Compression is recommended in most setups.": "Сжатие рекомендуется в большинстве случаев.",
@@ -24,7 +25,8 @@
"Device ID": "ID устройства",
"Device Identification": "Идентификация устройства",
"Device Name": "Имя устройства",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) хочет подключиться. Добавить новое устройство?",
"Devices": "Устройства",
"Disconnected": "Нет соединения",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
@@ -54,24 +56,25 @@
"Global Discovery Server": "Сервер глобального обнаружения",
"Global State": "Глобальное состояние",
"Idle": "Бездействует",
"Ignore": "Ignore",
"Ignore": "Игнорировать",
"Ignore Patterns": "Шаблоны игнорирования",
"Ignore Permissions": "Игнорировать файловые права доступа",
"Incoming Rate Limit (KiB/s)": "Ограничение входящего потока (Кбит/сек)",
"Introducer": "Рекомендатель",
"Inversion of the given condition (i.e. do not exclude)": "Инвертировать текущее условие (например, исключить)",
"Keep Versions": "Количество хранимых версий",
"Last File Received": "Последний полученный файл",
"Last File Synced": "Последний синхронизированный файл",
"Last seen": "Был доступен",
"Later": "Later",
"Later": "Потом",
"Latest Release": "Последняя версия",
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
"Maximum Age": "Максимальный срок",
"Multi level wildcard (matches multiple directory levels)": "Многоуровневая маска (поиск совпадений во всех подпапках)",
"Never": "Никогда",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Новое устройство",
"New Folder": "Новая напка",
"No": "Нет",
"No File Versioning": "Без управления версиями файлов",
"Notice": "Внимание",
@@ -79,6 +82,7 @@
"Offline": "Оффлайн",
"Online": "Онлайн",
"Out Of Sync": "Не синхронизировано",
"Out of Sync Items": "Не синхронизированные пункты",
"Outgoing Rate Limit (KiB/s)": "Предел скорости отдачи (KiB/s)",
"Override Changes": "Перезаписать изменения",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Путь к папке на локальном компьютере. Если её не существует, то она будет создана. Тильда (~) может использоваться как сокращение для",
@@ -99,23 +103,25 @@
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
"Select the folders to share with this device.": "Выберите папку для предоставления доступа данному устройству",
"Settings": "Настройки",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Предоставить доступ",
"Share Folder": "Предоставить доступ к папке",
"Share Folders With Device": "Предоставить доступ устройству к папкам",
"Share With Devices": "Предоставить доступ устройствам",
"Share this folder?": "Share this folder?",
"Share this folder?": "Предоставить доступ к этой папке?",
"Shared With": "Доступ предоставлен",
"Short identifier for the folder. Must be the same on all cluster devices.": "Короткий идентификатор папки. Должен быть одинаковым на всех устройствах кластера.",
"Show ID": "Показать ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Отображается вместо 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)": "Одноуровневая маска (поиск совпадений только внутри папки)",
"Source Code": "Исходный код",
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
"Start Browser": "Открыть браузер",
"Stopped": "Остановлено",
"Support": "Поддержка",
"Support / Forum": "Поддержка / Форум",
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
"Synchronization": "Синхронизация",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Перезапуск Syncthing",
"Syncthing is upgrading.": "Обновление Syncthing ",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Кажется, Syncthing не запущен или есть проблемы с подключением к Интернету. Переподключаюсь...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Похоже, у Syncthing возникла проблема с обработкой запроса. Обновите страницу в обозревателе или перезапустите Syncthing, если это не поможет.",
"The aggregated statistics are publicly available at {%url%}.": "Суммарная статистика общедоступна на {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурация была сохранена но не активирована. Для активации новой конфигурации необходимо рестартовать Syncthing.",
"The device ID cannot be blank.": "ID устройства не может быть пустым.",

View File

@@ -1,30 +1,32 @@
{
"API Key": "API-nyckel",
"About": "Om",
"Add": "Add",
"Add": "Lägg till",
"Add Device": "Lägg till enhet",
"Add Folder": "Lägg till katalog",
"Add new folder?": "Add new folder?",
"Add new folder?": "Lägg till katalog?",
"Address": "Adress",
"Addresses": "Adresser",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
"Anonymous Usage Reporting": "Anonym användarstatistik",
"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.",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurerade på en introduktörsenhet kommer också att läggas till den här enheten.",
"Automatic upgrades": "Automatisk uppgradering",
"Bugs": "Buggar",
"CPU Utilization": "CPU-användning",
"Changelog": "Changelog",
"Close": "Stäng",
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
"Compression is recommended in most setups.": "Komprimering är rekommenderat för de flesta.",
"Connection Error": "Anslutningsproblem",
"Copied from elsewhere": "Kopierat utifrån",
"Copied from original": "Oförändrat",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg och de följande medarbetarna:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg och följande medarbetare:",
"Delete": "Radera",
"Device ID": "Enhets-ID",
"Device Identification": "Enhetsidentifikation",
"Device Name": "Enhetsnamn",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enheten {{device}} ({{address}}) vill ansluta. Lägg till ny enhet?",
"Devices": "Enheter",
"Disconnected": "Ej ansluten",
"Documentation": "Dokumentation",
"Download Rate": "Nedladdningshastighet",
@@ -54,35 +56,37 @@
"Global Discovery Server": "Global uppslagningsserver",
"Global State": "Global status",
"Idle": "Vilande",
"Ignore": "Ignore",
"Ignore Patterns": "Filmönster",
"Ignore": "Ignorera",
"Ignore Patterns": "Ignorerade filmönster",
"Ignore Permissions": "Ignorera filrättigheter",
"Incoming Rate Limit (KiB/s)": "Max nedladdningshastighet (KiB/s)",
"Introducer": "Introducer",
"Introducer": "introduktör",
"Inversion of the given condition (i.e. do not exclude)": "Vänder på villkoret, d.v.s. exkluderar inte.",
"Keep Versions": "Behåll versioner",
"Last File Synced": "Senast uppdaterad fil",
"Last File Received": "Senast Mottagna Fil",
"Last File Synced": "Senast uppdaterade fil",
"Last seen": "Senast online",
"Later": "Later",
"Later": "Senare",
"Latest Release": "Senaste version",
"Local Discovery": "Lokal uppslagning",
"Local State": "Lokal status",
"Maximum Age": "Högsta åldersgräns",
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
"Never": "Aldrig",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ny enhet",
"New Folder": "Ny katalog",
"No": "Nej",
"No File Versioning": "Ingen versionshantering",
"Notice": "OBS",
"Notice": "Observera",
"OK": "OK",
"Offline": "Ej tillgänglig",
"Online": "Tillgänglig",
"Out Of Sync": "Ur synk",
"Out Of Sync": "Osynkad",
"Out of Sync Items": "Osynkade poster",
"Outgoing Rate Limit (KiB/s)": "Max uppladdningshastighet (KiB/s)",
"Override Changes": "Skriv över ändringar",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sökväg till katalogen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna blankt för att använda .stversions i den ordinarie 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 .stversions i den ordinarie katalogen).",
"Please wait": "Var god vänta",
"Preview": "Förhandsgranska",
"Preview Usage Report": "Förhandsgranska statistik",
@@ -97,53 +101,56 @@
"Save": "Spara",
"Scanning": "Uppdaterar",
"Select the devices to share this folder with.": "Ange enheterna att dela den här katalogen med.",
"Select the folders to share with this device.": "Välja kataloger att dela med den här enheten",
"Select the folders to share with this device.": "Välj kataloger att dela med den här enheten.",
"Settings": "Inställningar",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Dela",
"Share Folder": "Dela katalog",
"Share Folders With Device": "Dela kataloger med enhet",
"Share With Devices": "Dela med enheter",
"Share this folder?": "Share this folder?",
"Shared With": "Delat med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort identifieringssträng för katalogen. Måste vara samma på alla enheter i klustern.",
"Share this folder?": "Dela den här katalogen?",
"Shared With": "Delad med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort identifieringssträng för katalogen. Måste vara samma på alla enheter i klustret.",
"Show ID": "Visa ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visas i stället för enhets-ID. Skickas till andra enheter som namn på denna enhet.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhets-ID. Sätts till namnet på den andra enheten vid första anslutning om det lämnas blankt.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhets-ID. Sätts till namnet på den andra enheten vid första anslutning om det lämnas tomt.",
"Shutdown": "Stäng av",
"Shutdown Complete": "Avstängning klar",
"Simple File Versioning": "Enkel versionshantering",
"Single level wildcard (matches within a directory only)": "Jokertecken som representerar noll eller fler godtyckliga tecken i ett filnamn.",
"Source Code": "Källkod",
"Staggered File Versioning": "Versionshantering i intervall",
"Start Browser": "Starta browser",
"Stopped": "Stoppad",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Address för inkommande anslutningar",
"Synchronization": "Synkronisering",
"Syncing": "Synkroniserar",
"Syncthing has been shut down.": "Syncthing har stängts ner.",
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller de följande mjukvarupaketen eller delar därav:",
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:",
"Syncthing is restarting.": "Syncthing startar om.",
"Syncthing is upgrading.": "Syncthing uppgraderas.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller så är där ett problem med din Internetanslutning. Försöker igen...",
"The aggregated statistics are publicly available at {%url%}.": "Aggregerad statistik finns publikt tillgänglig på {{url}}.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller finns det problem med din Internetanslutning. Försöker igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing verkar ha problem med att genomföra din förfrågan. Ladda om sidan i din webbläsare eller starta om Syncthing ifall problemet kvarstår.",
"The aggregated statistics are publicly available at {%url%}.": "Sammanställd statistik finns publikt tillgänglig på {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
"The device ID cannot be blank.": "Enhets-ID kan inte vara blankt.",
"The device ID cannot be blank.": "Enhets-ID kan inte vara tomt.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhets-ID som behövs här kan du hitta i \"Redigera > Visa ID\"-dialogen på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets-ID:t verkar inte korrekt. Det ska vara en 52 eller 56 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
"The folder ID cannot be blank.": "Ange ett enhets-ID.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Katalog-ID:t måste vara en kort sträng (64 tecken eller mindre), bestående av endast bokstäver, siffror, punkt (.), bindestreck (-) och understreck (_).",
"The folder ID must be unique.": "Enhets-ID måste vara unikt.",
"The folder ID must be unique.": "Katalog-ID:t måste vara unikt.",
"The folder path cannot be blank.": "Ange en sökväg.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De följande intervallen används: varje 30 sekunder under den första timmen; varje timme under den första dagen; varje dag för de första 30 dagarna; varje vecka tills den maximala åldersgränsen uppnås.",
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas blankt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tid att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas, per fil.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas blankt.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas tomt.",
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
"The rescan interval must be at least 5 seconds.": "Uppdateringsintervallet måste vara minst 5 sekunder.",
"Unknown": "Okänt",
"Unshared": "Odelat",
"Unshared": "Inte delad",
"Unused": "Oanvänd",
"Up to Date": "Helt uppdaterad",
"Upgrade To {%version%}": "Uppgradera till {{version}}",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"full documentation": "fullständig dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API Key",
"About": "关于",
"Add": "Add",
"Add": "添加",
"Add Device": "添加设备",
"Add Folder": "添加文件夹",
"Add new folder?": "Add new folder?",
"Add new folder?": "添加新文件夹?",
"Address": "地址",
"Addresses": "地址列表",
"Allow Anonymous Usage Reporting?": "允许匿名使用报告?",
@@ -13,6 +13,7 @@
"Automatic upgrades": "自动升级",
"Bugs": "Bug汇报",
"CPU Utilization": "CPU使用率",
"Changelog": "更新日志",
"Close": "关闭",
"Comment, when used at the start of a line": "注释,在行首使用",
"Compression is recommended in most setups.": "在大多数场合,建议开启压缩",
@@ -24,7 +25,8 @@
"Device ID": "设备ID",
"Device Identification": "设备ID",
"Device Name": "设备名",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "设备 {{device}} ({{address}}) 请求连接。是否添加新设备?",
"Devices": "设备",
"Disconnected": "连接已断开",
"Documentation": "文档",
"Download Rate": "下载速度",
@@ -54,24 +56,25 @@
"Global Discovery Server": "用以在互联网上寻找节点的Announce服务器地址",
"Global State": "全局状态",
"Idle": "空闲",
"Ignore": "Ignore",
"Ignore": "忽略",
"Ignore Patterns": "忽略列表",
"Ignore Permissions": "忽略文件权限",
"Incoming Rate Limit (KiB/s)": "下载速率限制(千字节/秒)",
"Introducer": "介绍人节点",
"Inversion of the given condition (i.e. do not exclude)": "对本条件取反(例如:不要排除某项)",
"Keep Versions": "保留历史版本数量",
"Last File Received": "最后接收的文件",
"Last File Synced": "最近同步的文件",
"Last seen": "最后可见",
"Later": "Later",
"Later": "稍后",
"Latest Release": "最新版本",
"Local Discovery": "在局域网上寻找节点",
"Local State": "本地状态",
"Maximum Age": "历史版本最长保留时间",
"Multi level wildcard (matches multiple directory levels)": "多级通配符(用以匹配多层文件夹)",
"Never": "从未",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "新设备",
"New Folder": "新文件夹",
"No": "否",
"No File Versioning": "不启用版本控制",
"Notice": "提示",
@@ -79,6 +82,7 @@
"Offline": "离线",
"Online": "在线",
"Out Of Sync": "未同步",
"Out of Sync Items": "未同步的项目",
"Outgoing Rate Limit (KiB/s)": "上传速度限制(千字节/秒)",
"Override Changes": "撤销改变",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "文件夹在本地的路径。如果不存在,则会被创建。波浪线符号(~)是如下路径的缩略符:",
@@ -99,23 +103,25 @@
"Select the devices to share this folder with.": "选择将本文件夹共享给哪些设备",
"Select the folders to share with this device.": "选择与该设备共享的文件夹。",
"Settings": "设置",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "共享",
"Share Folder": "共享文件夹",
"Share Folders With Device": "将指定文件夹共享给设备",
"Share With Devices": "共享给",
"Share this folder?": "Share this folder?",
"Share this folder?": "是否共享该文件夹?",
"Shared With": "共享给",
"Short identifier for the folder. Must be the same on all cluster devices.": "文件夹的别名。必须在所有设备上保持一致。",
"Show ID": "显示设备ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "在设备丛中显示该名称而不是设备ID。亦会作为一个可选的默认名称被发送到其他设备。",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "在设备丛中将会显示本名称而不是设备ID。如果设置为空则会使用目标设备提供的默认名称。",
"Shutdown": "关闭Syncthing",
"Shutdown Complete": "关闭完成",
"Simple File Versioning": "简易版本控制",
"Single level wildcard (matches within a directory only)": "单级通配符(仅匹配单层文件夹)",
"Source Code": "源代码",
"Staggered File Versioning": "阶段版本控制",
"Start Browser": "启动浏览器",
"Stopped": "已停止",
"Support": "支持",
"Support / Forum": "支持/论坛",
"Sync Protocol Listen Addresses": "协议监听地址",
"Synchronization": "同步完成度",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing正在重启",
"Syncthing is upgrading.": "Syncthing正在升级",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing似乎关闭了或者您的网络连接存在故障。重试中...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing 在处理您的请求时似乎碰到了问题。如果问题持续,请刷新您的浏览器或重启 Syncthing 。",
"The aggregated statistics are publicly available at {%url%}.": "全局统计公布于 {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "设置已经保存但是还未生效。Syncthing需要重启以启用新的设置。",
"The device ID cannot be blank.": "设备ID不能为空",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "您必须保留至少一个版本。",
"full documentation": "完整文档",
"items": "条目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将文件夹 \"{{folder}}\" 共享给您。"
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API 金鑰",
"About": "關於",
"Add": "Add",
"Add": "增加",
"Add Device": "增加裝置",
"Add Folder": "增加資料夾",
"Add new folder?": "Add new folder?",
"Add new folder?": "新增資料夾?",
"Address": "位址",
"Addresses": "位址",
"Allow Anonymous Usage Reporting?": "允許匿名的使用資訊回報?",
@@ -13,18 +13,20 @@
"Automatic upgrades": "自動升級",
"Bugs": "程式錯誤",
"CPU Utilization": "CPU 使用率",
"Changelog": "Changelog",
"Close": "關閉",
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
"Compression is recommended in most setups.": "建議在大多數的設置中使用壓縮。",
"Connection Error": "連線錯誤",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from elsewhere": "從別處複製",
"Copied from original": "Copied from original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "版權所有 © 2014 Jakob Borg 及以下貢獻者:",
"Delete": "刪除",
"Device ID": "裝置識別碼",
"Device Identification": "裝置識別",
"Device Name": "裝置名稱",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "裝置 {{device}} ({{address}}) 想要連線。要新增裝置嗎?",
"Devices": "裝置",
"Disconnected": "斷線",
"Documentation": "說明文件",
"Download Rate": "下載速率",
@@ -54,24 +56,25 @@
"Global Discovery Server": "全域探索伺服器",
"Global State": "全域狀態",
"Idle": "閒置",
"Ignore": "Ignore",
"Ignore": "忽略",
"Ignore Patterns": "忽略樣式",
"Ignore Permissions": "忽略權限",
"Incoming Rate Limit (KiB/s)": "連入速率限制 (KiB/s)",
"Introducer": "引入者",
"Inversion of the given condition (i.e. do not exclude)": "反轉給定條件 (即:不要排除)",
"Keep Versions": "保留歷史版本數",
"Last File Received": "Last File Received",
"Last File Synced": "最後同步檔案",
"Last seen": "最後發現時間",
"Later": "Later",
"Later": "稍後",
"Latest Release": "最新發佈",
"Local Discovery": "本地探索",
"Local State": "本地狀態",
"Maximum Age": "最長保留時間",
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
"Never": "從未",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "新裝置",
"New Folder": "新資料夾",
"No": "否",
"No File Versioning": "無檔案版本控制",
"Notice": "注意",
@@ -79,6 +82,7 @@
"Offline": "離線",
"Online": "上線",
"Out Of Sync": "不同步",
"Out of Sync Items": "不同步物件",
"Outgoing Rate Limit (KiB/s)": "連出速率限制 (KiB/s)",
"Override Changes": "置換改變",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "資料夾在本地電腦的路徑。若資料夾不存在則會建立。波浪符號 (~) 可用作下列資料夾的捷徑:",
@@ -99,23 +103,25 @@
"Select the devices to share this folder with.": "選擇要共享這個資料夾的裝置。",
"Select the folders to share with this device.": "選擇要共享這個資料夾的裝置。",
"Settings": "設定",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "分享",
"Share Folder": "分享資料夾",
"Share Folders With Device": "與裝置共享資料夾",
"Share With Devices": "與這些裝置共享",
"Share this folder?": "Share this folder?",
"Share this folder?": "分享此資料夾?",
"Shared With": "與誰共享",
"Short identifier for the folder. Must be the same on all cluster devices.": "資料夾的簡短識別字。必須在叢集內所有的裝置上皆相同。",
"Show ID": "顯示識別碼",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "代替裝置識別碼顯示在叢集狀態中。這段文字將會廣播到其他的裝置作為一個可選的預設名稱。",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "代替裝置識別碼顯示在叢集狀態中。本欄若未填寫則將被更新為此裝置所廣播的名稱。",
"Shutdown": "關閉",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "簡單檔案版本控制",
"Single level wildcard (matches within a directory only)": "單階層萬用字元 (只在單個資料夾階層內比對)",
"Source Code": "原始碼",
"Staggered File Versioning": "變動式檔案版本控制",
"Start Browser": "啟動瀏覽器",
"Stopped": "已停止",
"Support": "支援",
"Support / Forum": "支援 / 論壇",
"Sync Protocol Listen Addresses": "同步通訊協定監聽位址",
"Synchronization": "同步作業",
@@ -125,6 +131,7 @@
"Syncthing is restarting.": "Syncthing 正在重新啟動。",
"Syncthing is upgrading.": "Syncthing 正在進行升級。",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing 似乎下線了,或者您的網際網路連線出現問題。正在重試...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "匯總統計資訊公佈於 {{url}}。",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "組態已經儲存但尚未啟用。Syncthing 必須重新啟動以便啟用新的組態。",
"The device ID cannot be blank.": "裝置識別碼不能為空白。",
@@ -160,5 +167,5 @@
"You must keep at least one version.": "您必須保留至少一個版本。",
"full documentation": "完整說明文件",
"items": "個項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\""
}

View File

@@ -1 +1 @@
var validLangs = ["be","bg","cs","de","en","es","fr","hu","it","lt","nb","nl","nn","pl","pt-PT","ru","sv","zh-CN","zh-TW"]
var validLangs = ["be","bg","ca","cs","de","en","es","fr","hu","it","lt","nb","nl","nn","pl","pt-PT","ru","sv","zh-CN","zh-TW"]

View File

@@ -64,12 +64,12 @@
<div class="container">
<!-- First row, only shown if necessary; Restart warning -->
<!-- Panel: Restart Needed -->
<div ng-if="!configInSync" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading"><h3 translate class="panel-title">Restart Needed</h3></div>
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Restart Needed</span></h3></div>
<div class="panel-body">
<p translate>The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.</p>
</div>
@@ -81,6 +81,90 @@
</div>
</div>
<!-- Panel: New Device -->
<div ng-repeat="(device, event) in deviceRejections" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<identicon data-value="device"></identicon>&emsp;<span translate>New Device</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span></button>
<button class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Ignore</span></button>
<button class="btn btn-sm btn-default" ng-click="dismissDeviceRejection(device)"><span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span></button>
</div>
</div>
</div>
</div>
</div>
<!-- Panel: New Folder -->
<div ng-repeat="(key, event) in folderRejections" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-hdd"></span>
<span translate ng-if="!folders[event.data.folder]">New Folder</span>
<span translate ng-if="folders[event.data.folder]">Share Folder</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span translate ng-if="folders[event.data.folder]">Share this folder?</span>
<span translate ng-if="!folders[event.data.folder]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span>
</button>
<button class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Share</span>
</button>
<button class="btn btn-sm btn-default" ng-click="dismissFolderRejection(event.data.folder, event.data.device)">
<span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Panel: Notice -->
<div ng-if="errorList().length > 0" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.Error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<!-- First regular row -->
<div class="row">
@@ -89,19 +173,20 @@
<div class="col-md-6">
<div class="panel-group" id="folders">
<div class="visible-xs"><h3><span translate>Folders</span></h3><hr></div>
<div class="panel panel-default" ng-repeat="folder in folderList()">
<div class="panel-heading" data-toggle="collapse" data-parent="#folders" href="#folder-{{$index}}" style="cursor: pointer">
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.ID)}}%"></div>
<h3 class="panel-title">
<span class="glyphicon glyphicon-hdd"></span>&emsp;{{folder.ID}}
<span class="pull-right hidden-xs text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span translate ng-switch-when="unknown">Unknown</span>
<span translate ng-switch-when="unshared">Unshared</span>
<span translate ng-switch-when="stopped">Stopped</span>
<span translate ng-switch-when="scanning">Scanning</span>
<span translate ng-switch-when="idle">Up to Date</span>
<span class="glyphicon glyphicon-hdd hidden-xs"></span>{{folder.ID}}
<span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="scanning"><span class="hidden-xs" translate>Scanning</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span translate>Syncing</span>
<span class="hidden-xs" translate>Syncing</span>
({{syncPercentage(folder.ID)}}%)
</span>
</span>
@@ -139,6 +224,12 @@
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="model[folder.ID].ignorePatterns">
<th><span class="glyphicon glyphicon-minus"></span>&emsp;<span translate>Ignore Patterns</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.IgnorePerms">
<th><span class="glyphicon glyphicon-unchecked"></span>&emsp;<span translate>Ignore Permissions</span></th>
<td class="text-right">
@@ -149,12 +240,19 @@
<th><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan Interval</span></th>
<td class="text-right">{{folder.RescanIntervalS}} s</td>
</tr>
<tr ng-if="folder.Versioning.Type">
<th><span class="glyphicon glyphicon-tags"></span>&emsp;<span translate>File Versioning</span></th>
<td class="text-right" ng-switch="folder.Versioning.Type">
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
<span ng-switch-when="simple" translate>Simple File Versioning</span>
</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-share-alt"></span>&emsp;<span translate>Shared With</span></th>
<td class="text-right">{{sharesFolder(folder)}}</td>
</tr>
<tr ng-if="folderStats[folder.ID].LastFile">
<th><span class="glyphicon glyphicon-transfer"></span>&emsp;<span translate>Last File Synced</span></th>
<th><span class="glyphicon glyphicon-transfer"></span>&emsp;<span translate>Last File Received</span></th>
<td class="text-right">
<span title="{{folderStats[folder.ID].LastFile.Filename}} @ {{folderStats[folder.ID].LastFile.At | date:'yyyy-MM-dd HH:mm'}}">
{{folderStats[folder.ID].LastFile.Filename | basename}}
@@ -165,7 +263,7 @@
</table>
</div>
<div class="panel-footer">
<button class="btn btn-sm btn-danger" ng-if="folder.ReadOnly && model[folder.ID].needFiles > 0" ng-click="override(folder.ID)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<button class="btn btn-sm btn-danger pull-left" ng-if="folder.ReadOnly && model[folder.ID].needFiles > 0" ng-click="override(folder.ID)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<span class="pull-right">
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.ID)"><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan</span></button>
<button class="btn btn-sm btn-default" href="" ng-click="editFolder(folder)"><span class="glyphicon glyphicon-pencil"></span>&emsp;<span translate>Edit</span></button>
@@ -187,6 +285,7 @@
<!-- This device -->
<div class="col-md-6">
<div class="visible-xs"><h3><span translate>Devices</span></h3><hr></div>
<div class="panel panel-default" ng-repeat="deviceCfg in [thisDevice()]">
<div class="panel-heading" data-toggle="collapse" href="#device-this" style="cursor: pointer">
<h3 class="panel-title">
@@ -244,13 +343,13 @@
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.DeviceID]._total | number:0}}%"></div>
<h3 class="panel-title">
<identicon data-value="deviceCfg.DeviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right hidden-xs text-{{deviceClass(deviceCfg)}}">
<span translate ng-switch-when="insync">Up to Date</span>
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right text-{{deviceClass(deviceCfg)}}">
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span translate>Syncing</span> ({{completion[deviceCfg.DeviceID]._total | number:0}}%)
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.DeviceID]._total | number:0}}%)
</span>
<span translate ng-switch-when="disconnected">Disconnected</span>
<span translate ng-switch-when="unused">Unused</span>
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs">&#9724;</span></span>
</span>
</h3>
</div>
@@ -308,102 +407,18 @@
</div>
</div> <!-- /row -->
<!-- Device Rejections -->
<div ng-repeat="(device, event) in deviceRejections" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<identicon data-value="device"></identicon>&emsp;<span translate>New Device</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span></button>
<button class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Ignore</span></button>
<button class="btn btn-sm btn-default" ng-click="dismissDeviceRejection(device)"><span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span></button>
</div>
</div>
</div>
</div>
</div>
<!-- Folder Rejections -->
<div ng-repeat="(key, event) in folderRejections" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<span translate ng-if="!folders[event.data.folder]">New Folder</span>
<span translate ng-if="folders[event.data.folder]">Share Folder</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span translate ng-if="folders[event.data.folder]">Share this folder?</span>
<span translate ng-if="!folders[event.data.folder]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span>
</button>
<button class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Share</span>
</button>
<button class="btn btn-sm btn-default" ng-click="dismissFolderRejection(event.data.folder, event.data.device)">
<span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Errors -->
<div ng-if="errorList().length > 0" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.Error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div> <!-- /container -->
<!-- Bottom bar -->
<nav class="navbar navbar-default navbar-fixed-bottom hidden-xs">
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container">
<ul class="nav navbar-nav">
<li><a class="navbar-link" href="http://discourse.syncthing.net/"><span translate>Support / Forum</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases"><span translate>Latest Release</span></a></li>
<li><a class="navbar-link" href="http://discourse.syncthing.net/category/documentation"><span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues"><span translate>Bugs</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing"><span translate>Source Code</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/wiki" target="_blank"><span class="glyphicon glyphicon-book"></span>&ensp;<span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://discourse.syncthing.net" target="_blank"><span class="glyphicon glyphicon-question-sign"></span>&ensp;<span translate>Support</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases" target="_blank"><span class="glyphicon glyphicon-info-sign"></span>&ensp;<span translate>Changelog</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues" target="_blank"><span class="glyphicon glyphicon-warning-sign"></span>&ensp;<span translate>Bugs</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing" target="_blank"><span class="glyphicon glyphicon-wrench"></span>&ensp;<span translate>Source Code</span></a></li>
</ul>
</div>
</nav>
@@ -416,6 +431,14 @@
</p>
</modal>
<!-- HTTP error modal -->
<modal id="httpError" status="danger" icon="exclamation-sign" title="{{'Connection Error' | translate}}">
<p translate>
Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.
</p>
</modal>
<!-- Restarting modal -->
<modal id="restarting" icon="refresh" title="{{'Restarting' | translate}}" status="info">
@@ -430,7 +453,7 @@
<!-- Shutdown modal -->
<modal id="shutdown" icon="off" status="success" title="Shutdown Complete">
<modal id="shutdown" icon="off" status="success" title="{{'Shutdown Complete' | translate}}">
<p translate>Syncthing has been shut down.</p>
</modal>
@@ -673,7 +696,7 @@
<hr/>
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://discourse.syncthing.net/t/80" translate>full documentation</a>):</p>
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://github.com/syncthing/syncthing/wiki/Ignoring-Files" target="_blank" translate>full documentation</a>):</p>
<dl class="dl-horizontal dl-narrow small">
<dt><code>!</code></dt> <dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
<dt><code>*</code></dt> <dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
@@ -682,7 +705,7 @@
</dl>
</div>
<div class="modal-footer">
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.Path}}/.stignore</code></div>
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.Path}}{{system.pathSeparator}}.stignore</code></div>
<button type="button" class="btn btn-primary btn-sm" data-dismiss="modal" ng-click="saveIgnores()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Save</span></button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Close</span></button>
</div>
@@ -822,7 +845,7 @@
</div>
<div class="modal-body">
<p translate>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.</p>
<p translate translate-value-url="https://data.syncthing.net">The aggregated statistics are publicly available at {%url%}.</p>
<p translate translate-value-url="<a href=&quot;https://data.syncthing.net&quot; target=&quot;_blank&quot;>https://data.syncthing.net</a>">The aggregated statistics are publicly available at {%url%}.</p>
<button translate type="button" class="btn btn-default btn-sm" ng-show="!reportPreview" ng-click="showReportPreview()">Preview Usage Report</button>
<pre ng-if="reportPreview"><small>{{reportData | json}}</small></pre>
</div>
@@ -844,7 +867,7 @@
</div>
<div class="modal-body">
<p translate>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.</p>
<p translate translate-value-url="https://data.syncthing.net">The aggregated statistics are publicly available at {%url%}.</p>
<p translate translate-value-url="<a href=&quot;https://data.syncthing.net&quot; target=&quot;_blank&quot;>https://data.syncthing.net</a>">The aggregated statistics are publicly available at {%url%}.</p>
<pre><small>{{reportData | json}}</small></pre>
</div>
<div class="modal-footer">
@@ -856,7 +879,7 @@
<!-- Needed files modal -->
<modal id="needed" large="yes" status="info" icon="cloud-download" close="yes" title="Out of Sync Items">
<modal id="needed" large="yes" status="info" icon="cloud-download" close="yes" title="{{'Out of Sync Items' | translate}}">
<div class="progress">
<div class="progress-bar progress-bar-success" style="width: 20%"><span translate class="show">Reused</span></div>
<div class="progress-bar" style="width: 20%"><span translate class="show">Copied from original</span></div>
@@ -889,9 +912,8 @@
</tr>
<tr ng-repeat="f in needed.queued" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td title="{{f.Name}}">{{f.Name | basename}}</td>
<td><a href="" ng-if="$index != 0" ng-click="bumpFile(neededFolder, f.Name)" title="{{'Move to top of queue' | translate}}"><span class="glyphicon glyphicon-eject"></span></a><span ng-if="$index != 0">&ensp;</span><span title="{{f.Name}}">{{f.Name | basename}}</span></td>
<td class="text-right small-data">
<span ng-if="$index != 0" class="glyphicon glyphicon-chevron-up" ng-click="bumpFile(neededFolder, f.Name)"></span>
<span ng-if="f.Size > 0">{{f.Size | binary}}B</span>
</td>
</tr>
@@ -906,7 +928,7 @@
<!-- About modal -->
<modal id="about" large="yes" close="yes" status="info" title="About">
<modal id="about" large="yes" close="yes" status="info" title="{{'About' | translate}}">
<h1 class="text-center"><img alt="Syncthing" title="Syncthing" src="assets/img/logo-text-256.png" style="vertical-align: -16px" height="100" width="366"/><br/><small>{{version}}</small></h1>
<hr/>
@@ -922,6 +944,7 @@
<li>Ben Schulz</li>
<li>Ben Sidhom</li>
<li>Brandon Philips</li>
<li>Brendan Long</li>
<li>Caleb Callaway</li>
<li>Cathryne Linenweaver</li>
<li>Chris Joel</li>
@@ -929,19 +952,25 @@
<li>Dennis Wilson</li>
<li>Dominik Heidler</li>
<li>Emil Hessman</li>
<li>Federico Castagnini</li>
<li>Felix Ableitner</li>
<li>Felix Unterpaintner</li>
<li>Gilli Sigurdsson</li>
<li>James Patterson</li>
<li>Jens Diemer</li>
<li>Jochen Voss</li>
<li>Karol Różycki</li>
<li>Lode Hoste</li>
<li>Marcin Dziadus</li>
<li>Michael Jephcote</li>
<li>Michael Tilli</li>
<li>Peter Hoeg</li>
<li>Philippe Schommers</li>
<li>Phill Luby</li>
<li>Piotr Bejda</li>
<li>Ryan Sullivan</li>
<li>Stefan Tatschner</li>
<li>Tim Abell</li>
<li>Tomas Cerveny</li>
<li>Tully Robinson</li>
<li>Veeti Paananen</li>

View File

@@ -10,29 +10,7 @@ angular.module('syncthing.core')
var restarting = false;
function initController() {
LocaleService.autoConfigLocale();
refreshSystem();
refreshConfig();
refreshConnectionStats();
refreshDeviceStats();
refreshFolderStats();
$http.get(urlbase + '/version').success(function (data) {
$scope.version = data.version;
});
$http.get(urlbase + '/report').success(function (data) {
$scope.reportData = data;
});
$http.get(urlbase + '/upgrade').success(function (data) {
$scope.upgradeInfo = data;
}).error(function () {
$scope.upgradeInfo = null;
});
setInterval($scope.refresh, 10000);
}
@@ -86,7 +64,27 @@ angular.module('syncthing.core')
}
console.log('UIOnline');
initController();
refreshSystem();
refreshConfig();
refreshConnectionStats();
refreshDeviceStats();
refreshFolderStats();
$http.get(urlbase + '/version').success(function (data) {
$scope.version = data.version;
}).error($scope.emitHTTPError);
$http.get(urlbase + '/report').success(function (data) {
$scope.reportData = data;
}).error($scope.emitHTTPError);
$http.get(urlbase + '/upgrade').success(function (data) {
$scope.upgradeInfo = data;
}).error(function () {
$scope.upgradeInfo = null;
});
online = true;
restarting = false;
$('#networkError').modal('hide');
@@ -106,6 +104,30 @@ angular.module('syncthing.core')
}
});
$scope.$on('HTTPError', function (event, arg) {
// Emitted when a HTTP call fails. We use the status code to try
// to figure out what's wrong.
if (navigatingAway || !online) {
return;
}
console.log('HTTPError', arg);
online = false;
if (!restarting) {
if (arg.status === 0) {
// A network error, not an HTTP error
$scope.$emit('UIOffline');
} else if (arg.status >= 400 && arg.status <= 599) {
// A genuine HTTP error
$('#networkError').modal('hide');
$('#restarting').modal('hide');
$('#shutdown').modal('hide');
$('#httpError').modal();
}
}
});
$scope.$on('StateChanged', function (event, arg) {
var data = arg.data;
if ($scope.model[data.folder]) {
@@ -183,7 +205,7 @@ angular.module('syncthing.core')
$http.get(urlbase + '/config/sync').success(function (data) {
$scope.configInSync = data.configInSync;
});
}).error($scope.emitHTTPError);
});
$scope.$on('DownloadProgress', function (event, arg) {
@@ -233,6 +255,10 @@ angular.module('syncthing.core')
console.log("DownloadProgress", $scope.progress);
});
$scope.emitHTTPError = function (data, status, headers, config) {
$scope.$emit('HTTPError', {data: data, status: status, headers: headers, config: config});
};
var debouncedFuncs = {};
function refreshFolder(folder) {
@@ -242,7 +268,7 @@ angular.module('syncthing.core')
$http.get(urlbase + '/model?folder=' + encodeURIComponent(folder)).success(function (data) {
$scope.model[folder] = data;
console.log("refreshFolder", folder, data);
});
}).error($scope.emitHTTPError);
}, 1000, true);
}
debouncedFuncs[key]();
@@ -289,7 +315,7 @@ angular.module('syncthing.core')
}
$scope.announceServersFailed = failed;
console.log("refreshSystem", data);
});
}).error($scope.emitHTTPError);
}
function refreshCompletion(device, folder) {
@@ -318,7 +344,7 @@ angular.module('syncthing.core')
$scope.completion[device]._total = tot / cnt;
console.log("refreshCompletion", device, folder, $scope.completion[device]);
});
}).error($scope.emitHTTPError);
}, 1000, true);
}
debouncedFuncs[key]();
@@ -345,25 +371,25 @@ angular.module('syncthing.core')
}
$scope.connections = data;
console.log("refreshConnections", data);
});
}).error($scope.emitHTTPError);
}
function refreshErrors() {
$http.get(urlbase + '/errors').success(function (data) {
$scope.errors = data.errors;
console.log("refreshErrors", data);
});
}).error($scope.emitHTTPError);
}
function refreshConfig() {
$http.get(urlbase + '/config').success(function (data) {
updateLocalConfig(data);
console.log("refreshConfig", data);
});
}).error($scope.emitHTTPError);
$http.get(urlbase + '/config/sync').success(function (data) {
$scope.configInSync = data.configInSync;
});
}).error($scope.emitHTTPError);
}
function refreshNeed(folder) {
@@ -372,7 +398,7 @@ angular.module('syncthing.core')
console.log("refreshNeed", folder, data);
$scope.needed = data;
}
});
}).error($scope.emitHTTPError);
}
var refreshDeviceStats = debounce(function () {
@@ -383,7 +409,7 @@ angular.module('syncthing.core')
$scope.deviceStats[device].LastSeenDays = (new Date() - $scope.deviceStats[device].LastSeen) / 1000 / 86400;
}
console.log("refreshDeviceStats", data);
});
}).error($scope.emitHTTPError);
}, 500);
var refreshFolderStats = debounce(function () {
@@ -395,7 +421,7 @@ angular.module('syncthing.core')
}
}
console.log("refreshfolderStats", data);
});
}).error($scope.emitHTTPError);
}, 500);
$scope.refresh = function () {
@@ -576,7 +602,7 @@ angular.module('syncthing.core')
$http.get(urlbase + '/config/sync').success(function (data) {
$scope.configInSync = data.configInSync;
});
});
}).error($scope.emitHTTPError);
};
$scope.saveSettings = function () {
@@ -656,7 +682,7 @@ angular.module('syncthing.core')
restarting = true;
$http.post(urlbase + '/shutdown').success(function () {
$('#shutdown').modal();
});
}).error($scope.emitHTTPError);
$scope.configInSync = true;
};
@@ -857,11 +883,14 @@ angular.module('syncthing.core')
params: { current: newvalue }
}).success(function (data) {
$scope.directoryList = data;
});
}).error($scope.emitHTTPError);
});
$scope.editFolder = function (folderCfg) {
$scope.currentFolder = angular.copy(folderCfg);
if ($scope.currentFolder.Path.slice(-1) == $scope.system.pathSeparator) {
$scope.currentFolder.Path = $scope.currentFolder.Path.slice(0, -1);
}
$scope.currentFolder.selectedDevices = {};
$scope.currentFolder.Devices.forEach(function (n) {
$scope.currentFolder.selectedDevices[n.DeviceID] = true;
@@ -1137,7 +1166,7 @@ angular.module('syncthing.core')
console.log("bumpFile", folder, data);
$scope.needed = data;
}
});
}).error($scope.emitHTTPError);
};
// pseudo main. called on all definitions assigned

View File

File diff suppressed because one or more lines are too long

View File

@@ -26,10 +26,11 @@ import (
"reflect"
"sort"
"strconv"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
"golang.org/x/crypto/bcrypt"
)
@@ -60,9 +61,9 @@ type FolderConfiguration struct {
IgnorePerms bool `xml:"ignorePerms,attr"`
Versioning VersioningConfiguration `xml:"versioning"`
LenientMtimes bool `xml:"lenientMtimes"`
Copiers int `xml:"copiers" default:"1"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" default:"16"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Finishers int `xml:"finishers" default:"1"` // Most of the time, should be equal to the number of copiers. These are CPU bound due to hashing.
Copiers int `xml:"copiers" default:"1"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" default:"16"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int `xml:"hashers" default:"0"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
@@ -261,6 +262,10 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
continue
}
if !strings.HasSuffix(folder.Path, string(filepath.Separator)) {
folder.Path += string(filepath.Separator)
}
if folder.ID == "" {
folder.ID = "default"
}
@@ -358,9 +363,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
if cfg.Folders[i].Pullers == 0 {
cfg.Folders[i].Pullers = 16
}
if cfg.Folders[i].Finishers == 0 {
cfg.Folders[i].Finishers = 1
}
sort.Sort(FolderDeviceConfigurationList(cfg.Folders[i].Devices))
}

View File

@@ -19,9 +19,10 @@ import (
"fmt"
"os"
"reflect"
"runtime"
"testing"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syncthing/protocol"
)
var device1, device2, device3, device4 protocol.DeviceID
@@ -89,7 +90,7 @@ func TestDeviceConfig(t *testing.T) {
RescanIntervalS: 600,
Copiers: 1,
Pullers: 16,
Finishers: 1,
Hashers: 0,
},
}
expectedDevices := []DeviceConfiguration{
@@ -261,6 +262,23 @@ func TestVersioningConfig(t *testing.T) {
}
}
func TestIssue1262(t *testing.T) {
cfg, err := Load("testdata/issue-1262.xml", device4)
if err != nil {
t.Error(err)
}
actual := cfg.Folders()["test"].Path
expected := "e:/"
if runtime.GOOS == "windows" {
expected = `e:\`
}
if actual != expected {
t.Errorf("%q != %q", actual, expected)
}
}
func TestNewSaveLoad(t *testing.T) {
path := "testdata/temp.xml"
os.Remove(path)

View File

@@ -0,0 +1,4 @@
<configuration version="7">
<folder id="test" path="e:" ro="true" ignorePerms="false" rescanIntervalS="600">
</folder>
</configuration>

View File

@@ -21,9 +21,9 @@ import (
"path/filepath"
"sync"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/events"
"github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
)
// An interface to handle configuration changes, and a wrapper type á la
@@ -264,6 +264,7 @@ func (w *Wrapper) Save() error {
if err != nil {
return err
}
defer os.Remove(fd.Name())
err = w.cfg.WriteXML(fd)
if err != nil {

View File

View File

@@ -13,14 +13,14 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Package files provides a set type to track local/remote files with newness
// Package db provides a set type to track local/remote files with newness
// checks. We must do a certain amount of normalization in here. We will get
// fed paths with either native or wire-format separators and encodings
// depending on who calls us. We transform paths to wire-format (NFC and
// slashes) on the way to the database, and transform to native format
// (varying separator and encoding) on the way back out.
package files
package db
import (
"bytes"
@@ -28,9 +28,9 @@ import (
"sort"
"sync"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
@@ -160,7 +160,7 @@ func (f *BlockFinder) Changed(cfg config.Configuration) error {
// the block) or false to continue iterating for whatever reason.
// The iterator finally returns the result, whether or not a satisfying block
// was eventually found.
func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, uint32) bool) bool {
func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, int32) bool) bool {
f.mut.RLock()
folders := f.folders
f.mut.RUnlock()
@@ -171,7 +171,7 @@ func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, uint32) b
for iter.Next() && iter.Error() == nil {
folder, file := fromBlockKey(iter.Key())
index := binary.BigEndian.Uint32(iter.Value())
index := int32(binary.BigEndian.Uint32(iter.Value()))
if iterFn(folder, osutil.NativeFilename(file), index) {
return true
}
@@ -182,7 +182,7 @@ func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, uint32) b
// A method for repairing incorrect blockmap entries, removes the old entry
// and replaces it with a new entry for the given block
func (f *BlockFinder) Fix(folder, file string, index uint32, oldHash, newHash []byte) error {
func (f *BlockFinder) Fix(folder, file string, index int32, oldHash, newHash []byte) error {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(index))

View File

@@ -13,13 +13,13 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package files
package db
import (
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
@@ -32,7 +32,7 @@ func genBlocks(n int) []protocol.BlockInfo {
for j := range h {
h[j] = byte(i + j)
}
b[i].Size = uint32(i)
b[i].Size = int32(i)
b[i].Hash = h
}
return b
@@ -103,21 +103,21 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
t.Fatal(err)
}
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f1" || index != 0 {
t.Fatal("Mismatch")
}
return true
})
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f2" || index != 0 {
t.Fatal("Mismatch")
}
return true
})
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
return true
})
@@ -132,17 +132,17 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
t.Fatal(err)
}
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
return false
})
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
return false
})
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f3" || index != 0 {
t.Fatal("Mismatch")
}
@@ -189,7 +189,7 @@ func TestBlockFinderLookup(t *testing.T) {
}
counter := 0
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
counter++
switch counter {
case 1:
@@ -217,7 +217,7 @@ func TestBlockFinderLookup(t *testing.T) {
}
counter = 0
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
counter++
switch counter {
case 1:
@@ -239,7 +239,7 @@ func TestBlockFinderLookup(t *testing.T) {
func TestBlockFinderFix(t *testing.T) {
db, f := setup()
iterFn := func(folder, file string, index uint32) bool {
iterFn := func(folder, file string, index int32) bool {
return true
}

View File

@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package files_test
package db_test
import (
"crypto/rand"

View File

@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package files
package db
import (
"os"

View File

@@ -16,7 +16,7 @@
//go:generate -command genxdr go run ../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o leveldb_xdr.go leveldb.go
package files
package db
import (
"bytes"
@@ -25,8 +25,8 @@ import (
"sort"
"sync"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/lamport"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
@@ -34,11 +34,11 @@ import (
)
var (
clockTick uint64
clockTick int64
clockMut sync.Mutex
)
func clock(v uint64) uint64 {
func clock(v int64) int64 {
clockMut.Lock()
defer clockMut.Unlock()
if v > clockTick {
@@ -56,7 +56,7 @@ const (
)
type fileVersion struct {
version uint64
version int64
device []byte
}
@@ -164,11 +164,9 @@ func globalKeyFolder(key []byte) []byte {
return folder[:izero]
}
type deletionHandler func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64
type deletionHandler func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64
type fileIterator func(f protocol.FileIntf) bool
func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, deleteFn deletionHandler) uint64 {
func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, deleteFn deletionHandler) int64 {
runtime.GC()
sort.Sort(fileList(fs)) // sort list on name, same as in the database
@@ -199,7 +197,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
moreDb := dbi.Next()
fsi := 0
var maxLocalVer uint64
var maxLocalVer int64
for {
var newName, oldName []byte
@@ -246,7 +244,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
if debugDB {
l.Debugln("generic replace; exists - compare")
}
var ef protocol.FileInfoTruncated
var ef FileInfoTruncated
ef.UnmarshalXDR(dbi.Value())
if fs[fsi].Version > ef.Version ||
(fs[fsi].Version == ef.Version && fs[fsi].Flags != ef.Flags) {
@@ -290,9 +288,9 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
return maxLocalVer
}
func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) int64 {
// TODO: Return the remaining maxLocalVer?
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
// Database has a file that we are missing. Remove it.
if debugDB {
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
@@ -306,9 +304,9 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) u
})
}
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
var tf protocol.FileInfoTruncated
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) int64 {
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
var tf FileInfoTruncated
err := tf.UnmarshalXDR(dbi.Value())
if err != nil {
panic(err)
@@ -337,7 +335,7 @@ func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.F
})
}
func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) int64 {
runtime.GC()
batch := new(leveldb.Batch)
@@ -358,7 +356,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
snap.Release()
}()
var maxLocalVer uint64
var maxLocalVer int64
for _, f := range fs {
name := []byte(f.Name)
fk := deviceKey(folder, device, name)
@@ -378,7 +376,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
continue
}
var ef protocol.FileInfoTruncated
var ef FileInfoTruncated
err = ef.UnmarshalXDR(bs)
if err != nil {
panic(err)
@@ -408,7 +406,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
return maxLocalVer
}
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) uint64 {
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) int64 {
if debugDB {
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
}
@@ -430,7 +428,7 @@ func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) ui
// ldbUpdateGlobal 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 ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device, file []byte, version uint64) bool {
func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device, file []byte, version int64) bool {
if debugDB {
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file, version)
}
@@ -528,7 +526,7 @@ func ldbRemoveFromGlobal(db dbReader, batch dbWriter, folder, device, file []byt
}
}
func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn fileIterator) {
func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterator) {
start := deviceKey(folder, device, nil) // before all folder/device files
limit := deviceKey(folder, device, []byte{0xff, 0xff, 0xff, 0xff}) // after all folder/device files
snap, err := db.GetSnapshot()
@@ -559,7 +557,7 @@ func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn fileIt
}
}
func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []byte, f protocol.FileInfoTruncated) bool) {
func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []byte, f FileInfoTruncated) bool) {
runtime.GC()
start := deviceKey(folder, nil, nil) // before all folder/device files
@@ -583,22 +581,33 @@ func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []b
for dbi.Next() {
device := deviceKeyDevice(dbi.Key())
var f protocol.FileInfoTruncated
var f FileInfoTruncated
err := f.UnmarshalXDR(dbi.Value())
if err != nil {
panic(err)
}
switch f.Name {
case "", ".", "..", "/": // A few obviously invalid filenames
l.Infof("Dropping invalid filename %q from database", f.Name)
batch := new(leveldb.Batch)
ldbRemoveFromGlobal(db, batch, folder, device, nil)
batch.Delete(dbi.Key())
db.Write(batch, nil)
continue
}
if cont := fn(device, f); !cont {
return
}
}
}
func ldbGet(db *leveldb.DB, folder, device, file []byte) protocol.FileInfo {
func ldbGet(db *leveldb.DB, folder, device, file []byte) (protocol.FileInfo, bool) {
nk := deviceKey(folder, device, file)
bs, err := db.Get(nk, nil)
if err == leveldb.ErrNotFound {
return protocol.FileInfo{}
return protocol.FileInfo{}, false
}
if err != nil {
panic(err)
@@ -609,10 +618,10 @@ func ldbGet(db *leveldb.DB, folder, device, file []byte) protocol.FileInfo {
if err != nil {
panic(err)
}
return f
return f, true
}
func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf, bool) {
k := globalKey(folder, file)
snap, err := db.GetSnapshot()
if err != nil {
@@ -633,7 +642,7 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
}
bs, err := snap.Get(k, nil)
if err == leveldb.ErrNotFound {
return protocol.FileInfo{}
return nil, false
}
if err != nil {
panic(err)
@@ -658,15 +667,14 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
panic(err)
}
var f protocol.FileInfo
err = f.UnmarshalXDR(bs)
fi, err := unmarshalTrunc(bs, truncate)
if err != nil {
panic(err)
}
return f
return fi, true
}
func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn fileIterator) {
func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn Iterator) {
runtime.GC()
start := globalKey(folder, nil)
@@ -754,7 +762,7 @@ func ldbAvailability(db *leveldb.DB, folder, file []byte) []protocol.DeviceID {
return devices
}
func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn fileIterator) {
func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterator) {
runtime.GC()
start := globalKey(folder, nil)
@@ -790,7 +798,7 @@ outer:
have := false // If we have the file, any version
need := false // If we have a lower version of the file
var haveVersion uint64
var haveVersion int64
for _, v := range vl.versions {
if bytes.Compare(v.device, device) == 0 {
have = true
@@ -938,9 +946,9 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
dbi.Release()
}
func unmarshalTrunc(bs []byte, truncate bool) (protocol.FileIntf, error) {
func unmarshalTrunc(bs []byte, truncate bool) (FileIntf, error) {
if truncate {
var tf protocol.FileInfoTruncated
var tf FileInfoTruncated
err := tf.UnmarshalXDR(bs)
return tf, err
} else {

View File

@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package files
package db
import (
"bytes"

View File

@@ -2,7 +2,7 @@
// This file is automatically generated by genxdr. Do not edit.
// ************************************************************
package files
package db
import (
"bytes"
@@ -31,7 +31,7 @@ fileVersion Structure:
struct fileVersion {
unsigned hyper version;
hyper version;
opaque device<>;
}
@@ -62,7 +62,7 @@ func (o fileVersion) AppendXDR(bs []byte) ([]byte, error) {
}
func (o fileVersion) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteUint64(o.version)
xw.WriteUint64(uint64(o.version))
xw.WriteBytes(o.device)
return xw.Tot(), xw.Error()
}
@@ -79,7 +79,7 @@ func (o *fileVersion) UnmarshalXDR(bs []byte) error {
}
func (o *fileVersion) decodeXDR(xr *xdr.Reader) error {
o.version = xr.ReadUint64()
o.version = int64(xr.ReadUint64())
o.device = xr.ReadBytes()
return xr.Error()
}

View File

@@ -13,42 +13,50 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// Package files provides a set type to track local/remote files with newness
// Package db provides a set type to track local/remote files with newness
// checks. We must do a certain amount of normalization in here. We will get
// fed paths with either native or wire-format separators and encodings
// depending on who calls us. We transform paths to wire-format (NFC and
// slashes) on the way to the database, and transform to native format
// (varying separator and encoding) on the way back out.
package files
package db
import (
"sync"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/lamport"
"github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syndtr/goleveldb/leveldb"
)
type fileRecord struct {
File protocol.FileInfo
Usage int
Global bool
}
type bitset uint64
type Set struct {
localVersion map[protocol.DeviceID]uint64
type FileSet struct {
localVersion map[protocol.DeviceID]int64
mutex sync.Mutex
folder string
db *leveldb.DB
blockmap *BlockMap
}
func NewSet(folder string, db *leveldb.DB) *Set {
var s = Set{
localVersion: make(map[protocol.DeviceID]uint64),
// FileIntf is the set of methods implemented by both protocol.FileInfo and
// protocol.FileInfoTruncated.
type FileIntf interface {
Size() int64
IsDeleted() bool
IsInvalid() bool
IsDirectory() bool
IsSymlink() bool
HasPermissionBits() bool
}
// The Iterator is called with either a protocol.FileInfo or a
// protocol.FileInfoTruncated (depending on the method) and returns true to
// continue iteration, false to stop.
type Iterator func(f FileIntf) bool
func NewFileSet(folder string, db *leveldb.DB) *FileSet {
var s = FileSet{
localVersion: make(map[protocol.DeviceID]int64),
folder: folder,
db: db,
blockmap: NewBlockMap(db, folder),
@@ -57,7 +65,7 @@ func NewSet(folder string, db *leveldb.DB) *Set {
ldbCheckGlobals(db, []byte(folder))
var deviceID protocol.DeviceID
ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f protocol.FileInfoTruncated) bool {
ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
if f.LocalVersion > s.localVersion[deviceID] {
s.localVersion[deviceID] = f.LocalVersion
@@ -73,7 +81,7 @@ func NewSet(folder string, db *leveldb.DB) *Set {
return &s
}
func (s *Set) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
if debug {
l.Debugf("%s Replace(%v, [%d])", s.folder, device, len(fs))
}
@@ -91,7 +99,7 @@ func (s *Set) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
}
}
func (s *Set) ReplaceWithDelete(device protocol.DeviceID, fs []protocol.FileInfo) {
func (s *FileSet) ReplaceWithDelete(device protocol.DeviceID, fs []protocol.FileInfo) {
if debug {
l.Debugf("%s ReplaceWithDelete(%v, [%d])", s.folder, device, len(fs))
}
@@ -107,7 +115,7 @@ func (s *Set) ReplaceWithDelete(device protocol.DeviceID, fs []protocol.FileInfo
}
}
func (s *Set) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
if debug {
l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
}
@@ -118,8 +126,8 @@ func (s *Set) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
discards := make([]protocol.FileInfo, 0, len(fs))
updates := make([]protocol.FileInfo, 0, len(fs))
for _, newFile := range fs {
existingFile := ldbGet(s.db, []byte(s.folder), device[:], []byte(newFile.Name))
if existingFile.Version <= newFile.Version {
existingFile, ok := ldbGet(s.db, []byte(s.folder), device[:], []byte(newFile.Name))
if !ok || existingFile.Version <= newFile.Version {
discards = append(discards, existingFile)
updates = append(updates, newFile)
}
@@ -132,65 +140,79 @@ func (s *Set) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
}
}
func (s *Set) WithNeed(device protocol.DeviceID, fn fileIterator) {
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithNeed(%v)", s.folder, device)
}
ldbWithNeed(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
}
func (s *Set) WithNeedTruncated(device protocol.DeviceID, fn fileIterator) {
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
}
ldbWithNeed(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
}
func (s *Set) WithHave(device protocol.DeviceID, fn fileIterator) {
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithHave(%v)", s.folder, device)
}
ldbWithHave(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
}
func (s *Set) WithHaveTruncated(device protocol.DeviceID, fn fileIterator) {
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
}
ldbWithHave(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
}
func (s *Set) WithGlobal(fn fileIterator) {
func (s *FileSet) WithGlobal(fn Iterator) {
if debug {
l.Debugf("%s WithGlobal()", s.folder)
}
ldbWithGlobal(s.db, []byte(s.folder), false, nativeFileIterator(fn))
}
func (s *Set) WithGlobalTruncated(fn fileIterator) {
func (s *FileSet) WithGlobalTruncated(fn Iterator) {
if debug {
l.Debugf("%s WithGlobalTruncated()", s.folder)
}
ldbWithGlobal(s.db, []byte(s.folder), true, nativeFileIterator(fn))
}
func (s *Set) Get(device protocol.DeviceID, file string) protocol.FileInfo {
f := ldbGet(s.db, []byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
func (s *FileSet) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {
f, ok := ldbGet(s.db, []byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
f.Name = osutil.NativeFilename(f.Name)
return f
return f, ok
}
func (s *Set) GetGlobal(file string) protocol.FileInfo {
f := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)))
func (s *FileSet) GetGlobal(file string) (protocol.FileInfo, bool) {
fi, ok := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
if !ok {
return protocol.FileInfo{}, false
}
f := fi.(protocol.FileInfo)
f.Name = osutil.NativeFilename(f.Name)
return f
return f, true
}
func (s *Set) Availability(file string) []protocol.DeviceID {
func (s *FileSet) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
fi, ok := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
if !ok {
return FileInfoTruncated{}, false
}
f := fi.(FileInfoTruncated)
f.Name = osutil.NativeFilename(f.Name)
return f, true
}
func (s *FileSet) Availability(file string) []protocol.DeviceID {
return ldbAvailability(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)))
}
func (s *Set) LocalVersion(device protocol.DeviceID) uint64 {
func (s *FileSet) LocalVersion(device protocol.DeviceID) int64 {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.localVersion[device]
@@ -218,13 +240,13 @@ func normalizeFilenames(fs []protocol.FileInfo) {
}
}
func nativeFileIterator(fn fileIterator) fileIterator {
return func(fi protocol.FileIntf) bool {
func nativeFileIterator(fn Iterator) Iterator {
return func(fi FileIntf) bool {
switch f := fi.(type) {
case protocol.FileInfo:
f.Name = osutil.NativeFilename(f.Name)
return fn(f)
case protocol.FileInfoTruncated:
case FileInfoTruncated:
f.Name = osutil.NativeFilename(f.Name)
return fn(f)
default:

View File

@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package files_test
package db_test
import (
"bytes"
@@ -22,9 +22,9 @@ import (
"sort"
"testing"
"github.com/syncthing/syncthing/internal/files"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/lamport"
"github.com/syncthing/syncthing/internal/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
)
@@ -43,15 +43,15 @@ func genBlocks(n int) []protocol.BlockInfo {
for j := range h {
h[j] = byte(i + j)
}
b[i].Size = uint32(i)
b[i].Size = int32(i)
b[i].Hash = h
}
return b
}
func globalList(s *files.Set) []protocol.FileInfo {
func globalList(s *db.FileSet) []protocol.FileInfo {
var fs []protocol.FileInfo
s.WithGlobal(func(fi protocol.FileIntf) bool {
s.WithGlobal(func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
fs = append(fs, f)
return true
@@ -59,9 +59,9 @@ func globalList(s *files.Set) []protocol.FileInfo {
return fs
}
func haveList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo {
func haveList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
var fs []protocol.FileInfo
s.WithHave(n, func(fi protocol.FileIntf) bool {
s.WithHave(n, func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
fs = append(fs, f)
return true
@@ -69,9 +69,9 @@ func haveList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo {
return fs
}
func needList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo {
func needList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
var fs []protocol.FileInfo
s.WithNeed(n, func(fi protocol.FileIntf) bool {
s.WithNeed(n, func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
fs = append(fs, f)
return true
@@ -106,12 +106,12 @@ func (l fileList) String() string {
func TestGlobalSet(t *testing.T) {
lamport.Default = lamport.Clock{}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
local0 := fileList{
protocol.FileInfo{Name: "a", Version: 1000, Blocks: genBlocks(1)},
@@ -209,27 +209,42 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Need incorrect;\n A: %v !=\n E: %v", n, expectedRemoteNeed)
}
f := m.Get(protocol.LocalDeviceID, "b")
f, ok := m.Get(protocol.LocalDeviceID, "b")
if !ok {
t.Error("Unexpectedly not OK")
}
if fmt.Sprint(f) != fmt.Sprint(localTot[1]) {
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, localTot[1])
}
f = m.Get(remoteDevice0, "b")
f, ok = m.Get(remoteDevice0, "b")
if !ok {
t.Error("Unexpectedly not OK")
}
if fmt.Sprint(f) != fmt.Sprint(remote1[0]) {
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, remote1[0])
}
f = m.GetGlobal("b")
f, ok = m.GetGlobal("b")
if !ok {
t.Error("Unexpectedly not OK")
}
if fmt.Sprint(f) != fmt.Sprint(remote1[0]) {
t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, remote1[0])
}
f = m.Get(protocol.LocalDeviceID, "zz")
f, ok = m.Get(protocol.LocalDeviceID, "zz")
if ok {
t.Error("Unexpectedly OK")
}
if f.Name != "" {
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, protocol.FileInfo{})
}
f = m.GetGlobal("zz")
f, ok = m.GetGlobal("zz")
if ok {
t.Error("Unexpectedly OK")
}
if f.Name != "" {
t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, protocol.FileInfo{})
}
@@ -252,12 +267,12 @@ func TestGlobalSet(t *testing.T) {
func TestNeedWithInvalid(t *testing.T) {
lamport.Default = lamport.Clock{}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
s := files.NewSet("test", db)
s := db.NewFileSet("test", ldb)
localHave := fileList{
protocol.FileInfo{Name: "a", Version: 1000, Blocks: genBlocks(1)},
@@ -294,12 +309,12 @@ func TestNeedWithInvalid(t *testing.T) {
func TestUpdateToInvalid(t *testing.T) {
lamport.Default = lamport.Clock{}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
s := files.NewSet("test", db)
s := db.NewFileSet("test", ldb)
localHave := fileList{
protocol.FileInfo{Name: "a", Version: 1000, Blocks: genBlocks(1)},
@@ -331,12 +346,12 @@ func TestUpdateToInvalid(t *testing.T) {
func TestInvalidAvailability(t *testing.T) {
lamport.Default = lamport.Clock{}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
s := files.NewSet("test", db)
s := db.NewFileSet("test", ldb)
remote0Have := fileList{
protocol.FileInfo{Name: "both", Version: 1001, Blocks: genBlocks(2)},
@@ -372,11 +387,11 @@ func TestInvalidAvailability(t *testing.T) {
}
func TestLocalDeleted(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
lamport.Default = lamport.Clock{}
local1 := []protocol.FileInfo{
@@ -447,7 +462,7 @@ func TestLocalDeleted(t *testing.T) {
}
func Benchmark10kReplace(b *testing.B) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
b.Fatal(err)
}
@@ -459,7 +474,7 @@ func Benchmark10kReplace(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
m.ReplaceWithDelete(protocol.LocalDeviceID, local)
}
}
@@ -470,12 +485,12 @@ func Benchmark10kUpdateChg(b *testing.B) {
remote = append(remote, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Version: 1000})
}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
b.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
m.Replace(remoteDevice0, remote)
var local []protocol.FileInfo
@@ -502,11 +517,11 @@ func Benchmark10kUpdateSme(b *testing.B) {
remote = append(remote, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Version: 1000})
}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
b.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
m.Replace(remoteDevice0, remote)
var local []protocol.FileInfo
@@ -528,12 +543,12 @@ func Benchmark10kNeed2k(b *testing.B) {
remote = append(remote, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Version: 1000})
}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
b.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
m.Replace(remoteDevice0, remote)
var local []protocol.FileInfo
@@ -561,12 +576,12 @@ func Benchmark10kHaveFullList(b *testing.B) {
remote = append(remote, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Version: 1000})
}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
b.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
m.Replace(remoteDevice0, remote)
var local []protocol.FileInfo
@@ -594,12 +609,12 @@ func Benchmark10kGlobal(b *testing.B) {
remote = append(remote, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Version: 1000})
}
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
b.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
m.Replace(remoteDevice0, remote)
var local []protocol.FileInfo
@@ -622,12 +637,12 @@ func Benchmark10kGlobal(b *testing.B) {
}
func TestGlobalReset(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
local := []protocol.FileInfo{
{Name: "a", Version: 1000},
@@ -663,12 +678,12 @@ func TestGlobalReset(t *testing.T) {
}
func TestNeed(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
local := []protocol.FileInfo{
{Name: "a", Version: 1000},
@@ -704,12 +719,12 @@ func TestNeed(t *testing.T) {
}
func TestLocalVersion(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
m := files.NewSet("test", db)
m := db.NewFileSet("test", ldb)
local1 := []protocol.FileInfo{
{Name: "a", Version: 1000},
@@ -743,12 +758,12 @@ func TestLocalVersion(t *testing.T) {
}
func TestListDropFolder(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
s0 := files.NewSet("test0", db)
s0 := db.NewFileSet("test0", ldb)
local1 := []protocol.FileInfo{
{Name: "a", Version: 1000},
{Name: "b", Version: 1000},
@@ -756,7 +771,7 @@ func TestListDropFolder(t *testing.T) {
}
s0.Replace(protocol.LocalDeviceID, local1)
s1 := files.NewSet("test1", db)
s1 := db.NewFileSet("test1", ldb)
local2 := []protocol.FileInfo{
{Name: "d", Version: 1002},
{Name: "e", Version: 1002},
@@ -767,7 +782,7 @@ func TestListDropFolder(t *testing.T) {
// Check that we have both folders and their data is in the global list
expectedFolderList := []string{"test0", "test1"}
if actualFolderList := files.ListFolders(db); !reflect.DeepEqual(actualFolderList, expectedFolderList) {
if actualFolderList := db.ListFolders(ldb); !reflect.DeepEqual(actualFolderList, expectedFolderList) {
t.Fatalf("FolderList mismatch\nE: %v\nA: %v", expectedFolderList, actualFolderList)
}
if l := len(globalList(s0)); l != 3 {
@@ -779,10 +794,10 @@ func TestListDropFolder(t *testing.T) {
// Drop one of them and check that it's gone.
files.DropFolder(db, "test1")
db.DropFolder(ldb, "test1")
expectedFolderList = []string{"test0"}
if actualFolderList := files.ListFolders(db); !reflect.DeepEqual(actualFolderList, expectedFolderList) {
if actualFolderList := db.ListFolders(ldb); !reflect.DeepEqual(actualFolderList, expectedFolderList) {
t.Fatalf("FolderList mismatch\nE: %v\nA: %v", expectedFolderList, actualFolderList)
}
if l := len(globalList(s0)); l != 3 {
@@ -794,12 +809,12 @@ func TestListDropFolder(t *testing.T) {
}
func TestGlobalNeedWithInvalid(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
s := files.NewSet("test1", db)
s := db.NewFileSet("test1", ldb)
rem0 := fileList{
protocol.FileInfo{Name: "a", Version: 1002, Blocks: genBlocks(4)},
@@ -834,12 +849,12 @@ func TestGlobalNeedWithInvalid(t *testing.T) {
}
func TestLongPath(t *testing.T) {
db, err := leveldb.Open(storage.NewMemStorage(), nil)
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
s := files.NewSet("test", db)
s := db.NewFileSet("test", ldb)
var b bytes.Buffer
for i := 0; i < 100; i++ {

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