Compare commits

..

473 Commits

Author SHA1 Message Date
Jakob Borg
ad8c266f76 Update docs & translation 2015-11-15 18:15:10 +01:00
Jakob Borg
807c3bdcc7 Merge pull request #2473 from syncthing/revert-2470-nostoppedrescan
Revert "Don't show "Rescan" button for stopped folders"
2015-11-14 21:10:52 +01:00
Audrius Butkevicius
62efbd17de Revert "Don't show "Rescan" button for stopped folders" 2015-11-14 14:01:22 -05:00
Audrius Butkevicius
d99350fd61 Merge pull request #2470 from calmh/nostoppedrescan
Don't show "Rescan" button for stopped folders
2015-11-14 09:09:28 -05:00
Jakob Borg
40c70a9a2a Don't show "Rescan" button for stopped folders 2015-11-14 12:14:09 +01:00
Audrius Butkevicius
8fb7f40a6a Merge pull request #2468 from calmh/removefolder
Remove folder without restart (fixes #2262)
2015-11-13 09:51:58 -05:00
Jakob Borg
2f12d41d9d Don't dirty blockmap key between lookups (fixes #2455) 2015-11-13 15:44:30 +01:00
Jakob Borg
9fbdb6b305 Cancel a running scan 2015-11-13 15:30:21 +01:00
Jakob Borg
73285cadb6 Remove folder without restart (fixes #2262) 2015-11-13 13:32:52 +01:00
Jakob Borg
56f1c295b6 Merge pull request #2462 from AudriusButkevicius/mtimes
Use virtualMtime when deciding if a file is up to date
2015-11-12 08:48:45 +01:00
Jakob Borg
7f76ed8413 Fix build 2015-11-12 08:46:13 +01:00
AudriusButkevicius
f7edd36931 Use virtualMtime when deciding if a file is up to date 2015-11-12 03:40:06 +00:00
Audrius Butkevicius
c39f2b7c05 Merge pull request #2463 from boone/fix_typos
Fix typos.
2015-11-11 21:29:34 -05:00
Mike Boone
342036408e Fix typos. 2015-11-11 21:20:34 -05:00
Jakob Borg
f4904fce17 Merge pull request #2449 from Stefan-Code/upgrade-system
made upgrade-system smarter (fixes #2446)
2015-11-10 21:15:02 +01:00
Stefan Kuntz
2abb2de753 Made upgrade-system smarter (fixes #2446) 2015-11-10 17:41:50 +01:00
Jakob Borg
ef0a0db07e More local discovery URL debugging (ref #2444) 2015-11-10 10:16:55 +01:00
Jakob Borg
a45795efec Add tylerbrazier 2015-11-10 08:20:16 +01:00
Jakob Borg
88ae353aef Merge pull request #2443 from tylerbrazier/master
Audit logins with new Login event (fixes #2377)
2015-11-10 08:19:03 +01:00
Tyler Brazier
97b9690711 Audit logins with new LoginAttempt event (fixes #2377) 2015-11-10 00:49:51 -05:00
Audrius Butkevicius
48ce356d5c Merge pull request #2448 from calmh/connhandling
Refactor out methods resolveAddresses, connectDirect, connectViaRelay
2015-11-09 22:01:06 -05:00
Audrius Butkevicius
92451f94bc Merge pull request #2450 from syncthing/Zillode-patch-1
Correct commentary for ConnectionStats
2015-11-09 18:32:19 -05:00
Zillode
593632045d Correct commentary for ConnectionStats
Related to https://github.com/syncthing/syncthing-android/issues/473 and 944d9c84a0
ConnectionStats returns connection information for all devices, even when disconnected.
2015-11-09 23:58:06 +01:00
Jakob Borg
e3c55ef307 Fix address list in DeviceDiscovered, add debug prints (ref #2444) 2015-11-09 21:36:17 +01:00
Jakob Borg
edf1730fd2 Merge pull request #2447 from alex2108/master
Add default-v4 and default-v6 as options for discovery
2015-11-09 16:02:37 +01:00
Alexander Graf
34cd8e3f95 Add default-v4 and default-v6 as options for discovery 2015-11-09 15:56:46 +01:00
Jakob Borg
242cce022a Refactor out methods resolveAddresses, connectDirect, connectViaRelay 2015-11-09 15:35:32 +01:00
Jakob Borg
19bf51cefb Revert "Merge pull request #2440 from Stefan-Code/master"
This reverts commit 81bc6bf34b, reversing
changes made to 7de736e8d0.

Unfortunately this tricks the upgrade system into picking the wrong
binary. We need to fix the upgrade system before merging this.
2015-11-09 14:23:26 +01:00
Jakob Borg
74a2e80142 Update docs & translations 2015-11-09 14:00:10 +01:00
Jakob Borg
b9b630e3b6 Change certificate on discovery-2 2015-11-09 13:58:44 +01:00
Jakob Borg
f0bdf833d1 Oh come *on* 2015-11-09 12:39:09 +01:00
Jakob Borg
81bc6bf34b Merge pull request #2440 from Stefan-Code/master
Added ufw firewall application preset
2015-11-09 12:19:42 +01:00
Jakob Borg
7de736e8d0 Ok then, lower case 2015-11-09 12:17:23 +01:00
Jakob Borg
cd097e0fce Add Stefan-Code 2015-11-09 12:14:32 +01:00
Stefan-Code
cc81a7ccfe added ufw firewall application preset (fixes #2435) 2015-11-09 11:58:59 +01:00
Jakob Borg
58d320c270 String slice formatting 2015-11-08 18:06:06 +01:00
Jakob Borg
59565fd1d1 Woops 2015-11-07 11:25:00 +01:00
Jakob Borg
55592137a2 Use constructor functions for FolderConfiguration and DeviceConfiguration 2015-11-07 09:50:04 +01:00
Jakob Borg
58523060f0 Actually do negative caching on failed discovery lookups (fixes #2434) 2015-11-06 17:14:20 +01:00
Audrius Butkevicius
07d53be9fc Merge pull request #2432 from calmh/cachepath
Cache the folderconfig Path() call
2015-11-06 08:37:39 +00:00
Jakob Borg
d4b0235a8b Correctly report the default relay server in usage stats 2015-11-06 07:16:15 +00:00
Jakob Borg
34aa41e17b Cache the Path() call, as it's quite expensive and called a lot 2015-11-06 07:11:22 +00:00
Jakob Borg
36f6a9347c Benchmark must use *db.Instance 2015-11-05 17:46:53 +00:00
Jakob Borg
d49d386ef2 Docs and translation update 2015-11-05 15:47:06 +00:00
Jakob Borg
00c363829c Refactor: move folder prepare to it's own function 2015-11-05 08:01:47 +00:00
Audrius Butkevicius
a9691dbdf4 Merge pull request #2430 from calmh/jsondecode
Run JSON decoding through the usual setting of defaults and fixing up
2015-11-04 20:44:56 +00:00
Jakob Borg
9df701906f Run JSON decoding through the usual setting of defaults and fixing up
I see no reason not to do this, and it gives a unified place (the prepare()
call) to initialize cached attributes and so on.
2015-11-04 20:33:10 +00:00
Jakob Borg
283671fa9d Remove old dead code 2015-11-04 20:15:36 +00:00
Jakob Borg
435c29755d We haven't had cleartext passwords in the config for ages 2015-11-04 20:15:11 +00:00
Jakob Borg
686f91777c Don't force rescan dirs and symlinks
We can't look for changed modtime on these as we don't track the modtime
to start with.
2015-11-04 19:53:07 +00:00
Audrius Butkevicius
2aa028facb Add user-agent header, capitalize headers as others seems to do it (fixes #2422) 2015-10-31 15:36:08 +00:00
Audrius Butkevicius
b4bbd050c2 Merge pull request #2424 from calmh/dbinstance
We should pass around db.Instance instead of leveldb.DB
2015-10-31 12:51:23 +00:00
Jakob Borg
2a4fc28318 We should pass around db.Instance instead of leveldb.DB
We're going to need the db.Instance to keep some state, and for that to
work we need the same one passed around everywhere. Hence this moves the
leveldb-specific file opening stuff into the db package and exports the
dbInstance type.
2015-10-31 12:35:30 +01:00
Jakob Borg
313485e406 Remove file that snuck in by mistake 2015-10-31 11:38:59 +01:00
Jakob Borg
faf4267c73 Refactor: the various db key functions should be instance methods 2015-10-31 11:27:04 +01:00
Jakob Borg
e6277d799f Undo incorrect revert of folder ID in test config 2015-10-31 11:27:04 +01:00
Jakob Borg
cdbc8004fb Comment pedantry 2015-10-31 11:16:07 +01:00
Audrius Butkevicius
1fac2f686d Merge pull request #2423 from calmh/urls
Create a correct URL is more difficult than just slapping on a scheme (fixes #2316)
2015-10-30 20:50:32 +00:00
Jakob Borg
08c8d679ac Create a correct URL is more difficult than just slapping on a scheme (fixes #2316) 2015-10-30 21:22:40 +01:00
Jakob Borg
48c34b7234 Translation update 2015-10-30 10:23:09 +01:00
Audrius Butkevicius
28603f0d2c Merge pull request #2420 from calmh/closelog
Enable log rotation by automatically closing log file (fixes #2251)
2015-10-29 15:25:51 +00:00
Jakob Borg
b2855f02fe Enable log rotation by automatically closing log file (fixes #2251) 2015-10-29 16:04:07 +01:00
Audrius Butkevicius
bef3d88076 Merge pull request #2418 from calmh/fix2416
Rescan changed files before pulling on top of them (fixes #2416)
2015-10-29 08:15:36 +00:00
Jakob Borg
e1a8ea7dec Rescan changed files before pulling on top of them (fixes #2416) 2015-10-29 09:12:37 +01:00
Jakob Borg
c4ad97136f Move leveldb instance and transactions into separate files 2015-10-29 08:07:51 +01:00
Audrius Butkevicius
eab1d6782b Merge pull request #2415 from calmh/dbkeys
Add database and transaction instances
2015-10-28 21:50:29 +00:00
Jakob Borg
fd7b8ec77e Neater transaction handling 2015-10-28 22:04:00 +01:00
Jakob Borg
e28c991331 Create an instance type to tie database methods to 2015-10-28 21:03:05 +01:00
Jakob Borg
a52811dfa3 Don't use godep to run tests 2015-10-28 09:22:07 +01:00
Jakob Borg
9e210d705d The PublicKey() method is an addition in Go 1.4 2015-10-27 16:03:14 +01:00
Jakob Borg
c42f1b53ab pulorder.go -> pullorder.go 2015-10-27 12:14:14 +01:00
Jakob Borg
d171173e90 AlwaysLocalNets should not default to null 2015-10-27 12:04:51 +01:00
Jakob Borg
679f0f9363 Fix some config Copy() things we had forgotten 2015-10-27 11:53:42 +01:00
Jakob Borg
724c1e297f Remove handling of config versions < 10 (v0.11.0) 2015-10-27 11:46:33 +01:00
Jakob Borg
83154569b1 Refactor config types into separate files 2015-10-27 11:37:03 +01:00
Jakob Borg
e3c0fba34b Must not call hex.Dump in non-debug mode... 2015-10-27 10:27:18 +01:00
Jakob Borg
2b6a6b91f3 Remove unused struct field 2015-10-27 09:55:05 +01:00
Audrius Butkevicius
09a555fdd2 Merge pull request #2410 from calmh/hashalloc
Reduce allocations in HashFile
2015-10-27 08:45:38 +00:00
Jakob Borg
dc32f7f0a3 Reduce allocations in HashFile
By using copyBuffer we avoid a buffer allocation for each block we hash,
and by allocating space for the hashes up front we get one large backing
array instead of a small one for each block. For a 17 MiB file this
makes quite a difference in the amount of memory allocated:

	benchmark               old ns/op     new ns/op     delta
	BenchmarkHashFile-8     102045110     100459158     -1.55%

	benchmark               old allocs     new allocs     delta
	BenchmarkHashFile-8     415            144            -65.30%

	benchmark               old bytes     new bytes     delta
	BenchmarkHashFile-8     4504296       48104         -98.93%
2015-10-27 09:37:27 +01:00
Jakob Borg
1efd8d6c75 Add benchmark of HashFile 2015-10-27 09:30:34 +01:00
Jakob Borg
898fc72313 Fixup NICKS/authors 2015-10-27 08:38:25 +01:00
Jakob Borg
21c5806cbf Merge pull request #2405 from acogdev/master
Documentation and examples for autostarting with Upstart
2015-10-27 08:37:14 +01:00
Jakob Borg
464e6bec95 Log lines in REST should have lower case keys 2015-10-27 08:22:35 +01:00
Audrius Butkevicius
2ae832d919 Fix typo introduced 2015-10-25 21:10:55 +00:00
Audrius Butkevicius
5b03c2d949 Remove dead code 2015-10-25 20:46:09 +00:00
Audrius Butkevicius
f629a998a0 Change errNoDevice message to something more human 2015-10-25 13:27:26 +00:00
Jake Peterson
fe88781bc8 Changed system conf file to use $USER 2015-10-24 14:53:08 -06:00
Audrius Butkevicius
e725c97967 Merge pull request #2406 from syncthing/fix-non-local-local-networks
Consider 'AlwaysLocalNets' in bandwidth limiters
2015-10-24 13:07:04 +01:00
Matt Burke
63caf22671 Consider 'AlwaysLocalNets' in bandwidth limiters
'AlwaysLocalNets' was getting printed, but was getting used
when setting up connections. Now, the nets that should be
considered local are printed and used.
2015-10-24 01:14:25 -04:00
Jake Peterson
44790b1333 Added Jake Peterson to AUTHORS 2015-10-23 22:49:25 -06:00
Jake Peterson
b40bb64612 Documentation and examples for Ubuntu-like linux systems using
Upstart as the init system.
2015-10-23 22:37:35 -06:00
Audrius Butkevicius
7b5ab29a6d Because I am a muppet 2015-10-23 20:21:21 +01:00
Audrius Butkevicius
4fd614be09 Add a different mode to stindex 2015-10-23 20:02:38 +01:00
Audrius Butkevicius
73236e58c5 Close channel after the client is stopped 2015-10-22 23:09:02 +01:00
Jakob Borg
32414853c6 Fix Raleway font 2015-10-22 21:08:24 +02:00
Jakob Borg
f3dc78d457 Don't deadlock after checking relay client status (fixes #2404) 2015-10-22 20:32:15 +02:00
Jakob Borg
a32ac62208 Don't expect ending slash on Windows 2015-10-22 13:49:41 +02:00
Jakob Borg
d7a934cf0e Paths must not end with slash on Windows 2015-10-22 11:39:34 +02:00
Jakob Borg
503491392d Correct amount of stack unwinding for debug prints 2015-10-22 11:38:45 +02:00
Jakob Borg
b3a2bf367b Tweak new folder defaults 2015-10-22 09:01:10 +02:00
Jakob Borg
c19eff4872 Revive remote client version in the GUI 2015-10-22 08:53:28 +02:00
Jakob Borg
2941a813c2 Fix upgrade tests 2015-10-22 08:35:48 +02:00
Jakob Borg
0a022d38fa Upgrade lib should use same criteria for beta check as main 2015-10-22 08:28:35 +02:00
Jakob Borg
7ed3b3dd3a Docs update 2015-10-22 08:14:43 +02:00
Jakob Borg
ce52963d2b Update test configs to modern v0.12 defaults 2015-10-22 08:06:17 +02:00
Jakob Borg
9a1922fdc6 Merge pull request #2385 from AudriusButkevicius/you-are-next
Add separate client for dynamic relays (fixes #2368)
2015-10-22 08:01:22 +02:00
Audrius Butkevicius
967424a538 Merge pull request #2402 from calmh/truncfaster
Don't load block list in ...Truncated methods
2015-10-21 23:03:48 +01:00
Jakob Borg
83131103cf Don't load block list in ...Truncated methods
Speeds up and reduces allocations on those operations, at the price of
having a manually tweaked XDR decoder for FileInfoTruncated.

benchmark                         old ns/op      new ns/op      delta
BenchmarkReplaceAll-8             1868198122     1880206886     +0.64%
BenchmarkUpdateOneChanged-8       231852         172695         -25.51%
BenchmarkUpdateOneUnchanged-8     230624         179341         -22.24%
BenchmarkNeedHalf-8               104601744      109461427      +4.65%
BenchmarkHave-8                   29102480       34105026       +17.19%
BenchmarkGlobal-8                 150547687      172778045      +14.77%
BenchmarkNeedHalfTruncated-8      102471355      76564986       -25.28%
BenchmarkHaveTruncated-8          28758368       14277481       -50.35%
BenchmarkGlobalTruncated-8        151192913      106070136      -29.84%

benchmark                         old allocs     new allocs     delta
BenchmarkReplaceAll-8             555577         557554         +0.36%
BenchmarkUpdateOneChanged-8       1135           587            -48.28%
BenchmarkUpdateOneUnchanged-8     1135           587            -48.28%
BenchmarkNeedHalf-8               374780         374775         -0.00%
BenchmarkHave-8                   151992         152085         +0.06%
BenchmarkGlobal-8                 530033         530135         +0.02%
BenchmarkNeedHalfTruncated-8      374699         22160          -94.09%
BenchmarkHaveTruncated-8          151834         4904           -96.77%
BenchmarkGlobalTruncated-8        530037         30536          -94.24%

benchmark                         old bytes      new bytes      delta
BenchmarkReplaceAll-8             1765116216     1765305376     +0.01%
BenchmarkUpdateOneChanged-8       135085         93043          -31.12%
BenchmarkUpdateOneUnchanged-8     134976         92928          -31.15%
BenchmarkNeedHalf-8               44758752       44751791       -0.02%
BenchmarkHave-8                   11845052       11967172       +1.03%
BenchmarkGlobal-8                 80431136       80431065       -0.00%
BenchmarkNeedHalfTruncated-8      46526459       18243543       -60.79%
BenchmarkHaveTruncated-8          11348357       418998         -96.31%
BenchmarkGlobalTruncated-8        80977672       43116991       -46.75%
2015-10-21 23:49:10 +02:00
Audrius Butkevicius
9f4a0d3216 Merge pull request #2401 from calmh/blockmap2
Performance tweaks on leveldb code and blockmap
2015-10-21 22:32:10 +01:00
Jakob Borg
c1591a5efd Only run benchmarks with -tags benchmark
Avoids creating temp database and stuff on a normal test run
2015-10-21 23:19:26 +02:00
Jakob Borg
918ef4dff8 Use batches in blockmap, speeds up and reduces memory usage on large Replace and Update ops
benchmark                         old ns/op      new ns/op      delta
BenchmarkReplaceAll-8             2880834572     1868198122     -35.15%
BenchmarkUpdateOneChanged-8       236596         231852         -2.01%
BenchmarkUpdateOneUnchanged-8     227326         230624         +1.45%
BenchmarkNeedHalf-8               105151538      104601744      -0.52%
BenchmarkHave-8                   28827492       29102480       +0.95%
BenchmarkGlobal-8                 150768724      150547687      -0.15%
BenchmarkNeedHalfTruncated-8      104434216      102471355      -1.88%
BenchmarkHaveTruncated-8          27860093       28758368       +3.22%
BenchmarkGlobalTruncated-8        149972888      151192913      +0.81%

benchmark                         old allocs     new allocs     delta
BenchmarkReplaceAll-8             555451         555577         +0.02%
BenchmarkUpdateOneChanged-8       1135           1135           +0.00%
BenchmarkUpdateOneUnchanged-8     1135           1135           +0.00%
BenchmarkNeedHalf-8               374779         374780         +0.00%
BenchmarkHave-8                   151996         151992         -0.00%
BenchmarkGlobal-8                 530066         530033         -0.01%
BenchmarkNeedHalfTruncated-8      374702         374699         -0.00%
BenchmarkHaveTruncated-8          151834         151834         +0.00%
BenchmarkGlobalTruncated-8        530049         530037         -0.00%

benchmark                         old bytes      new bytes      delta
BenchmarkReplaceAll-8             5018351912     1765116216     -64.83%
BenchmarkUpdateOneChanged-8       135085         135085         +0.00%
BenchmarkUpdateOneUnchanged-8     134976         134976         +0.00%
BenchmarkNeedHalf-8               44769400       44758752       -0.02%
BenchmarkHave-8                   11930612       11845052       -0.72%
BenchmarkGlobal-8                 81523668       80431136       -1.34%
BenchmarkNeedHalfTruncated-8      46692342       46526459       -0.36%
BenchmarkHaveTruncated-8          11348357       11348357       +0.00%
BenchmarkGlobalTruncated-8        81843956       80977672       -1.06%
2015-10-21 23:05:23 +02:00
Jakob Borg
0d9a04c713 Reuse blockkey, speeds up large Update and Replace calls
benchmark                         old ns/op      new ns/op      delta
BenchmarkReplaceAll-8             2866418930     2880834572     +0.50%
BenchmarkUpdateOneChanged-8       226635         236596         +4.40%
BenchmarkUpdateOneUnchanged-8     229090         227326         -0.77%
BenchmarkNeedHalf-8               104483393      105151538      +0.64%
BenchmarkHave-8                   29288220       28827492       -1.57%
BenchmarkGlobal-8                 159269126      150768724      -5.34%
BenchmarkNeedHalfTruncated-8      108235000      104434216      -3.51%
BenchmarkHaveTruncated-8          28945489       27860093       -3.75%
BenchmarkGlobalTruncated-8        149355833      149972888      +0.41%

benchmark                         old allocs     new allocs     delta
BenchmarkReplaceAll-8             1054944        555451         -47.35%
BenchmarkUpdateOneChanged-8       1135           1135           +0.00%
BenchmarkUpdateOneUnchanged-8     1135           1135           +0.00%
BenchmarkNeedHalf-8               374777         374779         +0.00%
BenchmarkHave-8                   151995         151996         +0.00%
BenchmarkGlobal-8                 530063         530066         +0.00%
BenchmarkNeedHalfTruncated-8      374699         374702         +0.00%
BenchmarkHaveTruncated-8          151834         151834         +0.00%
BenchmarkGlobalTruncated-8        530021         530049         +0.01%

benchmark                         old bytes      new bytes      delta
BenchmarkReplaceAll-8             5074297112     5018351912     -1.10%
BenchmarkUpdateOneChanged-8       135097         135085         -0.01%
BenchmarkUpdateOneUnchanged-8     134976         134976         +0.00%
BenchmarkNeedHalf-8               44759436       44769400       +0.02%
BenchmarkHave-8                   11911138       11930612       +0.16%
BenchmarkGlobal-8                 81609867       81523668       -0.11%
BenchmarkNeedHalfTruncated-8      46588024       46692342       +0.22%
BenchmarkHaveTruncated-8          11348354       11348357       +0.00%
BenchmarkGlobalTruncated-8        79485168       81843956       +2.97%
2015-10-21 23:05:23 +02:00
Jakob Borg
0c0c69f0cf The GC runs are legacy and slows things down quite a bit
benchmark                         old ns/op      new ns/op      delta
BenchmarkReplaceAll-8             2942370526     2866418930     -2.58%
BenchmarkUpdateOneChanged-8       7402489        226635         -96.94%
BenchmarkUpdateOneUnchanged-8     7298777        229090         -96.86%
BenchmarkNeedHalf-8               113608416      104483393      -8.03%
BenchmarkHave-8                   29834263       29288220       -1.83%
BenchmarkGlobal-8                 162773699      159269126      -2.15%
BenchmarkNeedHalfTruncated-8      111943400      108235000      -3.31%
BenchmarkHaveTruncated-8          29490369       28945489       -1.85%
BenchmarkGlobalTruncated-8        165841081      149355833      -9.94%

benchmark                         old allocs     new allocs     delta
BenchmarkReplaceAll-8             1054942        1054944        +0.00%
BenchmarkUpdateOneChanged-8       1149           1135           -1.22%
BenchmarkUpdateOneUnchanged-8     1135           1135           +0.00%
BenchmarkNeedHalf-8               374774         374777         +0.00%
BenchmarkHave-8                   151995         151995         +0.00%
BenchmarkGlobal-8                 530042         530063         +0.00%
BenchmarkNeedHalfTruncated-8      374697         374699         +0.00%
BenchmarkHaveTruncated-8          151834         151834         +0.00%
BenchmarkGlobalTruncated-8        530050         530021         -0.01%

benchmark                         old bytes      new bytes      delta
BenchmarkReplaceAll-8             5074294728     5074297112     +0.00%
BenchmarkUpdateOneChanged-8       141048         135097         -4.22%
BenchmarkUpdateOneUnchanged-8     134976         134976         +0.00%
BenchmarkNeedHalf-8               44734813       44759436       +0.06%
BenchmarkHave-8                   11911634       11911138       -0.00%
BenchmarkGlobal-8                 80436854       81609867       +1.46%
BenchmarkNeedHalfTruncated-8      46514673       46588024       +0.16%
BenchmarkHaveTruncated-8          11348357       11348354       -0.00%
BenchmarkGlobalTruncated-8        81730740       79485168       -2.75%
2015-10-21 23:05:22 +02:00
Jakob Borg
943e80e26c Make benchmarks more realistic 2015-10-21 23:04:29 +02:00
Audrius Butkevicius
058a327584 Merge pull request #2397 from calmh/localsize
Keep LocalSize & GlobalSize data in RAM
2015-10-21 21:05:18 +01:00
Jakob Borg
1eca4170f7 Add test for LocalSize/GlobalSize results 2015-10-21 21:58:48 +02:00
Jakob Borg
c268e4ad1b Also keep GlobalSize in RAM 2015-10-21 21:58:48 +02:00
Jakob Borg
d4f81e8791 Keep LocalSize data in RAM 2015-10-21 21:58:48 +02:00
Audrius Butkevicius
4f0680c3c8 Add separate client for dynamic relays (fixes #2368)
Did some manual tests in the playground, such as kicking off two clients in parallel, first connecting,
second one getting a message about already being connected, falling back to the second address.
2015-10-21 20:08:14 +01:00
Jakob Borg
8c26fe44c3 Actually run protocol tests faster with -short (on Go 1.5...) 2015-10-21 14:45:18 +02:00
Jakob Borg
8c7d9f3dd2 Protocol tests should run faster with -short 2015-10-21 14:35:59 +02:00
Jakob Borg
f241b7e79a Global discovery should time out (fixes #2389) 2015-10-21 14:24:55 +02:00
Audrius Butkevicius
dc1f3503be Merge pull request #2391 from burkemw3/warn-overwrite-config-files
Emit warning when sync could overwrite configuration
2015-10-20 21:57:16 +01:00
Matt Burke
c2a5e180b8 Emit warning when sync could overwrite configuration
Overwriting configuration files is likely to happen if a
user syncs their home directories across computers. In this
case, the biggest risk is that all nodes will end up with
the same certificate and thus Device ID.

When the model prepares a folder for syncing, it checks to
see if the configuration files this instance is using are
getting synced. If the are getting synced, and they aren't
getting ignored, a warning is emitted. The model is used
so that when a new folder is added dynamically, a warning
is also emitted.

This will not prevent a user from shooting themselves in
the foot, and will not cover all cases (e.g. symlinks).
It should provide _something_ for many users in this
situation to go on, though.
2015-10-20 12:22:27 -04:00
Jakob Borg
7351217489 Relative GOBIN not allowed in Go 1.5.2+ 2015-10-20 15:59:38 +02:00
Jakob Borg
aa42aafe33 Don't panic on clean shutdown 2015-10-20 15:59:37 +02:00
Stefan Tatschner
b2da0120d6 Add syncthing-localdisco.7 2015-10-20 14:06:14 +02:00
Jakob Borg
9e84e09c26 Update specs link 2015-10-20 13:41:24 +02:00
Jakob Borg
32c1a9bc45 Docs & translation update 2015-10-20 09:59:50 +02:00
Audrius Butkevicius
a7e95922c1 Merge pull request #2395 from calmh/printhashrate
Print the single thread hash performance at startup
2015-10-20 08:10:52 +01:00
Jakob Borg
1392d0bc14 Print the single thread hash performance at startup 2015-10-20 08:51:59 +02:00
Jakob Borg
0f9fa9507e Tests must use locking to avoid race (fixes #2394) 2015-10-20 08:51:31 +02:00
Jakob Borg
1087535d8f Add address for burkemw3 2015-10-20 08:38:12 +02:00
Audrius Butkevicius
0e51f51979 Merge pull request #2379 from calmh/nodbvalidate
Don't validate requests against the database
2015-10-19 16:57:58 +01:00
Jakob Borg
90e0141ac5 Request() should return protocol errors 2015-10-19 15:14:41 +02:00
Jakob Borg
8435a8678e Don't validate requests against the database 2015-10-19 15:14:41 +02:00
Jakob Borg
bd2888fc3b Include maxConflicts -1 in test configs 2015-10-19 15:14:06 +02:00
Jakob Borg
6578ffe2c9 Merge pull request #2320 from AudriusButkevicius/proto
More proto changes
2015-10-18 08:47:30 +02:00
Jakob Borg
175340522f Merge pull request #2375 from AudriusButkevicius/proxy
Add proxy support (fixes #271)
2015-10-18 08:45:17 +02:00
Audrius Butkevicius
afc917b582 Fix tests 2015-10-17 09:48:41 +01:00
Audrius Butkevicius
9f4cd7716e Add more information about the folders to ClusterConfig 2015-10-17 09:46:46 +01:00
Jakob Borg
29b0017445 Merge pull request #2386 from AudriusButkevicius/epoint
Change relaypoolsrv endpoint
2015-10-17 09:14:35 +09:00
Jakob Borg
910a7c619a Merge pull request #2381 from AudriusButkevicius/maxcon
Allow limiting max conflicts (fixes #2282)
2015-10-17 09:10:31 +09:00
Audrius Butkevicius
273fac2028 Change relaypoolsrv endpoint
Just incase we want to show some stats in the future, such as a Geo-IP based map of where relays are, their dot size being proportional to global rate limits,
together with potentially how much data in total has been transferred, and how many sessions there by crawling relay status pages etc ;)
2015-10-17 00:10:01 +01:00
Audrius Butkevicius
a323d85d32 Add more information about the device to ClusterConfig 2015-10-16 19:40:12 +01:00
Audrius Butkevicius
491a33de0b Move device name into the protocol messages 2015-10-16 19:40:12 +01:00
Audrius Butkevicius
d6a0a44432 Update xdr 2015-10-16 19:40:11 +01:00
Audrius Butkevicius
752533489a Allow limiting max conflicts (fixes #2282) 2015-10-16 19:26:38 +01:00
Audrius Butkevicius
e4e3c19e96 Our dialer sets up TCP options 2015-10-16 19:18:22 +01:00
Jakob Borg
4ddb066728 Update lang-en 2015-10-16 18:54:41 +09:00
Jakob Borg
958bbbc8cb Fix mateon1 2015-10-16 18:54:07 +09:00
Jakob Borg
e15be5c2bf Merge pull request #2354 from eipiminus1/issue1361
Add trailing folder seperator to allow symlinks as folder path (fixes #1361)
2015-10-16 09:29:37 +09:00
Jakob Borg
cc436dc8cb Merge pull request #2372 from calmh/fix2371
Option -gui-address should accept scheme prefixes
2015-10-16 09:25:41 +09:00
Audrius Butkevicius
abbcd1f436 Patch up HTTP clients 2015-10-15 21:02:17 +01:00
Audrius Butkevicius
db494f2afc God damn godeps 2015-10-15 21:01:48 +01:00
Audrius Butkevicius
985ea29940 Add proxy support (fixes #271) 2015-10-15 21:01:42 +01:00
Jakob Borg
76359da58e Apparently -race adds some stuff gocov doesn't like. Lets try this instead. 2015-10-14 15:41:15 +09:00
Jakob Borg
368cd44558 Fix race conditions in model tests 2015-10-14 14:41:16 +09:00
Jakob Borg
cc1387ec0c Tests should be run with -race 2015-10-14 14:41:16 +09:00
Jakob Borg
7c79985a29 Clarify listen address 2015-10-13 22:07:22 +09:00
Jakob Borg
2b56961b54 ... with alternate email 2015-10-13 08:39:35 +09:00
Jakob Borg
ff9920cbdc Add eipiminus1 2015-10-13 08:38:03 +09:00
Jakob Borg
953a67bc3a Option -gui-address should accept scheme prefixes (fixes #2371) 2015-10-13 08:26:07 +09:00
Audrius Butkevicius
29343aec3a Fix division by zero (fixes #2373) 2015-10-12 18:57:15 +01:00
Audrius Butkevicius
2972472179 Add missing close 2015-10-12 17:10:59 +01:00
Jakob Borg
240e7b0835 Log/error fields changed name 2015-10-12 14:18:53 +09:00
Jakob Borg
baf5191433 Add mateon1 2015-10-12 14:13:38 +09:00
Jakob Borg
2645e87766 Errors may now be null, and that's fine 2015-10-12 10:12:57 +09:00
Jakob Borg
ec8bc02d33 Silence spurious debug (fixes #2369) 2015-10-12 10:11:58 +09:00
Yannic A
054bc970e2 Add trailing folder seperator to allow symlinks as folder path (fixes #1361) 2015-10-10 19:38:59 +02:00
Jakob Borg
c1c41242bb Merge pull request #2362 from AudriusButkevicius/sleepysleep
Make puller pause configurable
2015-10-10 20:13:10 +09:00
Audrius Butkevicius
169ff73d26 Merge pull request #2365 from mateon1/master
Change Out of Sync message in folder details (fixes #2364)
2015-10-10 11:37:24 +01:00
Audrius Butkevicius
d985ed553a Make puller pause configurable 2015-10-10 11:36:09 +01:00
Mateon1
dea1ef24d9 Change Out of Sync message in folder details (fixes #2364) 2015-10-09 01:34:11 +02:00
Jakob Borg
49f29a0453 Merge pull request #2355 from calmh/trace3
New debug logging infrastructure
2015-10-05 21:01:37 +09:00
Jakob Borg
76af9ba53d Implement facility based logger, debugging via REST API
This implements a new debug/trace infrastructure based on a slightly
hacked up logger. Instead of the traditional "if debug { ... }" I've
rewritten the logger to have no-op Debugln and Debugf, unless debugging
has been enabled for a given "facility". The "facility" is just a
string, typically a package name.

This will be slightly slower than before; but not that much as it's
mostly a function call that returns immediately. For the cases where it
matters (the Debugln takes a hex.Dump() of something for example, and
it's not in a very occasional "if err != nil" branch) there is an
l.ShouldDebug(facility) that is fast enough to be used like the old "if
debug".

The point of all this is that we can now toggle debugging for the
various packages on and off at runtime. There's a new method
/rest/system/debug that can be POSTed a set of facilities to enable and
disable debug for, or GET from to get a list of facilities with
descriptions and their current debug status.

Similarly a /rest/system/log?since=... can grab the latest log entries,
up to 250 of them (hardcoded constant in main.go) plus the initial few.

Not implemented in this commit (but planned) is a simple debug GUI
available on /debug that shows the current log in an easily pasteable
format and has checkboxes to enable the various debug facilities.

The debug instructions to a user then becomes "visit this URL, check
these boxes, reproduce your problem, copy and paste the log". The actual
log viewer on the hypothetical /debug URL can poll regularly for new log
entries and this bypass the 250 line limit.

The existing STTRACE=foo variable is still obeyed and just sets the
start state of the system.
2015-10-03 18:09:53 +02:00
Jakob Borg
2de364414f Adopt calmh/logger into lib/logger 2015-10-03 18:09:53 +02:00
Audrius Butkevicius
5ae84970e7 Merge pull request #2352 from uok/morespace
Add space for scrolling (fixes #2351)
2015-10-03 15:49:20 +01:00
Ben Schulz
141b0d38a6 Add space for scrolling (fixes #2351)
Add space at bottom for scrolling on small resolutions
2015-10-03 16:15:06 +02:00
Jakob Borg
44891b6924 Merge pull request #2349 from rumpelsepp/man-update
Update refresh.sh to fetch missing manpages
2015-10-02 09:13:59 +02:00
Stefan Tatschner
f008588307 Update refresh.sh to fetch missing manpages
syncthing-bep(7) and syncthing-localdisco(7) had been added.
2015-10-02 08:54:20 +02:00
Audrius Butkevicius
e481d03b5e Merge pull request #2347 from uok/clickselect
Select text on click
2015-10-01 14:57:48 +01:00
Ben Schulz
a8a73b60c4 Add select text on click
add (again?) select text on click
- device ID
- remote device ID
- API key
2015-10-01 14:34:23 +02:00
Audrius Butkevicius
7e8b76e8ea Merge pull request #2346 from calmh/dststatdir
Create missing directories
2015-10-01 10:47:16 +01:00
Jakob Borg
36c746bd9f Create missing directories 2015-10-01 09:43:16 +02:00
Audrius Butkevicius
7476c583e7 Merge pull request #2343 from calmh/discoprio2
Add discovery source priorities (fixes #2339)
2015-10-01 08:22:17 +01:00
Jakob Borg
89928ca8e4 Add discovery source priorities (fixes #2339)
Sources are given a priority, lower being better, when added to a
CachingMux.
2015-10-01 08:45:40 +02:00
Jakob Borg
38a3bf3ada Merge pull request #2317 from simplypeachy/patch-1
Add missing parameter in disk space warning
2015-10-01 08:10:36 +02:00
Jakob Borg
96b3d31b42 Add simplypeachy 2015-10-01 08:02:38 +02:00
Jakob Borg
362ae5c4bb Add copyright 2015-09-30 21:40:27 +02:00
Jakob Borg
dc303c2a71 Merge pull request #2340 from syncthing/revert-2337-caseins
Revert "Case insensitive renames, part 1"
2015-09-30 21:40:15 +02:00
Jakob Borg
be2ca0ea22 Revert "Case insensitive renames, part 1" 2015-09-30 21:40:04 +02:00
Audrius Butkevicius
460cb19839 Merge pull request #2337 from calmh/caseins
Case insensitive renames, part 1
2015-09-30 20:35:55 +01:00
Jakob Borg
ddfebb17cf Case insensitive renames, part 1 2015-09-30 12:41:29 +02:00
Audrius Butkevicius
375c9dd116 Merge pull request #2336 from calmh/overrides
Fix STGUIAPIKEY and STGUIADDR overrides (fixes #2335)
2015-09-30 08:47:44 +01:00
Jakob Borg
15716a0772 Fix STGUIAPIKEY and STGUIADDR overrides (fixes #2335)
Also removes STGUIAUTH and corresponding --gui-authentication as this
seems fundamentally insecure and I'm unsure of the actual use case for
it?
2015-09-30 09:36:11 +02:00
Audrius Butkevicius
6f6c1cd330 Merge pull request #2333 from calmh/brokenupgrade
Remove global cfg variable (fixes #2294)
2015-09-29 19:55:30 +01:00
Jakob Borg
36ac757c3a Remove global cfg variable (fixes #2294)
Not necessarily the easiest way to fix just this bug, but the root cause
was using the (at that point uninitialized) cfg variable, so it seemed
sensible to just get rid of it to avoid that kind of crap.
2015-09-29 20:23:15 +02:00
Audrius Butkevicius
b614cfffcb Merge pull request #2330 from calmh/eventids
Subscribing to events should not bump event ID (fixes #2329)
2015-09-29 19:05:57 +01:00
Audrius Butkevicius
b58a52c7b4 Merge pull request #2332 from calmh/brokenignore
Correctly report errors encountered parsing ignores (fixes #2309)
2015-09-29 17:15:07 +01:00
Jakob Borg
a80fc1b062 Correctly report errors encountered parsing ignores (fixes #2309, fixes #2296)
An error on opening .stignore will satisfy os.IsNotExist() and not be
reported. Other errors will be reported and stop the folder, including
is-not-exist errors from #include as these are passed through fmt.Errorf.

Also fixes minor issue where we would not print cause of folder stopping
to the log.

Also fixes minor issue with capitalization of errors.
2015-09-29 18:04:18 +02:00
Audrius Butkevicius
90d18189da Merge pull request #2331 from calmh/unique
CachingMux should return unique addresses only (fixes #2321)
2015-09-29 16:43:49 +01:00
Jakob Borg
22a2e95126 CachingMux should return unique addresses only (fixes #2321) 2015-09-29 17:40:39 +02:00
Jakob Borg
11e1a99e14 Subscribing to events should not bump event ID (fixes #2329) 2015-09-29 17:17:09 +02:00
Jakob Borg
3c6bfb880d Docs and translation update 2015-09-27 22:31:19 +02:00
Audrius Butkevicius
ad2c05c3f5 Merge pull request #2322 from calmh/colons
Don't naively join host and port using colon (fixes #2316)
2015-09-27 20:52:29 +01:00
Jakob Borg
fa75f54a05 Don't naively join host and port using colon (fixes #2316) 2015-09-27 21:44:08 +02:00
Audrius Butkevicius
6405fd4770 Merge pull request #2240 from calmh/masterfreespace
Don't check for free space on master folders (fixes #2236)
2015-09-27 11:11:03 +01:00
simplypeachy
f42f20c70c Update rwfolder.go
Revert copyright/grammar changes.
2015-09-27 09:50:54 +01:00
Jakob Borg
209bc59e8c Don't check for free space on master folders (fixes #2236)
Also clean up the logic a little, I thought it was a bit muddled.
2015-09-27 09:33:11 +02:00
Jakob Borg
84ee86f6b3 Merge pull request #2261 from rumpelsepp/readme-update
Cleanup markdown inline link mess
2015-09-27 09:28:51 +02:00
simplypeachy
ed7791d824 Add missing parameter in disk space warning
Plus copyright bump, minor grammatical fix.
2015-09-26 16:46:42 +01:00
Stefan Tatschner
0580b10385 Cleanup markdown inline link mess
I just wanted to add the freenode webchat link, because people who are
not used to irc can join the chatroom instantly. I tried to clean up the
markdown file a bit and removed the links to the footer; that makes the
"source code" less ugly.
2015-09-26 16:42:15 +02:00
Jakob Borg
47c7351b16 Clean up connection.Model interface a little 2015-09-26 13:19:22 +02:00
Jakob Borg
b158072a15 Merge pull request #2189 from burkemw3/lib-ify-connections
Decouple connections service from model
2015-09-26 13:18:23 +02:00
Jakob Borg
b6486c26e6 Add burkemw3 2015-09-26 13:15:25 +02:00
Audrius Butkevicius
da0ac96704 Merge pull request #2312 from rumpelsepp/systemd
Pull syncthing-inotify.service as an optional dep
2015-09-25 23:24:44 +01:00
Stefan Tatschner
af1fbda892 Pull syncthing-inotify.service as an optional dep
This patch adds syncthing-inotify.service as an optional dependency to
syncthing.service. That means, if syncthing-inotify.service is
available, it is started and stopped with syncthing.

See discussion here:
https://forum.syncthing.net/t/gnome-shell-extension-syncthing-icon/5759
2015-09-25 23:39:03 +02:00
Matt Burke
2234c45c19 Decouple connections service from model
The connections service no longer depends directly on the
syncthing model object, but on an interface instead. This
makes it drastically easier to write clients that handle
the model differently, but still want to benefit from
existing and future connections changes in the core.

This was motivated by burkemw3's interest in creating a
FUSE client that can present a view of the global model,
but not have all of the file data locally.

The actual decoupling was done by adding a connections.Model
interface. This interface is effectively an extension of the
protocol.Model interface that also handles connections
alongside the modified service.
2015-09-25 12:19:30 -04:00
Audrius Butkevicius
fd38fb684a Merge pull request #2307 from calmh/relaxlabels
Relax folder label restrictions
2015-09-25 13:05:28 +01:00
Jakob Borg
e0a16e08dd Relax folder label restrictions 2015-09-25 13:45:58 +02:00
Jakob Borg
43189dfe3a This unexpected EOF is really quite expected 2015-09-24 14:19:21 +02:00
Audrius Butkevicius
46d4f6037d Merge pull request #2303 from calmh/disco
Encapsulate local discovery address in struct
2015-09-22 23:07:49 +01:00
Jakob Borg
e522811a52 Encapsulate local discovery address in struct
The XDR encoder doesn't understart slices of strings very well. It can
encode and decode them, but there's no way to set limits on the length
of the strings themselves (only on the length of the slice), and the
generated diagrams are incorrect. This trivially works around this,
while also documenting what the string actually is (a URL).
2015-09-22 23:28:00 +02:00
Jakob Borg
6124bbb12a There is no local discovery query packet 2015-09-22 23:10:05 +02:00
Jakob Borg
fbf911cf7e Unbreak comments 2015-09-22 21:51:05 +02:00
Jakob Borg
7fdfa81fb8 Fix vet and lint complaints 2015-09-22 20:34:24 +02:00
Audrius Butkevicius
a4673f3007 Merge pull request #2302 from calmh/includeproto
Move external packages into the fold
2015-09-22 19:23:58 +01:00
Jakob Borg
24c499d282 Clean up deps 2015-09-22 19:39:07 +02:00
Jakob Borg
4581c57478 Fix import paths 2015-09-22 19:38:46 +02:00
Jakob Borg
f177924629 Rejiggle lib/relaysrv/* -> lib/relay/* 2015-09-22 19:37:03 +02:00
Jakob Borg
633e888ba7 Add 'lib/relaysrv/' from commit '6e126fb97e2ff566d35f8d8824e86793d22b2147'
git-subtree-dir: lib/relaysrv
git-subtree-mainline: 4316992d95
git-subtree-split: 6e126fb97e
2015-09-22 19:34:52 +02:00
Jakob Borg
4316992d95 Add 'lib/protocol/' from commit 'f91191218b192ace841c878f161832d19c09145a'
git-subtree-dir: lib/protocol
git-subtree-mainline: 5ecb8bdd8a
git-subtree-split: f91191218b
2015-09-22 19:34:29 +02:00
Jakob Borg
5ecb8bdd8a Correct success/error handling for multicast/broadcast sends 2015-09-22 16:04:48 +02:00
Jakob Borg
3b81d4b8a5 Update suture for data race bug 2015-09-21 15:48:37 +02:00
Jakob Borg
6e3b3dc4e7 Comment typo fix 2015-09-21 14:20:33 +02:00
Jakob Borg
8d421a62d2 Usage reporting should recognize new discovery server IP:s 2015-09-21 10:54:21 +02:00
Jakob Borg
185b0690c8 Further forgotten copyright notices 2015-09-21 10:43:36 +02:00
Jakob Borg
bd0e97023e We don't need a separate subscription lock
We forgot to lock it during replace, so data rate. This is simpler.
2015-09-21 10:42:07 +02:00
Jakob Borg
34ff0706a3 Add missing copyright notice 2015-09-21 10:34:20 +02:00
Jakob Borg
acba61babb Ping handling changes in protocol, removed from config here 2015-09-21 10:14:27 +02:00
Audrius Butkevicius
f91191218b Merge pull request #19 from syncthing/pingfix
Simplify and improve the ping mechanism
2015-09-21 09:00:40 +01:00
Jakob Borg
05c79ac8c2 Simplify and improve the ping mechanism
This should resolve the spurious ping timeouts we've had on low powered
boxes. Those errors are the result of us requiring a timely Pong
response to our Pings. However this is unnecessarily strict - as long as
we've received *anything* recently, we know the other peer is alive. So
the new mechanism removes the Pong message entirely and separates the
ping check into two routines:

 - One that makes sure to send ping periodically, if nothing else has
   been sent. This guarantees a message sent every 45-90 seconds.

 - One that checks how long it was since we last received a message. If
   it's longer than 300 seconds, we trigger an ErrTimeout.

So we're guaranteed to detect a connection failure in 300 + 300/2
seconds (due to how often the check runs) and we may detect it much
sooner if we get an actual error on the ping write (a connection reset
or so).

This is more sluggish than before but I think that's an OK price to pay
for making it actually work out of the box.

This removes the configurability of it, as the timeout on one side is
dependent on the send interval on the other side. Do we still need it
configurable?
2015-09-21 08:51:42 +02:00
Jakob Borg
24d2a93c0d Change default discovery server names 2015-09-20 22:30:31 +02:00
Audrius Butkevicius
de43080228 Merge pull request #2275 from calmh/tlsdisco
New global discovery protocol over HTTPS (fixes #628)
2015-09-20 20:16:04 +01:00
Jakob Borg
b0cd7be39b New global discovery protocol over HTTPS (fixes #628, fixes #1907) 2015-09-20 21:10:53 +02:00
Jakob Borg
a7169a6348 Add AppVeyor for Windows builds 2015-09-20 15:18:50 +02:00
Audrius Butkevicius
6e126fb97e Merge pull request #8 from syncthing/latency
Connected clients should know their own latency
2015-09-20 12:47:37 +01:00
Jakob Borg
22783d8f6c Connected clients should know their own latency 2015-09-20 13:40:24 +02:00
Jakob Borg
1d710bdcd9 Update lang-en.json 2015-09-18 14:11:01 +02:00
Audrius Butkevicius
5feffba1ff Merge pull request #2285 from uok/patch-1
Fix translation (fixes #2284)
2015-09-18 09:42:43 +01:00
Ben S.
6c56586a6b Add missing translation (fixes #2284) 2015-09-18 10:26:32 +02:00
Audrius Butkevicius
a32b66c7c3 Merge pull request #2278 from calmh/relaydep
lib/relay need not depend on lib/model any more
2015-09-14 20:16:31 +01:00
Jakob Borg
7e3c06191e lib/relay need not depend on lib/model any more 2015-09-14 20:19:39 +02:00
Jakob Borg
372c96c6b2 Merge pull request #5 from syncthing/info
Tweaks
2015-09-14 16:20:08 +02:00
Audrius Butkevicius
7073b8721a Merge pull request #7 from syncthing/ping
Server should respond to ping
2015-09-14 12:49:12 +01:00
Jakob Borg
fccb9c0bf4 Server should respond to ping 2015-09-14 13:46:20 +02:00
Audrius Butkevicius
3d09090c4e Merge pull request #2273 from calmh/relaydeps
Invert initialization dependence on relay/conns
2015-09-14 10:16:06 +01:00
Jakob Borg
596a49c112 Invert initialization dependence on relay/conns
This makes it so we can initialize the relay management and then give
that to the connection management, instead of the other way around.

This is important to me in the discovery revamp I'm doing, as otherwise
I get a circular dependency when constructing stuff, with relaying
depending on connection, connection depending on discovery, and
discovery depending on relaying.

With this fixed, discovery will depend on relaying, and connection will
depend on both discovery and relaying.
2015-09-14 10:21:55 +02:00
Jakob Borg
95fc253d6b Rename externalAddr to addressLister
It's going to have to list internal addresses too.
2015-09-13 18:09:44 +02:00
Jakob Borg
e6d5372029 Fix -no-upgrade 2015-09-13 18:04:58 +02:00
Jakob Borg
8e5c692244 Merge pull request #2269 from calmh/externaladdr
Add external address tracker object
2015-09-13 17:39:51 +02:00
Jakob Borg
e694c664e5 Add external address tracker object 2015-09-13 07:56:13 +02:00
Audrius Butkevicius
8779f93746 Merge pull request #2270 from calmh/diskspaceagain
Don't require free disk space when we might only update metadata
2015-09-12 22:11:26 +01:00
Jakob Borg
1f0f5c1e23 Don't require free disk space when we might only update metadata
Instead, make sure we do the check as part of CheckFolderHealth before
pulling, and individually per file to try to not run out of space at
that stage.

(The latter is far from fool proof as we may pull lots of stuff in
parallell, but it's worth a try.)
2015-09-12 23:00:43 +02:00
Audrius Butkevicius
f9f12131ae Drop all sessions when we realize a node has gone away 2015-09-11 22:29:50 +01:00
Audrius Butkevicius
7e0106da0c Tweaks
1. Advertise relay server paramters so that clients could make a decision wether or not to connect
2. Generate certificate if it's not there.
2015-09-11 20:06:14 +01:00
Audrius Butkevicius
aaf6bf3cd2 Merge pull request #2264 from calmh/customlan
Add custom networks that are considered local (internal routing, VPN etc)
2015-09-11 15:21:37 +01:00
Jakob Borg
fa95c82daf Add custom networks that are considered local (internal routing, VPN etc)
Allows things like this in the <options> element:

  <alwaysLocalNet>10.0.0.0/8</alwaysLocalNet>
2015-09-11 15:10:41 +02:00
Audrius Butkevicius
0da7142a59 Merge pull request #2231 from calmh/symtype
lib/symlinks need not depend on protocol
2015-09-11 12:28:38 +01:00
Jakob Borg
446a938b06 lib/symlinks need not depend on protocol 2015-09-11 12:55:25 +02:00
Audrius Butkevicius
21c3994650 Merge pull request #2260 from rumpelsepp/authors-update
Add my email to AUTHORS as well
2015-09-10 20:59:38 +01:00
Stefan Tatschner
f90f3a5ca6 Add my email to AUTHORS as well
The pull request has been merged faster than I was able to fix that
mistack. :) Sorry.
2015-09-10 21:23:38 +02:00
Audrius Butkevicius
a34d2b72c0 Merge pull request #2259 from rumpelsepp/mailmap-update
Add my "community email address" to NICKS
2015-09-10 20:18:51 +01:00
Stefan Tatschner
9d617dcfec Add my "community email address" to NICKS
Since my amount of received emails increased over the time, I would like
to separate my private email address from my "community email address".
2015-09-10 21:14:22 +02:00
Audrius Butkevicius
55506a0fc3 Merge pull request #2257 from rumpelsepp/docs-update
Update etc/linux-systemd/README.md
2015-09-10 20:12:31 +01:00
Stefan Tatschner
19c03f504f Update etc/linux-systemd/README.md 2015-09-10 21:04:52 +02:00
Jakob Borg
985a3436e2 Always show columns headers (accessibility) 2015-09-10 15:05:08 +02:00
Jakob Borg
9dae87c80c Allow configuration of releases URL 2015-09-10 14:16:44 +02:00
Jakob Borg
46364a38c6 Allow configuration of usage reporting URL 2015-09-10 14:08:40 +02:00
Jakob Borg
148b2b9d02 Fix crash when relaying or global discovery is disabled (fixes #2246) 2015-09-09 12:58:57 +02:00
Jakob Borg
64354b51c9 Generate certs with SHA256 signature instead of SHA1
Doesn't matter at all for BEP, but the same stuff is used by the web UI
and modern browsers are starting to dislike SHA1 extra much.
2015-09-09 12:55:17 +02:00
AudriusButkevicius
d180bc794b Handle 403 2015-09-07 18:12:18 +01:00
AudriusButkevicius
eab5fd5bdd Join relay pool by default 2015-09-07 09:21:23 +01:00
AudriusButkevicius
e3ca797dad Receive the invite, otherwise stop blocks, add extra arguments 2015-09-06 20:25:53 +01:00
Audrius Butkevicius
f2db1c8ab2 Merge pull request #2245 from calmh/ur
Relay server info, urVersion in ur
2015-09-06 20:24:00 +01:00
Jakob Borg
36b8a75ede Relay server info, urVersion in ur 2015-09-06 21:15:46 +02:00
AudriusButkevicius
11b2815b88 Add a test method, fix nil pointer panic 2015-09-06 18:35:38 +01:00
Audrius Butkevicius
0a42b85c06 Merge pull request #2242 from calmh/ur
Add interesting fields to usage report (fixes #559)
2015-09-06 17:19:18 +01:00
Jakob Borg
baf231e3b6 Add interesting fields to usage report (fixes #559) 2015-09-06 18:17:30 +02:00
Jakob Borg
e26e85b6d6 Only check pull file size if check is enabled (ref #2241) 2015-09-06 17:13:00 +02:00
Audrius Butkevicius
b2193b23e5 Merge pull request #2237 from calmh/freespace
Allow fractional percentages (fixes #2233)
2015-09-05 12:29:03 +01:00
Jakob Borg
2af3a92833 Allow fractional percentages (fixes #2233) 2015-09-05 12:39:15 +02:00
Jakob Borg
d2af6dcf38 CircleCI just plain doesn't work for us. 2015-09-04 15:31:03 +02:00
Jakob Borg
dd6b66167e Ok, last attempt now. 2015-09-04 15:17:23 +02:00
Jakob Borg
51a4a88a81 Dammit,CircleCI 2015-09-04 14:59:32 +02:00
Jakob Borg
516efb21cf Seriously CircleCI, come on... 2015-09-04 14:53:10 +02:00
Jakob Borg
2d4397af53 Clean out before build/test on CircleCI 2015-09-04 14:39:22 +02:00
Jakob Borg
06f319a380 lib/stats need not depend on protocol 2015-09-04 13:23:18 +02:00
Jakob Borg
37ed5a01e0 Fix sudden nil pointer dereference in walk 2015-09-04 13:13:08 +02:00
Jakob Borg
4a9997e449 lib/db need not depend on lib/config 2015-09-04 12:01:00 +02:00
Jakob Borg
54269553c8 lib/scanner need not depend on lib/ignore 2015-09-04 11:50:47 +02:00
AudriusButkevicius
541d05df1b Use new method name 2015-09-02 22:02:17 +01:00
AudriusButkevicius
3d6ea23511 Clarify names 2015-09-02 22:00:51 +01:00
AudriusButkevicius
c0554c9fbf Use a single socket for relaying 2015-09-02 21:35:52 +01:00
AudriusButkevicius
61130ea191 Add AcceptNoWrap to DowngradingListener 2015-09-02 21:25:56 +01:00
Jakob Borg
2581e56503 Use raw strings to describe regexes, avoids double escaping 2015-09-02 22:19:45 +02:00
Jakob Borg
fea0ae7f2f Merge pull request #2225 from AudriusButkevicius/onerelay
Pick a single relay (fixes #2182)
2015-09-02 22:15:00 +02:00
AudriusButkevicius
3299438cbd Move TLS utilities into a separate package 2015-09-02 21:05:54 +01:00
Audrius Butkevicius
5c160048df Merge pull request #2226 from calmh/ignores
Correctly handle (?i) in ignores (fixes #1953)
2015-09-02 20:44:32 +01:00
Jakob Borg
e3e1036dda Correctly handle (?i) in ignores (fixes #1953) 2015-09-02 21:12:41 +02:00
AudriusButkevicius
876d7ac85e Pick a single relay (fixes #2182) 2015-09-02 18:05:34 +01:00
Jakob Borg
70b37dc469 Add specific build setup for CircleCI 2015-09-02 14:56:16 +02:00
Jakob Borg
6393f69138 Second opinion build status via Circleci 2015-09-02 11:57:53 +02:00
Audrius Butkevicius
0ca39f6c65 Merge pull request #2220 from calmh/hashertweaks
Adjust defaults for number of hashers based on OS
2015-09-01 10:48:45 +01:00
Jakob Borg
02493251d5 Adjust defaults for number of hashers based on OS
https://forum.syncthing.net/t/syncthing-is-such-a-massive-resource-hog/5494/19?u=calmh
2015-09-01 10:30:35 +02:00
Jakob Borg
ff04648112 Remove leftovers from signing 2015-08-31 17:46:48 +02:00
Jakob Borg
55002d7adf Signing is done by stsigtool only 2015-08-30 20:50:07 +02:00
Jakob Borg
0664c6b5b0 Translation & docs update 2015-08-30 14:38:47 +02:00
Jakob Borg
a4ebac147b Harmonize rendering of identicons with other icons (fixes #2212) 2015-08-30 14:25:11 +02:00
AudriusButkevicius
cf802dc67e Hide .stigore (fixes #2114) 2015-08-30 12:59:01 +01:00
Jakob Borg
84365882de Fix tests 2015-08-28 09:01:21 +02:00
Jakob Borg
37fb6473b3 Woops, fix scanner test 2015-08-28 08:57:37 +02:00
Jakob Borg
ba676f2810 Dividing by zero is frowned upon 2015-08-27 21:41:39 +02:00
Jakob Borg
b3d7c622c3 Show folder scan progress in -verbose, hide local index updates 2015-08-27 21:37:41 +02:00
Jakob Borg
bc016e360e Refactor: ints used in arithmetic should be signed 2015-08-27 21:37:12 +02:00
Jakob Borg
594918dd3c Line height on headers to avoid cut off descenders 2015-08-27 21:28:07 +02:00
Jakob Borg
ec5feb41e8 Merge remote-tracking branch 'syncthing/pr/2200'
* syncthing/pr/2200:
  Add scan percentages (fixes #1030)
2015-08-27 21:25:45 +02:00
AudriusButkevicius
94c52e3a77 Add scan percentages (fixes #1030) 2015-08-27 19:20:43 +01:00
AudriusButkevicius
875de4f637 Use new address schema when creating default config 2015-08-27 19:18:45 +01:00
Audrius Butkevicius
72fa5a69f1 Merge pull request #2206 from calmh/logfile
Allow -logfile on all platforms (fixes #2004)
2015-08-27 18:23:03 +01:00
Jakob Borg
d63e54237b Allow -logfile on all platforms (fixes #2004) 2015-08-27 19:11:10 +02:00
Audrius Butkevicius
e256d93b43 Merge pull request #2205 from calmh/mclisten
Bind to IPv6 multicast group instead of ::
2015-08-27 17:50:47 +01:00
Audrius Butkevicius
4d12df5424 Merge pull request #2203 from calmh/discoport
Local discovery should use the same port on v4 as v6 (fixes #2201)
2015-08-27 17:45:13 +01:00
Jakob Borg
cae120fd4d Bind to IPv6 multicast group instead of ::
This makes it possible to run multiple instances on the same box, all
receiving local discovery packets. Tested on Mac, Windows, supposed to
work on at least Linux too. For Windows, there may be issues with XP and
earlier, but meh...
2015-08-27 17:51:15 +02:00
Jakob Borg
be332a6223 Local discovery should use the same port on v4 as v6 (fixes #2201) 2015-08-27 16:04:21 +02:00
Jakob Borg
bda0bb6f13 Merge pull request #2197 from kozec/instance-id
Feature request: startTime in system/status
2015-08-27 08:23:01 +02:00
AudriusButkevicius
68c5dcd83d Add CachedSize field 2015-08-26 22:33:03 +01:00
kozec
9bdcadf634 Added startTime into system/status REST call 2015-08-26 20:28:34 +02:00
Audrius Butkevicius
037be433f8 Merge pull request #2195 from calmh/noreload
Don't trust response header (fixes #2186)
2015-08-25 14:53:45 +01:00
Jakob Borg
2bed62dd9e Don't trust response header (fixes #2186)
Either Angular or the browser sometimes returns cached repsonse header,
causing a flap between requests that return the new version and requests
that return the old one. Here, instead, we trust the actual data
returned by the uncached /rest/system/version call.
2015-08-25 15:40:24 +02:00
Jakob Borg
a27bc4ebea stsigtool should use the built in key by default 2015-08-24 16:24:00 +02:00
Jakob Borg
d6e34761dc Fix events timeout errors
Resetting the timeout doesn't fully cut it, as it may timeout after we
got an event and be delivered later. This should fix it well enough for
the moment. https://github.com/golang/go/issues/11513
2015-08-24 09:38:39 +02:00
Audrius Butkevicius
98effcd8e3 Merge pull request #2188 from calmh/pausedevs
Pause and resume devices (ref #215)
2015-08-23 21:33:15 +01:00
Jakob Borg
baa87bc823 Command line switch -paused 2015-08-23 22:03:58 +02:00
Jakob Borg
944d9c84a0 Pause and resume devices (ref #215) 2015-08-23 22:00:21 +02:00
Jakob Borg
1e447741ee Update dependencies 2015-08-23 15:57:26 +02:00
Jakob Borg
4405ac7386 Report reason for no IPv6 multicast with STTRACE=discover 2015-08-23 15:50:57 +02:00
Audrius Butkevicius
e1190f0f0f Merge pull request #2184 from calmh/mc
Multicast double whammy!
2015-08-23 14:28:44 +01:00
Jakob Borg
a7f2416c0c IPv6 multicast on Windows (fixes #1817) 2015-08-23 15:14:26 +02:00
Jakob Borg
40d0100132 Change default IPv6 multicast address (fixes #2090) 2015-08-23 14:59:38 +02:00
Audrius Butkevicius
37f7c48cfc Merge pull request #2181 from calmh/refactor2
Small refactorings on relay
2015-08-23 13:02:06 +01:00
Jakob Borg
21adf752c8 Refactor: slightly simplify relay.Svc 2015-08-23 09:39:53 +02:00
Jakob Borg
aec143b882 Refactor: make IntermediateConnection more like Connection 2015-08-23 08:55:32 +02:00
Jakob Borg
f691040936 Refactor: s/Basic/Direct/ on connection type 2015-08-23 08:43:33 +02:00
Audrius Butkevicius
42acf0ed60 Try harder removing the temp file 2015-08-22 14:18:19 +01:00
Jakob Borg
ca4a3589e5 Increase event test timeout; the build server is slow, especially under -race 2015-08-21 13:26:16 +02:00
Jakob Borg
2eead17224 Actually map the key into Docker 2015-08-21 13:24:50 +02:00
Jakob Borg
626b26a227 Pass -sign parameter to build.go from Docker if key is present 2015-08-21 13:13:12 +02:00
Audrius Butkevicius
c46db0761e Merge pull request #2179 from calmh/signedrels
Use signed releases for automatic upgrade
2015-08-21 09:58:57 +01:00
Jakob Borg
cfed06697d Only accept correctly signed upgrades 2015-08-21 10:36:28 +02:00
Jakob Borg
a0d9183b14 Sign binaries when given "-sign keyfile" option 2015-08-21 09:33:46 +02:00
Jakob Borg
d3eb674b30 Add a signature package and stsigtool CLI utility 2015-08-21 09:31:17 +02:00
Jakob Borg
7fe1fdd8c7 Improve status reporter 2015-08-20 14:29:57 +02:00
Jakob Borg
37cbe68204 Fix broken connection close 2015-08-20 13:58:07 +02:00
Jakob Borg
f76a66fc55 Very basic status service 2015-08-20 12:59:44 +02:00
Jakob Borg
d7949aa58e I contribute stuff 2015-08-20 12:33:52 +02:00
Jakob Borg
f0c0c5483f Cleaner build 2015-08-20 12:33:11 +02:00
Jakob Borg
7d444021bb Update protocol dependency 2015-08-20 12:14:08 +02:00
Audrius Butkevicius
388a29bbe2 Merge pull request #17 from syncthing/fuzzing
Add some robustness for failure modes detected by go-fuzz
2015-08-20 09:47:47 +01:00
Jakob Borg
a03dd1bd41 Update test configs to v12 2015-08-20 09:38:47 +02:00
Jakob Borg
4b366f2857 This is now the v0.12 branch 2015-08-20 09:19:55 +02:00
Jakob Borg
c87faace6b Merge remote-tracking branch 'syncthing/pr/1995'
* syncthing/pr/1995:
  Add switch to disable relays
  Do not start relay service unless explicitly asked for, or global announcement server is running
  Add dynamic relay lookup (DDoS relays.syncthing.net!)
  Discovery clients now take an announcer, global discovery is delayed
  Expose connection type and relay status in the UI
  Add dependencies (fixes #1364)
  Check relays for available devices
  Add incoming connection relay service
  Add unsubscribe to config
  Connections have types
  Large refactoring/feature commit
2015-08-20 09:13:37 +02:00
Audrius Butkevicius
1e8b185377 Add switch to disable relays 2015-08-19 21:13:40 +01:00
Audrius Butkevicius
031804827f Do not start relay service unless explicitly asked for, or global announcement server is running 2015-08-19 21:13:10 +01:00
Audrius Butkevicius
6cccd9b6fc Add dynamic relay lookup (DDoS relays.syncthing.net!) 2015-08-19 21:12:34 +01:00
Audrius Butkevicius
687fbb0a7e Discovery clients now take an announcer, global discovery is delayed 2015-08-19 21:12:00 +01:00
Audrius Butkevicius
8f2db99c86 Expose connection type and relay status in the UI 2015-08-19 21:11:55 +01:00
Audrius Butkevicius
2c0f8dc546 Add dependencies (fixes #1364) 2015-08-19 21:06:46 +01:00
Audrius Butkevicius
a388fb0bb7 Check relays for available devices 2015-08-19 20:57:37 +01:00
Audrius Butkevicius
27465353c1 Add incoming connection relay service 2015-08-19 20:57:33 +01:00
Audrius Butkevicius
c2ccab4361 Add unsubscribe to config 2015-08-19 20:55:33 +01:00
Audrius Butkevicius
bb876eac82 Connections have types 2015-08-19 20:55:29 +01:00
Audrius Butkevicius
34c04babbe Large refactoring/feature commit
1. Change listen addresses to URIs
2. Break out connectionSvc to support listeners and dialers based on schema
3. Add relay announcement and lookups part of discovery service
2015-08-19 20:53:01 +01:00
Audrius Butkevicius
7c6a310179 Fix after package move 2015-08-19 20:49:34 +01:00
Jakob Borg
6a36ec63d7 Empty messages with the compression bit set should be accepted 2015-08-18 08:46:40 +02:00
Jakob Borg
9c8b907ff1 All slice types must have limits
The XDR unmarshaller allocates a []T when it sees a slice type and reads
the expected length, so we must always limit the length in order to
avoid allocating too much memory when encountering corruption.
2015-08-18 08:46:40 +02:00
Jakob Borg
f769df16e8 Reject unreasonably large messages
We allocate a []byte to read the message into, so if the header says the
messages is several gigabytes large we may run into trouble. In reality,
a message should never be that large so we impose a limit.
2015-08-18 08:46:40 +02:00
Jakob Borg
c6f5075721 Enable testing with go-fuzz 2015-08-18 08:46:40 +02:00
Jakob Borg
a693698279 Mend tests 2015-08-09 10:00:28 +02:00
Jakob Borg
4949e3ba41 Merge pull request #16 from syncthing/bufs
Use bytepool for response buffers
2015-08-02 08:08:10 +02:00
Audrius Butkevicius
ebcdea63c0 Use sync.Pool for response buffers 2015-08-01 12:21:49 +01:00
Audrius Butkevicius
eb29989dff Connection errors are debug errors 2015-07-23 20:53:16 +01:00
Audrius Butkevicius
78ef42daa1 Add ability to lookup relay status 2015-07-22 22:34:05 +01:00
Audrius Butkevicius
22e24fc387 Merge pull request #15 from syncthing/conflictwins
Base for better conflict resolution
2015-07-21 14:45:17 +01:00
Jakob Borg
516d88b072 Base for better conflict resolution 2015-07-21 15:40:02 +02:00
Audrius Butkevicius
77457e91e9 Merge pull request #1 from syncthing/review
Code review
2015-07-20 18:43:31 +01:00
Jakob Borg
049d92b525 Style and minor fixes, client package 2015-07-20 14:04:34 +02:00
Jakob Borg
f9bd59f031 Style and minor fixes, main package 2015-07-20 14:04:34 +02:00
Audrius Butkevicius
6d0d5bd566 Merge pull request #2 from syncthing/ratelimit
Implement global and per session rate limiting
2015-07-20 12:57:55 +01:00
Jakob Borg
35d20a19bc Implement global and per session rate limiting 2015-07-20 13:37:11 +02:00
Jakob Borg
dab1c4cfc9 Build script from discosrv 2015-07-20 12:11:06 +02:00
Audrius Butkevicius
f86946c6df Fix bugs 2015-07-17 22:04:02 +01:00
Audrius Butkevicius
e97f75cad5 Change receiver type, add GoStringer 2015-07-17 21:49:45 +01:00
Audrius Butkevicius
2505f82ce5 General cleanup 2015-07-17 20:17:49 +01:00
Audrius Butkevicius
7996ef0d45 Merge pull request #14 from syncthing/clusterconfigrace
Connection now needs explicit Start()
2015-07-10 08:31:57 +01:00
Jakob Borg
b05c1a5bb9 Connection now needs explicit Start() 2015-07-10 16:40:39 +10:00
Audrius Butkevicius
9dd6f848bd Name the folder in error messages 2015-07-09 22:38:21 +01:00
Audrius Butkevicius
e1959afb6b Change EOL 2015-06-28 21:18:38 +01:00
Audrius Butkevicius
c68c78d412 Do scheme validation in the client 2015-06-28 20:34:28 +01:00
Audrius Butkevicius
b72d31f87f Progress 2015-06-28 19:57:13 +01:00
Audrius Butkevicius
95e15c95f2 Merge pull request #13 from syncthing/timeout
Expose timeouts in protocol
2015-06-28 11:39:51 +01:00
Audrius Butkevicius
9f871a3726 Expose timeouts in protocol 2015-06-27 11:07:44 +01:00
Audrius Butkevicius
e19e2c123e Merge pull request #12 from syncthing/ccreq
Enforce ClusterConfiguration at start, then no ordering
2015-06-26 14:44:56 +01:00
Jakob Borg
cbe44e1fff Enforce ClusterConfiguration at start, then no ordering 2015-06-26 15:38:56 +02:00
Audrius Butkevicius
8e191c8e6b Add initial code 2015-06-24 15:02:23 +01:00
Audrius Butkevicius
19d742b9e4 Initial commit 2015-06-24 00:34:16 +01:00
Jakob Borg
e7db264803 Extract counter value from vector 2015-04-09 12:51:21 +02:00
Jakob Borg
3d8a71fdb2 Generate with updated XDR package 2015-04-08 14:46:08 +02:00
Jakob Borg
6277c0595c Merge pull request #3 from syncthing/changes
Changes for temp indexes
2015-03-26 22:31:45 +01:00
Audrius Butkevicius
aa9eda1979 Add IndexTemporary and RequestTemporary flags 2015-03-26 21:30:20 +00:00
Audrius Butkevicius
1d76efcbcd Remove duplication 2015-03-26 21:30:19 +00:00
Audrius Butkevicius
34c2c1ec16 Send and receive Request error codes 2015-03-26 21:30:19 +00:00
Audrius Butkevicius
bf7fea9a0a Rename error to code, update xdr path 2015-03-26 21:30:19 +00:00
Audrius Butkevicius
fdf15f3ca3 Flag checking is now responsibility of the model 2015-03-26 21:30:18 +00:00
Audrius Butkevicius
1cb5875b20 Expose flags, options in Index{,Update} 2015-03-26 21:30:18 +00:00
Audrius Butkevicius
1a59a5478f Expose hash, flags, options in Request 2015-03-26 21:30:17 +00:00
Audrius Butkevicius
5750443371 Merge pull request #10 from syncthing/vv
Implement version vectors
2015-03-26 13:25:04 +00:00
Jakob Borg
f9132cae85 Implement version vectors 2015-03-25 22:49:53 +01:00
Audrius Butkevicius
17149741a7 Merge pull request #9 from syncthing/flags
Add flags and options for future extensibility
2015-03-25 21:04:14 +00:00
Jakob Borg
d2ec40bb67 Add flags and options for future extensibility 2015-03-25 21:20:04 +01:00
Jakob Borg
1a4398cc55 Tests should actually pass 2015-03-11 21:11:43 +01:00
Audrius Butkevicius
529d91fb9d Merge pull request #7 from syncthing/compression
Add more fine grained compression control
2015-03-11 19:36:02 +00:00
Jakob Borg
108b4e2e10 Add more fine grained compression control 2015-03-11 19:09:58 +01:00
Jakob Borg
cd0cce4195 gofmt 2015-03-09 21:21:20 +01:00
Jakob Borg
9f1a72ec88 Merge pull request #6 from syncthing/limit
Remove 64 folder limit
2015-03-09 21:02:07 +01:00
Audrius Butkevicius
a10c621e33 Remove 64 folder limit 2015-03-08 16:55:01 +00:00
Jakob Borg
2e2d479103 Merge pull request #5 from syncthing/iota
We are not using iota
2015-02-15 09:40:19 +01:00
Audrius Butkevicius
35f0e355bf We are not using iota 2015-02-12 21:59:33 +00:00
Jakob Borg
4395711d26 Merge pull request #4 from syncthing/allflags
Add FlagsAll bit mask
2015-02-09 15:45:05 +01:00
Audrius Butkevicius
aba915037f Add FlagsAll bit mask 2015-02-01 16:33:52 +00:00
Audrius Butkevicius
442e93d3fc Merge pull request #2 from syncthing/integers
Integer type policy
2015-01-19 20:40:17 +00:00
Jakob Borg
3450b5f80c 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-18 02:13:25 +01:00
Jakob Borg
f76b5d8002 rm '.gitignore' 2015-01-13 13:47:16 +01:00
Audrius Butkevicius
7e54868206 Merge pull request #1 from syncthing/mit
Relicense as MIT
2015-01-13 12:38:13 +00:00
Jakob Borg
d84a8e6404 Relicense as MIT 2015-01-13 13:31:14 +01:00
Jakob Borg
4833b6085c The luhn package moved 2015-01-13 13:20:29 +01:00
Jakob Borg
2ceaca8828 Add documentation copied from Syncthing 2015-01-13 13:09:59 +01:00
Jakob Borg
d02158c0ef Also filter out some other obviously invalid filenames (ref #1243) 2015-01-13 12:28:35 +01:00
Jakob Borg
6213c4f2cd Remove nil filenames from database and indexes (fixes #1243) 2015-01-13 09:20:14 +01:00
Jakob Borg
cd34eea017 Reject Index and Request messages with unexpected flags 2015-01-11 13:29:01 +01:00
Jakob Borg
7a0a702ec0 Refactor readerLoop to switch on message type directly 2015-01-11 13:24:56 +01:00
Jakob Borg
d9ed8e125e 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
36708a5067 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
8c32955da1 Actually close connection based on unknown protocol version 2015-01-08 22:11:26 +01:00
Jakob Borg
c111ed4b20 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
a3ea9427d1 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
Audrius Butkevicius
09b534b8a3 Add job queue (fixes #629)
Request to terminate currently ongoing downloads and jump to the bumped file
incoming in 3, 2, 1.

Also, has a slightly strange effect where we pop a job off the queue, but
the copyChannel is still busy and blocks, though it gets moved to the
progress slice in the jobqueue, and looks like it's in progress which it isn't
as it's waiting to be picked up from the copyChan.

As a result, the progress emitter doesn't register on the task, and hence the file
doesn't have a progress bar, but cannot be replaced by a bump.

I guess I can fix progress bar issue by moving the progressEmiter.Register just
before passing the file to the copyChan, but then we are back to the initial
problem of a file with a progress bar, but no progress happening as it's stuck
 on write to copyChan

I checked if there is a way to check for channel writeability (before popping)
but got struck by lightning just for bringing the idea up in #go-nuts.

My ideal scenario would be to check if copyChan is writeable, pop job from the
queue and shove it down handleFile. This way jobs would stay in the queue while
they cannot be handled, meaning that the `Bump` could bring your file up higher.
2015-01-02 15:33:39 +01:00
Jakob Borg
190c61ba2f Use Go 1.4 'generate' to create XDR codec 2014-12-06 14:23:10 +01:00
Jakob Borg
dc71ec734d Dependency update, new golang.org/x package names 2014-11-30 00:17:00 +01:00
Audrius Butkevicius
3af96e50bd Use custom structure for /need calls (fixes #1001)
Also, remove trimming by number of blocks as this no longer affects the size
of the response.
2014-11-23 00:52:48 +00:00
Audrius Butkevicius
ddc56c8a0d Add symlink support at the protocol level 2014-11-20 16:32:00 +01:00
Audrius Butkevicius
e0da2764c9 Code smell 2014-11-20 16:32:00 +01:00
Jakob Borg
ad29093ac1 Use more inclusive copyright header 2014-11-17 12:54:42 +01:00
Jakob Borg
28610a9a42 Break out logger as a reusable component 2014-10-26 13:16:54 +01:00
Jakob Borg
65eb528e2d Update xdr; handle marshalling errors 2014-10-21 09:20:14 +02:00
Audrius Butkevicius
ec9d68960f Remove 64 device limit 2014-10-20 21:46:53 +01:00
Audrius Butkevicius
c618eba9a9 Implement BlockMap 2014-10-16 12:26:27 +02:00
Jakob Borg
1ef8378a30 FileInfoTruncated.String() for stindex' benefit 2014-10-16 09:26:24 +02:00
Jakob Borg
43289103cb Relicense to GPL 2014-10-01 07:53:59 +02:00
Audrius Butkevicius
1bc5632771 Run go fmt -w 2014-09-28 14:23:08 +01:00
Audrius Butkevicius
4b488a2d28 Rename Repository -> Folder, Node -> Device (fixes #739) 2014-09-28 14:23:07 +01:00
Jakob Borg
f28367bcfc Move top level packages to internal. 2014-09-27 09:42:10 +02:00
274 changed files with 14369 additions and 6686 deletions

8
.gitignore vendored
View File

@@ -1,4 +1,6 @@
./syncthing
syncthing
!gui/syncthing
!Godeps/_workspace/src/github.com/syncthing
syncthing.exe
*.tar.gz
*.zip
@@ -9,8 +11,6 @@ files/pidx
bin
perfstats*.csv
coverage.xml
!gui/scripts/syncthing
syncthing.md5
syncthing.exe.md5
syncthing.sig
RELEASE
deb

View File

@@ -1,6 +1,7 @@
# This is the official list of Syncthing authors for copyright purposes.
Aaron Bieber <qbit@deftly.net>
Adam Piggott <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
Alexander Graf <register-github@alex-graf.de>
Andrew Dunham <andrew@du.nham.ca>
Antony Male <antony.male@gmail.com>
@@ -35,6 +36,7 @@ Frank Isemann <frank@isemann.name>
Gilli Sigurdsson <gilli@vx.is>
Jacek Szafarkiewicz <szafar@linux.pl>
Jakob Borg <jakob@nym.se>
Jake Peterson <jake@acogdev.com>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jaroslav Malec <dzardacz@gmail.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
@@ -47,6 +49,8 @@ Lord Landon Agahnim <lordlandon@gmail.com>
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
Marc Pujol <kilburn@la3.org>
Marcin Dziadus <dziadus.marcin@gmail.com>
Mateusz Naściszewski <matin1111@wp.pl>
Matt Burke <mburke@amplify.com> <burkemw3@gmail.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com>
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
@@ -56,10 +60,13 @@ Phill Luby <phill.luby@newredo.com>
Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Sergey Mishin <ralder@yandex.ru>
Stefan Tatschner <stefan@sevenbyte.org>
Stefan Tatschner <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
Stefan Kuntz <stefan.github@gmail.com> <Stefan.github@gmail.com>
Tim Abell <tim@timwise.co.uk>
Tobias Nygren <tnn@nygren.pp.se>
Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org>
Tyler Brazier <tyler@tylerbrazier.com>
Veeti Paananen <veeti.paananen@rojekti.fi>
Vil Brekin <vilbrekin@gmail.com>
Yannic A. <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>

31
Godeps/Godeps.json generated
View File

@@ -1,29 +1,25 @@
{
"ImportPath": "github.com/syncthing/syncthing",
"GoVersion": "go1.5",
"GoVersion": "go1.5.1",
"Packages": [
"./cmd/..."
],
"Deps": [
{
"ImportPath": "github.com/bkaradzic/go-lz4",
"Rev": "d47913b1412890a261b9fefae99d72d2bf5aebd8"
"Rev": "74ddf82598bc4745b965729e9c6a463bedd33049"
},
{
"ImportPath": "github.com/calmh/du",
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24"
},
{
"ImportPath": "github.com/calmh/logger",
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
},
{
"ImportPath": "github.com/calmh/luhn",
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
"Rev": "47c0042d09a827b81ee62497f99e5e0c7f0bd31c"
},
{
"ImportPath": "github.com/golang/snappy",
@@ -37,17 +33,14 @@
"ImportPath": "github.com/kardianos/osext",
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5"
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "388a29bbe21d8772ee4c29f4520aa8040309607d"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "b743d92d3215f11c9b5ce8830fafe1f16786adf4"
"Rev": "1a9d62f03ea92815b46fcaab357cfd4df264b1a0"
},
{
"ImportPath": "github.com/thejerf/suture",
"Rev": "fc7aaeabdc43fe41c5328efa1479ffea0b820978"
"Comment": "v1.0.1",
"Rev": "99c1f2d613756768fc4299acd9dc621e11ed3fd7"
},
{
"ImportPath": "github.com/vitrun/qart/coding",
@@ -63,19 +56,23 @@
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "c16968172724c0b5e8bdc6ad33f5a79443a44cd7"
"Rev": "81bf7719a6b7ce9b665598222362b50122dfc13b"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "c16968172724c0b5e8bdc6ad33f5a79443a44cd7"
"Rev": "81bf7719a6b7ce9b665598222362b50122dfc13b"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "66f0418ca41253f8d1a024eb9754e9441a8e79b9"
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/net/ipv6",
"Rev": "66f0418ca41253f8d1a024eb9754e9441a8e79b9"
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/net/proxy",
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/text/transform",

View File

@@ -4,5 +4,6 @@ go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.4
- 1.5
- tip

View File

@@ -1,15 +0,0 @@
logger
======
A small wrapper around `log` to provide log levels.
Documentation
-------------
http://godoc.org/github.com/calmh/logger
License
-------
MIT

View File

@@ -1,187 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// Package logger implements a standardized logger with callback functionality
package logger
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"sync"
)
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelVerbose
LevelInfo
LevelOK
LevelWarn
LevelFatal
NumLevels
)
// A MessageHandler is called with the log level and message text.
type MessageHandler func(l LogLevel, msg string)
type Logger struct {
logger *log.Logger
handlers [NumLevels][]MessageHandler
mut sync.Mutex
}
// The default logger logs to standard output with a time prefix.
var DefaultLogger = New()
func New() *Logger {
if os.Getenv("LOGGER_DISCARD") != "" {
// Hack to completely disable logging, for example when running benchmarks.
return &Logger{
logger: log.New(ioutil.Discard, "", 0),
}
}
return &Logger{
logger: log.New(os.Stdout, "", log.Ltime),
}
}
// AddHandler registers a new MessageHandler to receive messages with the
// specified log level or above.
func (l *Logger) AddHandler(level LogLevel, h MessageHandler) {
l.mut.Lock()
defer l.mut.Unlock()
l.handlers[level] = append(l.handlers[level], h)
}
// See log.SetFlags
func (l *Logger) SetFlags(flag int) {
l.logger.SetFlags(flag)
}
// See log.SetPrefix
func (l *Logger) SetPrefix(prefix string) {
l.logger.SetPrefix(prefix)
}
func (l *Logger) callHandlers(level LogLevel, s string) {
for _, h := range l.handlers[level] {
h(level, strings.TrimSpace(s))
}
}
// Debugln logs a line with a DEBUG prefix.
func (l *Logger) Debugln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "DEBUG: "+s)
l.callHandlers(LevelDebug, s)
}
// Debugf logs a formatted line with a DEBUG prefix.
func (l *Logger) Debugf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "DEBUG: "+s)
l.callHandlers(LevelDebug, s)
}
// Infoln logs a line with a VERBOSE prefix.
func (l *Logger) Verboseln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "VERBOSE: "+s)
l.callHandlers(LevelVerbose, s)
}
// Infof logs a formatted line with a VERBOSE prefix.
func (l *Logger) Verbosef(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "VERBOSE: "+s)
l.callHandlers(LevelVerbose, s)
}
// Infoln logs a line with an INFO prefix.
func (l *Logger) Infoln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "INFO: "+s)
l.callHandlers(LevelInfo, s)
}
// Infof logs a formatted line with an INFO prefix.
func (l *Logger) Infof(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "INFO: "+s)
l.callHandlers(LevelInfo, s)
}
// Okln logs a line with an OK prefix.
func (l *Logger) Okln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "OK: "+s)
l.callHandlers(LevelOK, s)
}
// Okf logs a formatted line with an OK prefix.
func (l *Logger) Okf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "OK: "+s)
l.callHandlers(LevelOK, s)
}
// Warnln logs a formatted line with a WARNING prefix.
func (l *Logger) Warnln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "WARNING: "+s)
l.callHandlers(LevelWarn, s)
}
// Warnf logs a formatted line with a WARNING prefix.
func (l *Logger) Warnf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "WARNING: "+s)
l.callHandlers(LevelWarn, s)
}
// Fatalln logs a line with a FATAL prefix and exits the process with exit
// code 1.
func (l *Logger) Fatalln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}
// Fatalf logs a formatted line with a FATAL prefix and exits the process with
// exit code 1.
func (l *Logger) Fatalf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}

View File

@@ -1,58 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
package logger
import (
"strings"
"testing"
)
func TestAPI(t *testing.T) {
l := New()
l.SetFlags(0)
l.SetPrefix("testing")
debug := 0
l.AddHandler(LevelDebug, checkFunc(t, LevelDebug, "test 0", &debug))
info := 0
l.AddHandler(LevelInfo, checkFunc(t, LevelInfo, "test 1", &info))
warn := 0
l.AddHandler(LevelWarn, checkFunc(t, LevelWarn, "test 2", &warn))
ok := 0
l.AddHandler(LevelOK, checkFunc(t, LevelOK, "test 3", &ok))
l.Debugf("test %d", 0)
l.Debugln("test", 0)
l.Infof("test %d", 1)
l.Infoln("test", 1)
l.Warnf("test %d", 2)
l.Warnln("test", 2)
l.Okf("test %d", 3)
l.Okln("test", 3)
if debug != 2 {
t.Errorf("Debug handler called %d != 2 times", debug)
}
if info != 2 {
t.Errorf("Info handler called %d != 2 times", info)
}
if warn != 2 {
t.Errorf("Warn handler called %d != 2 times", warn)
}
if ok != 2 {
t.Errorf("Ok handler called %d != 2 times", ok)
}
}
func checkFunc(t *testing.T, expectl LogLevel, expectmsg string, counter *int) func(LogLevel, string) {
return func(l LogLevel, msg string) {
*counter++
if l != expectl {
t.Errorf("Incorrect message level %d != %d", l, expectl)
}
if !strings.HasSuffix(msg, expectmsg) {
t.Errorf("%q does not end with %q", msg, expectmsg)
}
}
}

View File

@@ -4,7 +4,7 @@ go:
install:
- export PATH=$PATH:$HOME/gopath/bin
- go get code.google.com/p/go.tools/cmd/cover
- go get golang.org/x/tools/cover
- go get github.com/mattn/goveralls
script:

View File

@@ -1,7 +1,7 @@
xdr
===
[![Build Status](https://img.shields.io/travis/calmh/xdr.svg?style=flat)](https://travis-ci.org/calmh/xdr)
[![Build Status](https://img.shields.io/circleci/project/calmh/xdr.svg?style=flat-square)](https://circleci.com/gh/calmh/xdr)
[![Coverage Status](https://img.shields.io/coveralls/calmh/xdr.svg?style=flat)](https://coveralls.io/r/calmh/xdr?branch=master)
[![API Documentation](http://img.shields.io/badge/api-Godoc-blue.svg?style=flat)](http://godoc.org/github.com/calmh/xdr)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT)

View File

@@ -28,6 +28,7 @@ type fieldInfo struct {
Encoder string // the encoder name, i.e. "Uint64" for Read/WriteUint64
Convert string // what to convert to when encoding, i.e. "uint64"
Max int // max size for slices and strings
Submax int // max size for strings inside slices
}
type structInfo struct {
@@ -156,7 +157,11 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
{{if ne $fieldInfo.Convert ""}}
o.{{$fieldInfo.Name}}[i] = {{$fieldInfo.FieldType}}(xr.Read{{$fieldInfo.Encoder}}())
{{else if $fieldInfo.IsBasic}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
{{if ge $fieldInfo.Submax 1}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}Max({{$fieldInfo.Submax}})
{{else}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
{{end}}
{{else}}
(&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr)
{{end}}
@@ -166,7 +171,7 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
return xr.Error()
}`))
var maxRe = regexp.MustCompile(`\Wmax:(\d+)`)
var maxRe = regexp.MustCompile(`(?:\Wmax:)(\d+)(?:\s*,\s*(\d+))?`)
type typeSet struct {
Type string
@@ -198,11 +203,15 @@ func handleStruct(t *ast.StructType) []fieldInfo {
}
fn := sf.Names[0].Name
var max = 0
var max1, max2 int
if sf.Comment != nil {
c := sf.Comment.List[0].Text
if m := maxRe.FindStringSubmatch(c); m != nil {
max, _ = strconv.Atoi(m[1])
m := maxRe.FindStringSubmatch(c)
if len(m) >= 2 {
max1, _ = strconv.Atoi(m[1])
}
if len(m) >= 3 {
max2, _ = strconv.Atoi(m[2])
}
if strings.Contains(c, "noencode") {
continue
@@ -220,14 +229,16 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn,
Encoder: enc.Encoder,
Convert: enc.Type,
Max: max,
Max: max1,
Submax: max2,
}
} else {
f = fieldInfo{
Name: fn,
IsBasic: false,
FieldType: tn,
Max: max,
Max: max1,
Submax: max2,
}
}
@@ -245,7 +256,8 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn,
Encoder: enc.Encoder,
Convert: enc.Type,
Max: max,
Max: max1,
Submax: max2,
}
} else if enc, ok := xdrEncoders[tn]; ok {
f = fieldInfo{
@@ -255,14 +267,16 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn,
Encoder: enc.Encoder,
Convert: enc.Type,
Max: max,
Max: max1,
Submax: max2,
}
} else {
f = fieldInfo{
Name: fn,
IsSlice: true,
FieldType: tn,
Max: max,
Max: max1,
Submax: max2,
}
}
@@ -270,7 +284,8 @@ func handleStruct(t *ast.StructType) []fieldInfo {
f = fieldInfo{
Name: fn,
FieldType: ft.Sel.Name,
Max: max,
Max: max1,
Submax: max2,
}
}

View File

@@ -1,15 +0,0 @@
// 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

@@ -201,6 +201,7 @@ func (p *BufferPool) String() string {
func (p *BufferPool) drain() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:

View File

@@ -6,10 +6,8 @@ Suture
Suture provides Erlang-ish supervisor trees for Go. "Supervisor trees" ->
"sutree" -> "suture" -> holds your code together when it's trying to die.
This is intended to be a production-quality library going into code that I
will be very early on the phone tree to support when it goes down. However,
it has not been deployed into something quite that serious yet. (I will
update this statement when that changes.)
This library has hit maturity, and isn't expected to be changed
radically. This can also be imported via gopkg.in/thejerf/suture.v1 .
It is intended to deal gracefully with the real failure cases that can
occur with supervision trees (such as burning all your CPU time endlessly
@@ -24,10 +22,6 @@ This module is fully covered with [godoc](http://godoc.org/github.com/thejerf/su
including an example, usage, and everything else you might expect from a
README.md on GitHub. (DRY.)
This is not currently tagged with particular git tags for Go as this is
currently considered to be alpha code. As I move this into production and
feel more confident about it, I'll give it relevant tags.
Code Signing
------------
@@ -43,3 +37,14 @@ easily fit into the OTP paradigm. It ought to someday be considered a good
idea to distribute libraries that provide some sort of supervisor tree
functionality out of the box. It is possible to provide this functionality
without explicitly depending on the Suture library.
Changelog
---------
suture uses semantic versioning.
1. 1.0.0
* Initial release.
2. 1.0.1
* Fixed data race on the .state variable.

View File

@@ -59,6 +59,7 @@ import (
"log"
"math"
"runtime"
"sync"
"sync/atomic"
"time"
)
@@ -123,7 +124,6 @@ type Supervisor struct {
lastFail time.Time
failures float64
restartQueue []serviceID
state uint8
serviceCounter serviceID
control chan supervisorMessage
resumeTimer <-chan time.Time
@@ -143,6 +143,9 @@ type Supervisor struct {
// a minimal chunk.
getNow func() time.Time
getResume func(time.Duration) <-chan time.Time
sync.Mutex
state uint8
}
// Spec is used to pass arguments to the New function to create a
@@ -373,6 +376,7 @@ func (s *Supervisor) Add(service Service) ServiceToken {
supervisor.logBackoff = s.logBackoff
}
s.Lock()
if s.state == notRunning {
id := s.serviceCounter
s.serviceCounter++
@@ -380,8 +384,10 @@ func (s *Supervisor) Add(service Service) ServiceToken {
s.services[id] = service
s.restartQueue = append(s.restartQueue, id)
s.Unlock()
return ServiceToken{uint64(s.id)<<32 | uint64(id)}
}
s.Unlock()
response := make(chan serviceID)
s.control <- addService{service, response}
@@ -408,16 +414,19 @@ func (s *Supervisor) Serve() {
}
defer func() {
s.Lock()
s.state = notRunning
s.Unlock()
}()
s.Lock()
if s.state != notRunning {
// FIXME: Don't explain why I don't need a semaphore, just use one
// This doesn't use a semaphore because it's just a sanity check.
s.Unlock()
panic("Running a supervisor while it is already running?")
}
s.state = normal
s.Unlock()
// for all the services I currently know about, start them
for _, id := range s.restartQueue {
@@ -472,7 +481,9 @@ func (s *Supervisor) Serve() {
// excessive thrashing
// FIXME: Ought to permit some spacing of these functions, rather
// than simply hammering through them
s.Lock()
s.state = normal
s.Unlock()
s.failures = 0
s.logBackoff(s, false)
for _, id := range s.restartQueue {
@@ -499,7 +510,9 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
}
if s.failures > s.failureThreshold {
s.Lock()
s.state = paused
s.Unlock()
s.logBackoff(s, true)
s.resumeTimer = s.getResume(s.failureBackoff)
}
@@ -511,7 +524,13 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
// It is possible for a service to be no longer monitored
// by the time we get here. In that case, just ignore it.
if monitored {
if s.state == normal {
// this may look dangerous because the state could change, but this
// code is only ever run in the one goroutine that is permitted to
// change the state, so nothing else will.
s.Lock()
curState := s.state
s.Unlock()
if curState == normal {
s.runService(failedService, id)
s.logFailure(s, failedService, s.failures, s.failureThreshold, true, err, stacktrace)
} else {

View File

@@ -17,7 +17,7 @@ func (i *Incrementor) Serve() {
for {
select {
case i.next <- i.current:
i.current += 1
i.current++
case <-i.stop:
// We sync here just to guarantee the output of "Stopping the service",
// so this passes the test reliably.

View File

@@ -478,6 +478,22 @@ func TestNilSupervisorAdd(t *testing.T) {
s.Add(s)
}
// https://github.com/thejerf/suture/issues/11
//
// The purpose of this test is to verify that it does not cause data races,
// so there are no obvious assertions.
func TestIssue11(t *testing.T) {
t.Parallel()
s := NewSimple("main")
s.ServeBackground()
subsuper := NewSimple("sub")
s.Add(subsuper)
subsuper.Add(NewService("may cause data race"))
}
// http://golangtutorials.blogspot.com/2011/10/gotest-unit-testing-and-benchmarking-go.html
// claims test function are run in the same order as the source file...
// I'm not sure if this is part of the contract, though. Especially in the
@@ -509,7 +525,7 @@ func (s *FailableService) Serve() {
everMultistarted = true
panic("Multi-started the same service! " + s.name)
}
s.existing += 1
s.existing++
s.started <- true
@@ -522,13 +538,13 @@ func (s *FailableService) Serve() {
case Happy:
// Do nothing on purpose. Life is good!
case Fail:
s.existing -= 1
s.existing--
if useStopChan {
s.stop <- true
}
return
case Panic:
s.existing -= 1
s.existing--
panic("Panic!")
case Hang:
// or more specifically, "hang until I release you"
@@ -537,7 +553,7 @@ func (s *FailableService) Serve() {
useStopChan = true
}
case <-s.shutdown:
s.existing -= 1
s.existing--
if useStopChan {
s.stop <- true
}

View File

@@ -2,7 +2,7 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).
package iana
package iana // import "golang.org/x/net/internal/iana"
// Differentiated Services Field Codepoints (DSCP), Updated: 2013-06-25
const (

View File

@@ -42,7 +42,7 @@
// The outgoing packets will be labeled DiffServ assured forwarding
// class 1 low drop precedence, known as AF11 packets.
//
// if err := ipv6.NewConn(c).SetTrafficClass(DiffServAF11); err != nil {
// if err := ipv6.NewConn(c).SetTrafficClass(0x28); err != nil {
// // error handling
// }
// if _, err := c.Write(data); err != nil {
@@ -124,7 +124,7 @@
//
// The application can also send both unicast and multicast packets.
//
// p.SetTrafficClass(DiffServCS0)
// p.SetTrafficClass(0x0)
// p.SetHopLimit(16)
// if _, err := p.WriteTo(data[:n], nil, src); err != nil {
// // error handling
@@ -237,4 +237,4 @@
// MLDv1 and starts to listen to multicast traffic.
// In the fallback case, ExcludeSourceSpecificGroup and
// IncludeSourceSpecificGroup may return an error.
package ipv6
package ipv6 // import "golang.org/x/net/ipv6"

18
Godeps/_workspace/src/golang.org/x/net/proxy/direct.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"net"
)
type direct struct{}
// Direct is a direct proxy: one that makes network connections directly.
var Direct = direct{}
func (direct) Dial(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}

View File

@@ -0,0 +1,140 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"net"
"strings"
)
// A PerHost directs connections to a default Dialer unless the hostname
// requested matches one of a number of exceptions.
type PerHost struct {
def, bypass Dialer
bypassNetworks []*net.IPNet
bypassIPs []net.IP
bypassZones []string
bypassHosts []string
}
// NewPerHost returns a PerHost Dialer that directs connections to either
// defaultDialer or bypass, depending on whether the connection matches one of
// the configured rules.
func NewPerHost(defaultDialer, bypass Dialer) *PerHost {
return &PerHost{
def: defaultDialer,
bypass: bypass,
}
}
// Dial connects to the address addr on the given network through either
// defaultDialer or bypass.
func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return p.dialerForRequest(host).Dial(network, addr)
}
func (p *PerHost) dialerForRequest(host string) Dialer {
if ip := net.ParseIP(host); ip != nil {
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
}
}
for _, bypassIP := range p.bypassIPs {
if bypassIP.Equal(ip) {
return p.bypass
}
}
return p.def
}
for _, zone := range p.bypassZones {
if strings.HasSuffix(host, zone) {
return p.bypass
}
if host == zone[1:] {
// For a zone "example.com", we match "example.com"
// too.
return p.bypass
}
}
for _, bypassHost := range p.bypassHosts {
if bypassHost == host {
return p.bypass
}
}
return p.def
}
// AddFromString parses a string that contains comma-separated values
// specifying hosts that should use the bypass proxy. Each value is either an
// IP address, a CIDR range, a zone (*.example.com) or a hostname
// (localhost). A best effort is made to parse the string and errors are
// ignored.
func (p *PerHost) AddFromString(s string) {
hosts := strings.Split(s, ",")
for _, host := range hosts {
host = strings.TrimSpace(host)
if len(host) == 0 {
continue
}
if strings.Contains(host, "/") {
// We assume that it's a CIDR address like 127.0.0.0/8
if _, net, err := net.ParseCIDR(host); err == nil {
p.AddNetwork(net)
}
continue
}
if ip := net.ParseIP(host); ip != nil {
p.AddIP(ip)
continue
}
if strings.HasPrefix(host, "*.") {
p.AddZone(host[1:])
continue
}
p.AddHost(host)
}
}
// AddIP specifies an IP address that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match an IP.
func (p *PerHost) AddIP(ip net.IP) {
p.bypassIPs = append(p.bypassIPs, ip)
}
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match.
func (p *PerHost) AddNetwork(net *net.IPNet) {
p.bypassNetworks = append(p.bypassNetworks, net)
}
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
// "example.com" matches "example.com" and all of its subdomains.
func (p *PerHost) AddZone(zone string) {
if strings.HasSuffix(zone, ".") {
zone = zone[:len(zone)-1]
}
if !strings.HasPrefix(zone, ".") {
zone = "." + zone
}
p.bypassZones = append(p.bypassZones, zone)
}
// AddHost specifies a hostname that will use the bypass proxy.
func (p *PerHost) AddHost(host string) {
if strings.HasSuffix(host, ".") {
host = host[:len(host)-1]
}
p.bypassHosts = append(p.bypassHosts, host)
}

View File

@@ -0,0 +1,55 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"errors"
"net"
"reflect"
"testing"
)
type recordingProxy struct {
addrs []string
}
func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) {
r.addrs = append(r.addrs, addr)
return nil, errors.New("recordingProxy")
}
func TestPerHost(t *testing.T) {
var def, bypass recordingProxy
perHost := NewPerHost(&def, &bypass)
perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16")
expectedDef := []string{
"example.com:123",
"1.2.3.4:123",
"[1001::]:123",
}
expectedBypass := []string{
"localhost:123",
"zone:123",
"foo.zone:123",
"127.0.0.1:123",
"10.1.2.3:123",
"[1000::]:123",
}
for _, addr := range expectedDef {
perHost.Dial("tcp", addr)
}
for _, addr := range expectedBypass {
perHost.Dial("tcp", addr)
}
if !reflect.DeepEqual(expectedDef, def.addrs) {
t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef)
}
if !reflect.DeepEqual(expectedBypass, bypass.addrs) {
t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass)
}
}

94
Godeps/_workspace/src/golang.org/x/net/proxy/proxy.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package proxy provides support for a variety of protocols to proxy network
// data.
package proxy // import "golang.org/x/net/proxy"
import (
"errors"
"net"
"net/url"
"os"
)
// A Dialer is a means to establish a connection.
type Dialer interface {
// Dial connects to the given address via the proxy.
Dial(network, addr string) (c net.Conn, err error)
}
// Auth contains authentication parameters that specific Dialers may require.
type Auth struct {
User, Password string
}
// FromEnvironment returns the dialer specified by the proxy related variables in
// the environment.
func FromEnvironment() Dialer {
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
return Direct
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return Direct
}
proxy, err := FromURL(proxyURL, Direct)
if err != nil {
return Direct
}
noProxy := os.Getenv("no_proxy")
if len(noProxy) == 0 {
return proxy
}
perHost := NewPerHost(proxy, Direct)
perHost.AddFromString(noProxy)
return perHost
}
// proxySchemes is a map from URL schemes to a function that creates a Dialer
// from a URL with such a scheme.
var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error)
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) {
if proxySchemes == nil {
proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error))
}
proxySchemes[scheme] = f
}
// FromURL returns a Dialer given a URL specification and an underlying
// Dialer for it to make network requests.
func FromURL(u *url.URL, forward Dialer) (Dialer, error) {
var auth *Auth
if u.User != nil {
auth = new(Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
switch u.Scheme {
case "socks5":
return SOCKS5("tcp", u.Host, auth, forward)
}
// If the scheme doesn't match any of the built-in schemes, see if it
// was registered by another package.
if proxySchemes != nil {
if f, ok := proxySchemes[u.Scheme]; ok {
return f(u, forward)
}
}
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
}

View File

@@ -0,0 +1,142 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"io"
"net"
"net/url"
"strconv"
"sync"
"testing"
)
func TestFromURL(t *testing.T) {
endSystem, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer endSystem.Close()
gateway, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer gateway.Close()
var wg sync.WaitGroup
wg.Add(1)
go socks5Gateway(t, gateway, endSystem, socks5Domain, &wg)
url, err := url.Parse("socks5://user:password@" + gateway.Addr().String())
if err != nil {
t.Fatalf("url.Parse failed: %v", err)
}
proxy, err := FromURL(url, Direct)
if err != nil {
t.Fatalf("FromURL failed: %v", err)
}
_, port, err := net.SplitHostPort(endSystem.Addr().String())
if err != nil {
t.Fatalf("net.SplitHostPort failed: %v", err)
}
if c, err := proxy.Dial("tcp", "localhost:"+port); err != nil {
t.Fatalf("FromURL.Dial failed: %v", err)
} else {
c.Close()
}
wg.Wait()
}
func TestSOCKS5(t *testing.T) {
endSystem, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer endSystem.Close()
gateway, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer gateway.Close()
var wg sync.WaitGroup
wg.Add(1)
go socks5Gateway(t, gateway, endSystem, socks5IP4, &wg)
proxy, err := SOCKS5("tcp", gateway.Addr().String(), nil, Direct)
if err != nil {
t.Fatalf("SOCKS5 failed: %v", err)
}
if c, err := proxy.Dial("tcp", endSystem.Addr().String()); err != nil {
t.Fatalf("SOCKS5.Dial failed: %v", err)
} else {
c.Close()
}
wg.Wait()
}
func socks5Gateway(t *testing.T, gateway, endSystem net.Listener, typ byte, wg *sync.WaitGroup) {
defer wg.Done()
c, err := gateway.Accept()
if err != nil {
t.Errorf("net.Listener.Accept failed: %v", err)
return
}
defer c.Close()
b := make([]byte, 32)
var n int
if typ == socks5Domain {
n = 4
} else {
n = 3
}
if _, err := io.ReadFull(c, b[:n]); err != nil {
t.Errorf("io.ReadFull failed: %v", err)
return
}
if _, err := c.Write([]byte{socks5Version, socks5AuthNone}); err != nil {
t.Errorf("net.Conn.Write failed: %v", err)
return
}
if typ == socks5Domain {
n = 16
} else {
n = 10
}
if _, err := io.ReadFull(c, b[:n]); err != nil {
t.Errorf("io.ReadFull failed: %v", err)
return
}
if b[0] != socks5Version || b[1] != socks5Connect || b[2] != 0x00 || b[3] != typ {
t.Errorf("got an unexpected packet: %#02x %#02x %#02x %#02x", b[0], b[1], b[2], b[3])
return
}
if typ == socks5Domain {
copy(b[:5], []byte{socks5Version, 0x00, 0x00, socks5Domain, 9})
b = append(b, []byte("localhost")...)
} else {
copy(b[:4], []byte{socks5Version, 0x00, 0x00, socks5IP4})
}
host, port, err := net.SplitHostPort(endSystem.Addr().String())
if err != nil {
t.Errorf("net.SplitHostPort failed: %v", err)
return
}
b = append(b, []byte(net.ParseIP(host).To4())...)
p, err := strconv.Atoi(port)
if err != nil {
t.Errorf("strconv.Atoi failed: %v", err)
return
}
b = append(b, []byte{byte(p >> 8), byte(p)}...)
if _, err := c.Write(b); err != nil {
t.Errorf("net.Conn.Write failed: %v", err)
return
}
}

210
Godeps/_workspace/src/golang.org/x/net/proxy/socks5.go generated vendored Normal file
View File

@@ -0,0 +1,210 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"errors"
"io"
"net"
"strconv"
)
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928.
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
s := &socks5{
network: network,
addr: addr,
forward: forward,
}
if auth != nil {
s.user = auth.User
s.password = auth.Password
}
return s, nil
}
type socks5 struct {
user, password string
network, addr string
forward Dialer
}
const socks5Version = 5
const (
socks5AuthNone = 0
socks5AuthPassword = 2
)
const socks5Connect = 1
const (
socks5IP4 = 1
socks5Domain = 3
socks5IP6 = 4
)
var socks5Errors = []string{
"",
"general failure",
"connection forbidden",
"network unreachable",
"host unreachable",
"connection refused",
"TTL expired",
"command not supported",
"address type not supported",
}
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
}
conn, err := s.forward.Dial(s.network, s.addr)
if err != nil {
return nil, err
}
closeConn := &conn
defer func() {
if closeConn != nil {
(*closeConn).Close()
}
}()
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return nil, errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, socks5Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
} else {
buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
}
if _, err := conn.Write(buf); err != nil {
return nil, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return nil, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[0] != 5 {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
}
if buf[1] == 0xff {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
if buf[1] == socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)
buf = append(buf, uint8(len(s.user)))
buf = append(buf, s.user...)
buf = append(buf, uint8(len(s.password)))
buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil {
return nil, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return nil, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[1] != 0 {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
}
}
buf = buf[:0]
buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, socks5IP4)
ip = ip4
} else {
buf = append(buf, socks5IP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return nil, errors.New("proxy: destination hostname too long: " + host)
}
buf = append(buf, socks5Domain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil {
return nil, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return nil, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
failure := "unknown error"
if int(buf[1]) < len(socks5Errors) {
failure = socks5Errors[buf[1]]
}
if len(failure) > 0 {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}
bytesToDiscard := 0
switch buf[3] {
case socks5IP4:
bytesToDiscard = net.IPv4len
case socks5IP6:
bytesToDiscard = net.IPv6len
case socks5Domain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return nil, errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return nil, errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return nil, errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return nil, errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
closeConn = nil
return conn, nil
}

9
NICKS
View File

@@ -7,8 +7,10 @@ LordLandon <lordlandon@gmail.com>
Moter8 <moter8@gmail.com>
Nutomic <me@nutomic.com>
Rewt0r <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Stefan-Code <stefan.github@gmail.com> <Stefan.github@gmail.com>
Vilbrekin <vilbrekin@gmail.com>
Zillode <zillode@zillode.be>
acogdev <jake@acogdev.com>
alex2108 <register-github@alex-graf.de>
andrew-d <andrew@du.nham.ca>
asdil12 <dominik@heidler.eu>
@@ -18,6 +20,7 @@ brbecker <brbecker@gmail.com>
brendanlong <self@brendanlong.com>
brgmnn <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
bsidhom <bsidhom@gmail.com>
burkemw3 <mburke@amplify.com> <burkemw3@gmail.com>
calmh <jakob@nym.se>
canton7 <antony.male@gmail.com>
cdata <chris@scriptolo.gy>
@@ -26,6 +29,7 @@ ceh <emil@hessman.se>
cqcallaw <enlightened.despot@gmail.com>
dva <denisva@gmail.com>
dzarda <dzardacz@gmail.com>
eipiminus1 <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
facastagnini <federico.castagnini@gmail.com>
filoozoom <philippe@schommers.be>
frioux <frew@afoolishmanifesto.com> <frioux@gmail.com>
@@ -41,6 +45,7 @@ kozec <kozec@kozec.com>
krozycki <rozycki.karol@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
marclaporte <marc@marclaporte.com>
mateon1 <matin1111@wp.pl>
mogwa1 <devriesb@gmail.com>
moshen <moshen.colin@gmail.com>
mvdan <mvdan@mvdan.cc>
@@ -52,13 +57,15 @@ pluby <phill.luby@newredo.com>
pyfisch <pyfisch@gmail.com>
qbit <qbit@deftly.net>
ralder <ralder@yandex.ru>
rumpelsepp <stefan@sevenbyte.org>
rumpelsepp <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
sciurius <jvromans@squirrel.nl>
seehuhn <voss@seehuhn.de>
snnd <dw@risu.io>
simplypeachy <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
timabell <tim@timwise.co.uk>
tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
tylerbrazier <tyler@tylerbrazier.com>
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>
wsgcsysadmin <e.meitner@willystreet.coo>

View File

@@ -1,17 +1,16 @@
Syncthing
=========
# Syncthing
[![Latest Build](http://img.shields.io/jenkins/s/http/build.syncthing.net/syncthing.svg?style=flat-square)](http://build.syncthing.net/job/syncthing/lastBuild/)
[![API Documentation](http://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](http://godoc.org/github.com/syncthing/syncthing)
[![MPLv2 License](http://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/)
[![Latest Build (Official)](https://img.shields.io/jenkins/s/http/build.syncthing.net/syncthing.svg?style=flat-square)](http://build.syncthing.net/job/syncthing/lastBuild/)
[![AppVeyor Build](https://img.shields.io/appveyor/ci/calmh/syncthing/master.svg?style=flat-square)](https://ci.appveyor.com/project/calmh/syncthing)
[![API Documentation](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](http://godoc.org/github.com/syncthing/syncthing)
[![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/)
This is the Syncthing project which pursues the following goals:
1. Define a protocol for synchronization of a folder between a number of
collaborating devices. This protocol should be well defined, unambiguous,
easily understood, free to use, efficient, secure and language neutral.
This is called the [Block Exchange
Protocol](https://github.com/syncthing/specs/blob/master/BEPv1.md).
This is called the [Block Exchange Protocol][1].
2. Provide the reference implementation to demonstrate the usability of
said protocol. This is the `syncthing` utility. We hope that
@@ -21,38 +20,38 @@ The two are evolving together; the protocol is not to be considered
stable until Syncthing 1.0 is released, at which point it is locked down
for incompatible changes.
Getting Started
---------------
## Getting Started
Take a look at the [getting started
guide](http://docs.syncthing.net/intro/getting-started.html).
Take a look at the [getting started guide][2].
There are a few examples for keeping Syncthing running in the background
on your system in [the etc directory](https://github.com/syncthing/syncthing/blob/master/etc).
on your system in [the etc directory][3].
There is an IRC channel, `#syncthing` on Freenode, for talking directly
There is an IRC channel, `#syncthing` on [Freenode][4], for talking directly
to developers and users.
Building
--------
## Building
Building Syncthing from source is easy, and there's a
[guide](http://docs.syncthing.net/dev/building.html).
Building Syncthing from source is easy, and there's a [guide][5].
that describes it for both Unix and Windows systems.
Signed Releases
---------------
## Signed Releases
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
with the key D26E6ED000654A3E (see https://syncthing.net/security.html).
For release binaries, MD5 and SHA1 checksums are calculated and signed,
available in the md5sum.txt.asc and sha1sum.txt.asc files.
Documentation
=============
## Documentation
Please see the [Syncthing
documentation site](http://docs.syncthing.net/).
Please see the [Syncthing documentation site][6].
All code is licensed under the
[MPLv2 License](https://github.com/syncthing/syncthing/blob/master/LICENSE).
All code is licensed under the [MPLv2 License][7].
[1]: http://docs.syncthing.net/specs/bep-v1.html
[2]: http://docs.syncthing.net/intro/getting-started.html
[3]: https://github.com/syncthing/syncthing/blob/master/etc
[4]: https://webchat.freenode.net/
[5]: http://docs.syncthing.net/dev/building.html
[6]: http://docs.syncthing.net/
[7]: https://github.com/syncthing/syncthing/blob/master/LICENSE

12
appveyor.yaml Normal file
View File

@@ -0,0 +1,12 @@
version: '{branch}-{build}'
clone_folder: C:\src\github.com\syncthing\syncthing
init:
- go version
environment:
GOPATH: C:\
build_script:
- go run build.go zip
test_script:
- go run build.go test
artifacts:
- path: '*.zip'

View File

@@ -13,7 +13,6 @@ import (
"archive/zip"
"bytes"
"compress/gzip"
"crypto/md5"
"flag"
"fmt"
"io"
@@ -187,7 +186,7 @@ func setup() {
func test(pkg string) {
setBuildEnv()
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
runPrint("go", "test", "-short", "-race", "-timeout", "60s", pkg)
}
func bench(pkg string) {
@@ -196,7 +195,11 @@ func bench(pkg string) {
}
func install(pkg string, tags []string) {
os.Setenv("GOBIN", "./bin")
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
args := []string{"install", "-v", "-ldflags", ldflags()}
if len(tags) > 0 {
args = append(args, "-tags", strings.Join(tags, ","))
@@ -215,7 +218,7 @@ func build(pkg string, tags []string) {
binary += ".exe"
}
rmr(binary, binary+".md5")
rmr(binary)
args := []string{"build", "-ldflags", ldflags()}
if len(tags) > 0 {
args = append(args, "-tags", strings.Join(tags, ","))
@@ -226,13 +229,6 @@ func build(pkg string, tags []string) {
args = append(args, pkg)
setBuildEnv()
runPrint("go", args...)
// Create an md5 checksum of the binary, to be included in the archive for
// automatic upgrades.
err := md5File(binary)
if err != nil {
log.Fatal(err)
}
}
func buildTar() {
@@ -249,7 +245,6 @@ func buildTar() {
{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") {
@@ -277,7 +272,6 @@ func buildZip() {
{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") {
@@ -411,7 +405,7 @@ func assets() {
}
func xdr() {
runPrint("go", "generate", "./lib/discover", "./lib/db")
runPrint("go", "generate", "./lib/discover", "./lib/db", "./lib/protocol")
}
func translate() {
@@ -712,32 +706,6 @@ func zipFile(out string, files []archiveFile) {
}
}
func md5File(file string) error {
fd, err := os.Open(file)
if err != nil {
return err
}
defer fd.Close()
h := md5.New()
_, err = io.Copy(h, fd)
if err != nil {
return err
}
out, err := os.Create(file + ".md5")
if err != nil {
return err
}
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
if err != nil {
return err
}
return out.Close()
}
func vet(pkg string) {
bs, err := runError("go", "vet", pkg)
if err != nil && err.Error() == "exit status 3" || bytes.Contains(bs, []byte("no such tool \"vet\"")) {

View File

@@ -108,10 +108,10 @@ case "${1:-default}" in
# For every package in the repo
for dir in $(go list ./...) ; do
# run the tests
godep go test -coverprofile=profile.out $dir
GOPATH="$(pwd)/Godeps/_workspace:$GOPATH" go test -race -coverprofile=profile.out $dir
if [ -f profile.out ] ; then
# and if there was test output, append it to coverage.out
grep -v "mode: set" profile.out >> coverage.out
grep -v "mode: " profile.out >> coverage.out
rm profile.out
fi
done

View File

@@ -12,7 +12,7 @@ import (
"os"
"path/filepath"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
)
@@ -68,7 +68,7 @@ func main() {
if *standardBlocks || blockSize < protocol.BlockSize {
blockSize = protocol.BlockSize
}
bs, err := scanner.Blocks(fd, blockSize, fi.Size())
bs, err := scanner.Blocks(fd, blockSize, fi.Size(), nil)
if err != nil {
log.Fatal(err)
}

View File

@@ -7,37 +7,105 @@
package main
import (
"crypto/tls"
"errors"
"flag"
"log"
"fmt"
"net/url"
"os"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/protocol"
)
func main() {
log.SetFlags(0)
log.SetOutput(os.Stdout)
var timeout = 5 * time.Second
func main() {
var server string
flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22026", "Announce server")
flag.StringVar(&server, "server", "", "Announce server (blank for default set)")
flag.DurationVar(&timeout, "timeout", timeout, "Query timeout")
flag.Usage = usage
flag.Parse()
if len(flag.Args()) != 1 || server == "" {
log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22026\"] <device>", os.Args[0])
if flag.NArg() != 1 {
flag.Usage()
os.Exit(64)
}
id, err := protocol.DeviceIDFromString(flag.Args()[0])
if err != nil {
log.Println(err)
fmt.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)
if server != "" {
checkServers(id, server)
} else {
checkServers(id, config.DefaultDiscoveryServers...)
}
}
type checkResult struct {
server string
direct []string
relays []discover.Relay
error
}
func checkServers(deviceID protocol.DeviceID, servers ...string) {
t0 := time.Now()
resc := make(chan checkResult)
for _, srv := range servers {
srv := srv
go func() {
res := checkServer(deviceID, srv)
res.server = srv
resc <- res
}()
}
for _ = range servers {
res := <-resc
u, _ := url.Parse(res.server)
fmt.Printf("%s (%v):\n", u.Host, time.Since(t0))
if res.error != nil {
fmt.Println(" " + res.error.Error())
}
for _, addr := range res.direct {
fmt.Println(" address:", addr)
}
for _, rel := range res.relays {
fmt.Printf(" relay: %s (%d ms)\n", rel.URL, rel.Latency)
}
}
}
func checkServer(deviceID protocol.DeviceID, server string) checkResult {
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, nil)
if err != nil {
return checkResult{error: err}
}
res := make(chan checkResult, 1)
time.AfterFunc(timeout, func() {
res <- checkResult{error: errors.New("timeout")}
})
go func() {
direct, relays, err := disco.Lookup(deviceID)
res <- checkResult{direct: direct, relays: relays, error: err}
}()
return <-res
}
func usage() {
fmt.Printf("Usage:\n\t%s [options] <device ID>\n\nOptions:\n", os.Args[0])
flag.PrintDefaults()
}

63
cmd/stindex/dump.go Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"encoding/binary"
"fmt"
"log"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
)
func dump(ldb *leveldb.DB) {
it := ldb.NewIterator(nil, nil)
var dev protocol.DeviceID
for it.Next() {
key := it.Key()
switch key[0] {
case db.KeyTypeDevice:
folder := nulString(key[1 : 1+64])
devBytes := key[1+64 : 1+64+32]
name := nulString(key[1+64+32:])
copy(dev[:], devBytes)
fmt.Printf("[device] F:%q N:%q D:%v\n", folder, name, dev)
var f protocol.FileInfo
err := f.UnmarshalXDR(it.Value())
if err != nil {
log.Fatal(err)
}
fmt.Printf(" N:%q\n F:%#o\n M:%d\n V:%v\n S:%d\n B:%d\n", f.Name, f.Flags, f.Modified, f.Version, f.Size(), len(f.Blocks))
case db.KeyTypeGlobal:
folder := nulString(key[1 : 1+64])
name := nulString(key[1+64:])
fmt.Printf("[global] F:%q N:%q V:%x\n", folder, name, it.Value())
case db.KeyTypeBlock:
folder := nulString(key[1 : 1+64])
hash := key[1+64 : 1+64+32]
name := nulString(key[1+64+32:])
fmt.Printf("[block] F:%q H:%x N:%q I:%d\n", folder, hash, name, binary.BigEndian.Uint32(it.Value()))
case db.KeyTypeDeviceStatistic:
fmt.Printf("[dstat]\n %x\n %x\n", it.Key(), it.Value())
case db.KeyTypeFolderStatistic:
fmt.Printf("[fstat]\n %x\n %x\n", it.Key(), it.Value())
case db.KeyTypeVirtualMtime:
fmt.Printf("[mtime]\n %x\n %x\n", it.Key(), it.Value())
default:
fmt.Printf("[???]\n %x\n %x\n", it.Key(), it.Value())
}
}
}

90
cmd/stindex/dumpsize.go Normal file
View File

@@ -0,0 +1,90 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"container/heap"
"fmt"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
)
// An IntHeap is a min-heap of ints.
type SizedElement struct {
key string
size int
}
type ElementHeap []SizedElement
func (h ElementHeap) Len() int { return len(h) }
func (h ElementHeap) Less(i, j int) bool { return h[i].size > h[j].size }
func (h ElementHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *ElementHeap) Push(x interface{}) {
*h = append(*h, x.(SizedElement))
}
func (h *ElementHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
func dumpsize(ldb *leveldb.DB) {
h := &ElementHeap{}
heap.Init(h)
it := ldb.NewIterator(nil, nil)
var dev protocol.DeviceID
var ele SizedElement
for it.Next() {
key := it.Key()
switch key[0] {
case db.KeyTypeDevice:
folder := nulString(key[1 : 1+64])
devBytes := key[1+64 : 1+64+32]
name := nulString(key[1+64+32:])
copy(dev[:], devBytes)
ele.key = fmt.Sprintf("DEVICE:%s:%s:%s", dev, folder, name)
case db.KeyTypeGlobal:
folder := nulString(key[1 : 1+64])
name := nulString(key[1+64:])
ele.key = fmt.Sprintf("GLOBAL:%s:%s", folder, name)
case db.KeyTypeBlock:
folder := nulString(key[1 : 1+64])
hash := key[1+64 : 1+64+32]
name := nulString(key[1+64+32:])
ele.key = fmt.Sprintf("BLOCK:%s:%x:%s", folder, hash, name)
case db.KeyTypeDeviceStatistic:
ele.key = fmt.Sprintf("DEVICESTATS:%s", key[1:])
case db.KeyTypeFolderStatistic:
ele.key = fmt.Sprintf("FOLDERSTATS:%s", key[1:])
case db.KeyTypeVirtualMtime:
ele.key = fmt.Sprintf("MTIME:%s", key[1:])
default:
ele.key = fmt.Sprintf("UNKNOWN:%x", key)
}
ele.size = len(it.Value())
heap.Push(h, ele)
}
for h.Len() > 0 {
ele = heap.Pop(h).(SizedElement)
fmt.Println(ele.key, ele.size)
}
}

View File

@@ -7,25 +7,33 @@
package main
import (
"encoding/binary"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/db"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
)
func main() {
var mode string
log.SetFlags(0)
log.SetOutput(os.Stdout)
flag.StringVar(&mode, "mode", "dump", "Mode of operation: dump, dumpsize")
flag.Parse()
ldb, err := leveldb.OpenFile(flag.Arg(0), &opt.Options{
path := flag.Arg(0)
if path == "" {
path = filepath.Join(defaultConfigDir(), "index-v0.11.0.db")
}
fmt.Println("Path:", path)
ldb, err := leveldb.OpenFile(path, &opt.Options{
ErrorIfMissing: true,
Strict: opt.StrictAll,
OpenFilesCacheCapacity: 100,
@@ -34,53 +42,11 @@ func main() {
log.Fatal(err)
}
it := ldb.NewIterator(nil, nil)
var dev protocol.DeviceID
for it.Next() {
key := it.Key()
switch key[0] {
case db.KeyTypeDevice:
folder := nulString(key[1 : 1+64])
devBytes := key[1+64 : 1+64+32]
name := nulString(key[1+64+32:])
copy(dev[:], devBytes)
fmt.Printf("[device] F:%q N:%q D:%v\n", folder, name, dev)
var f protocol.FileInfo
err := f.UnmarshalXDR(it.Value())
if err != nil {
log.Fatal(err)
}
fmt.Printf(" N:%q\n F:%#o\n M:%d\n V:%v\n S:%d\n B:%d\n", f.Name, f.Flags, f.Modified, f.Version, f.Size(), len(f.Blocks))
case db.KeyTypeGlobal:
folder := nulString(key[1 : 1+64])
name := nulString(key[1+64:])
fmt.Printf("[global] F:%q N:%q V:%x\n", folder, name, it.Value())
case db.KeyTypeBlock:
folder := nulString(key[1 : 1+64])
hash := key[1+64 : 1+64+32]
name := nulString(key[1+64+32:])
fmt.Printf("[block] F:%q H:%x N:%q I:%d\n", folder, hash, name, binary.BigEndian.Uint32(it.Value()))
case db.KeyTypeDeviceStatistic:
fmt.Printf("[dstat]\n %x\n %x\n", it.Key(), it.Value())
case db.KeyTypeFolderStatistic:
fmt.Printf("[fstat]\n %x\n %x\n", it.Key(), it.Value())
default:
fmt.Printf("[???]\n %x\n %x\n", it.Key(), it.Value())
}
if mode == "dump" {
dump(ldb)
} else if mode == "dumpsize" {
dumpsize(ldb)
} else {
fmt.Println("Unknown mode")
}
}
func nulString(bs []byte) string {
for i := range bs {
if bs[i] == 0 {
return string(bs[:i])
}
}
return string(bs)
}

52
cmd/stindex/util.go Normal file
View File

@@ -0,0 +1,52 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"log"
"os"
"path/filepath"
"runtime"
"github.com/syncthing/syncthing/lib/osutil"
)
func nulString(bs []byte) string {
for i := range bs {
if bs[i] == 0 {
return string(bs[:i])
}
}
return string(bs)
}
func defaultConfigDir() string {
switch runtime.GOOS {
case "windows":
if p := os.Getenv("LocalAppData"); p != "" {
return filepath.Join(p, "Syncthing")
}
return filepath.Join(os.Getenv("AppData"), "Syncthing")
case "darwin":
dir, err := osutil.ExpandTilde("~/Library/Application Support/Syncthing")
if err != nil {
log.Fatal(err)
}
return dir
default:
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
return filepath.Join(xdgCfg, "syncthing")
}
dir, err := osutil.ExpandTilde("~/.config/syncthing")
if err != nil {
log.Fatal(err)
}
return dir
}
}

115
cmd/stsigtool/main.go Normal file
View File

@@ -0,0 +1,115 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"github.com/syncthing/syncthing/lib/signature"
"github.com/syncthing/syncthing/lib/upgrade"
)
func main() {
log.SetFlags(0)
log.SetOutput(os.Stdout)
flag.Parse()
if flag.NArg() < 1 {
log.Println(`Usage:
stsigtool <command>
Where command is one of:
gen
- generate a new key pair
sign <privkeyfile> <datafile>
- sign a file
verify <signaturefile> <datafile>
- verify a signature, using the built in public key
verify <signaturefile> <datafile> <pubkeyfile>
- verify a signature, using the specified public key file
`)
}
switch flag.Arg(0) {
case "gen":
gen()
case "sign":
sign(flag.Arg(1), flag.Arg(2))
case "verify":
if flag.NArg() == 4 {
verifyWithFile(flag.Arg(1), flag.Arg(2), flag.Arg(3))
} else {
verifyWithKey(flag.Arg(1), flag.Arg(2), upgrade.SigningKey)
}
}
}
func gen() {
priv, pub, err := signature.GenerateKeys()
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(priv)
os.Stdout.Write(pub)
}
func sign(keyname, dataname string) {
privkey, err := ioutil.ReadFile(keyname)
if err != nil {
log.Fatal(err)
}
fd, err := os.Open(dataname)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
sig, err := signature.Sign(privkey, fd)
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(sig)
}
func verifyWithFile(signame, dataname, keyname string) {
pubkey, err := ioutil.ReadFile(keyname)
if err != nil {
log.Fatal(err)
}
verifyWithKey(signame, dataname, pubkey)
}
func verifyWithKey(signame, dataname string, pubkey []byte) {
sig, err := ioutil.ReadFile(signame)
if err != nil {
log.Fatal(err)
}
fd, err := os.Open(dataname)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
err = signature.Verify(pubkey, sig, fd)
if err != nil {
log.Fatal(err)
}
log.Println("correct signature")
}

View File

@@ -0,0 +1,127 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"fmt"
"net"
"net/url"
"github.com/syncthing/syncthing/lib/config"
)
type addressLister struct {
upnpSvc *upnpSvc
cfg *config.Wrapper
}
func newAddressLister(upnpSvc *upnpSvc, cfg *config.Wrapper) *addressLister {
return &addressLister{
upnpSvc: upnpSvc,
cfg: cfg,
}
}
// ExternalAddresses returns a list of addresses that are our best guess for
// where we are reachable from the outside. As a special case, we may return
// one or more addresses with an empty IP address (0.0.0.0 or ::) and just
// port number - this means that the outside address of a NAT gateway should
// be substituted.
func (e *addressLister) ExternalAddresses() []string {
return e.addresses(false)
}
// AllAddresses returns a list of addresses that are our best guess for where
// we are reachable from the local network. Same conditions as
// ExternalAddresses, but private IPv4 addresses are included.
func (e *addressLister) AllAddresses() []string {
return e.addresses(true)
}
func (e *addressLister) addresses(includePrivateIPV4 bool) []string {
var addrs []string
// Grab our listen addresses from the config. Unspecified ones are passed
// on verbatim (to be interpreted by a global discovery server or local
// discovery peer). Public addresses are passed on verbatim. Private
// addresses are filtered.
for _, addrStr := range e.cfg.Options().ListenAddress {
addrURL, err := url.Parse(addrStr)
if err != nil {
l.Infoln("Listen address", addrStr, "is invalid:", err)
continue
}
addr, err := net.ResolveTCPAddr("tcp", addrURL.Host)
if err != nil {
l.Infoln("Listen address", addrStr, "is invalid:", err)
continue
}
if addr.IP == nil || addr.IP.IsUnspecified() {
// Address like 0.0.0.0:22000 or [::]:22000 or :22000; include as is.
addrs = append(addrs, tcpAddr(addr.String()))
} else if isPublicIPv4(addr.IP) || isPublicIPv6(addr.IP) {
// A public address; include as is.
addrs = append(addrs, tcpAddr(addr.String()))
} else if includePrivateIPV4 && addr.IP.To4().IsGlobalUnicast() {
// A private IPv4 address.
addrs = append(addrs, tcpAddr(addr.String()))
}
}
// Get an external port mapping from the upnpSvc, if it has one. If so,
// add it as another unspecified address.
if e.upnpSvc != nil {
if port := e.upnpSvc.ExternalPort(); port != 0 {
addrs = append(addrs, fmt.Sprintf("tcp://:%d", port))
}
}
return addrs
}
func isPublicIPv4(ip net.IP) bool {
ip = ip.To4()
if ip == nil {
// Not an IPv4 address (IPv6)
return false
}
// IsGlobalUnicast below only checks that it's not link local or
// multicast, and we want to exclude private (NAT:ed) addresses as well.
rfc1918 := []net.IPNet{
{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}},
{IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}},
{IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}},
}
for _, n := range rfc1918 {
if n.Contains(ip) {
return false
}
}
return ip.IsGlobalUnicast()
}
func isPublicIPv6(ip net.IP) bool {
if ip.To4() != nil {
// Not an IPv6 address (IPv4)
// (To16() returns a v6 mapped v4 address so can't be used to check
// that it's an actual v6 address)
return false
}
return ip.IsGlobalUnicast()
}
func tcpAddr(host string) string {
u := url.URL{
Scheme: "tcp",
Host: host,
}
return u.String()
}

View File

@@ -1,372 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"crypto/tls"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/thejerf/suture"
)
// The connection service listens on TLS and dials configured unconnected
// devices. Successful connections are handed to the model.
type connectionSvc struct {
*suture.Supervisor
cfg *config.Wrapper
myID protocol.DeviceID
model *model.Model
tlsCfg *tls.Config
conns chan *tls.Conn
}
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.Model, tlsCfg *tls.Config) *connectionSvc {
svc := &connectionSvc{
Supervisor: suture.NewSimple("connectionSvc"),
cfg: cfg,
myID: myID,
model: model,
tlsCfg: tlsCfg,
conns: make(chan *tls.Conn),
}
// There are several moving parts here; one routine per listening address
// to handle incoming connections, one routine to periodically attempt
// outgoing connections, and lastly one routine to the the common handling
// regardless of whether the connection was incoming or outgoing. It ends
// up as in the diagram below. We embed a Supervisor to manage the
// routines (i.e. log and restart if they crash or exit, etc).
//
// +-----------------+
// Incoming | +---------------+-+ +-----------------+
// Connections | | | | | Outgoing
// -------------->| | svc.listen | | | Connections
// | | (1 per listen | | svc.connect |-------------->
// | | address) | | |
// +-+ | | |
// +-----------------+ +-----------------+
// v v
// | |
// | |
// +------------+-----------+
// |
// | svc.conns
// v
// +-----------------+
// | |
// | |
// | svc.handle |------> model.AddConnection()
// | |
// | |
// +-----------------+
//
// TODO: Clean shutdown, and/or handling config changes on the fly. We
// partly do this now - new devices and addresses will be picked up, but
// not new listen addresses and we don't support disconnecting devices
// that are removed and so on...
svc.Add(serviceFunc(svc.connect))
for _, addr := range svc.cfg.Options().ListenAddress {
addr := addr
listener := serviceFunc(func() {
svc.listen(addr)
})
svc.Add(listener)
}
svc.Add(serviceFunc(svc.handle))
return svc
}
func (s *connectionSvc) handle() {
next:
for conn := range s.conns {
cs := conn.ConnectionState()
// We should have negotiated the next level protocol "bep/1.0" as part
// of the TLS handshake. Unfortunately this can't be a hard error,
// because there are implementations out there that don't support
// protocol negotiation (iOS for one...).
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != bepProtocolName {
l.Infof("Peer %s did not negotiate bep/1.0", conn.RemoteAddr())
}
// We should have received exactly one certificate from the other
// side. If we didn't, they don't have a device ID and we drop the
// connection.
certs := cs.PeerCertificates
if cl := len(certs); cl != 1 {
l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, conn.RemoteAddr())
conn.Close()
continue
}
remoteCert := certs[0]
remoteID := protocol.NewDeviceID(remoteCert.Raw)
// The device ID should not be that of ourselves. It can happen
// though, especially in the presence of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == myID {
l.Infof("Connected to myself (%s) - should not happen", remoteID)
conn.Close()
continue
}
// We should not already be connected to the other party. TODO: This
// could use some better handling. If the old connection is dead but
// hasn't timed out yet we may want to drop *that* connection and keep
// this one. But in case we are two devices connecting to each other
// in parallel we don't want to do that or we end up with no
// connections still established...
if s.model.ConnectedTo(remoteID) {
l.Infof("Connected to already connected device (%s)", remoteID)
conn.Close()
continue
}
for deviceID, deviceCfg := range s.cfg.Devices() {
if deviceID == remoteID {
// Verify the name on the certificate. By default we set it to
// "syncthing" when generating, but the user may have replaced
// the certificate and used another name.
certName := deviceCfg.CertName
if certName == "" {
certName = tlsDefaultCommonName
}
err := remoteCert.VerifyHostname(certName)
if err != nil {
// Incorrect certificate name is something the user most
// likely wants to know about, since it's an advanced
// config. Warn instead of Info.
l.Warnf("Bad certificate from %s (%v): %v", remoteID, conn.RemoteAddr(), err)
conn.Close()
continue next
}
// If rate limiting is set, and based on the address we should
// limit the connection, then we wrap it in a limiter.
limit := s.shouldLimit(conn.RemoteAddr())
wr := io.Writer(conn)
if limit && writeRateLimit != nil {
wr = &limitedWriter{conn, writeRateLimit}
}
rd := io.Reader(conn)
if limit && readRateLimit != nil {
rd = &limitedReader{conn, readRateLimit}
}
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression)
l.Infof("Established secure connection to %s at %s", remoteID, name)
if debugNet {
l.Debugf("cipher suite: %04X in lan: %t", conn.ConnectionState().CipherSuite, !limit)
}
s.model.AddConnection(conn, protoConn)
continue next
}
}
if !s.cfg.IgnoredDevice(remoteID) {
events.Default.Log(events.DeviceRejected, map[string]string{
"device": remoteID.String(),
"address": conn.RemoteAddr().String(),
})
l.Infof("Connection from %s with unknown device ID %s", conn.RemoteAddr(), remoteID)
} else {
l.Infof("Connection from %s with ignored device ID %s", conn.RemoteAddr(), remoteID)
}
conn.Close()
}
}
func (s *connectionSvc) listen(addr string) {
if debugNet {
l.Debugln("listening on", addr)
}
tcaddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection:", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
tcpConn := conn.(*net.TCPConn)
s.setTCPOptions(tcpConn)
tc := tls.Server(conn, s.tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
s.conns <- tc
}
}
func (s *connectionSvc) connect() {
delay := time.Second
for {
nextDevice:
for deviceID, deviceCfg := range s.cfg.Devices() {
if deviceID == myID {
continue
}
if s.model.ConnectedTo(deviceID) {
continue
}
var addrs []string
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if discoverer != nil {
t := discoverer.Lookup(deviceID)
if len(t) == 0 {
continue
}
addrs = append(addrs, t...)
}
} else {
addrs = append(addrs, addr)
}
}
for _, addr := range addrs {
host, port, err := net.SplitHostPort(addr)
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
// addr is on the form "1.2.3.4"
addr = net.JoinHostPort(addr, "22000")
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
addr = net.JoinHostPort(host, "22000")
}
if debugNet {
l.Debugln("dial", deviceCfg.DeviceID, addr)
}
raddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
s.setTCPOptions(conn)
tc := tls.Client(conn, s.tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
s.conns <- tc
continue nextDevice
}
}
time.Sleep(delay)
delay *= 2
if maxD := time.Duration(s.cfg.Options().ReconnectIntervalS) * time.Second; delay > maxD {
delay = maxD
}
}
}
func (*connectionSvc) setTCPOptions(conn *net.TCPConn) {
var err error
if err = conn.SetLinger(0); err != nil {
l.Infoln(err)
}
if err = conn.SetNoDelay(false); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlive(true); err != nil {
l.Infoln(err)
}
}
func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
if s.cfg.Options().LimitBandwidthInLan {
return true
}
tcpaddr, ok := addr.(*net.TCPAddr)
if !ok {
return true
}
for _, lan := range lans {
if lan.Contains(tcpaddr.IP) {
return false
}
}
return !tcpaddr.IP.IsLoopback()
}
func (s *connectionSvc) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool {
// We require a restart if a device as been removed.
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
for _, dev := range to.Devices {
newDevices[dev.DeviceID] = true
}
for _, dev := range from.Devices {
if !newDevices[dev.DeviceID] {
return false
}
}
return true
}

View File

@@ -9,10 +9,16 @@ package main
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
debugHTTP = strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all"
debugSuture = strings.Contains(os.Getenv("STTRACE"), "suture") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger.NewFacility("main", "Main package")
httpl = logger.DefaultLogger.NewFacility("http", "REST API")
)
func init() {
l.SetDebug("main", strings.Contains(os.Getenv("STTRACE"), "main") || os.Getenv("STTRACE") == "all")
l.SetDebug("http", strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all")
}

View File

@@ -20,65 +20,70 @@ import (
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/calmh/logger"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/auto"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt"
)
type guiError struct {
Time time.Time `json:"time"`
Error string `json:"error"`
}
var (
configInSync = true
guiErrors = []guiError{}
guiErrorsMut = sync.NewMutex()
startTime = time.Now()
)
type apiSvc struct {
id protocol.DeviceID
cfg config.GUIConfiguration
cfg *config.Wrapper
assetDir string
model *model.Model
eventSub *events.BufferedSubscription
discoverer *discover.CachingMux
relaySvc *relay.Svc
listener net.Listener
fss *folderSummarySvc
stop chan struct{}
systemConfigMut sync.Mutex
eventSub *events.BufferedSubscription
guiErrors *logger.Recorder
systemLog *logger.Recorder
}
func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription) (*apiSvc, error) {
func newAPISvc(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) (*apiSvc, error) {
svc := &apiSvc{
id: id,
cfg: cfg,
assetDir: assetDir,
model: m,
systemConfigMut: sync.NewMutex(),
eventSub: eventSub,
discoverer: discoverer,
relaySvc: relaySvc,
systemConfigMut: sync.NewMutex(),
guiErrors: errors,
systemLog: systemLog,
}
var err error
svc.listener, err = svc.getListener(cfg)
svc.listener, err = svc.getListener(cfg.GUI())
return svc, err
}
func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error) {
func (s *apiSvc) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
@@ -92,7 +97,7 @@ func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error)
name = tlsDefaultCommonName
}
cert, err = newCertificate(locations[locHTTPSCertFile], locations[locHTTPSKeyFile], name)
cert, err = tlsutil.NewCertificate(locations[locHTTPSCertFile], locations[locHTTPSKeyFile], name, tlsRSABits)
}
if err != nil {
return nil, err
@@ -115,20 +120,18 @@ func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error)
},
}
rawListener, err := net.Listen("tcp", cfg.Address)
rawListener, err := net.Listen("tcp", guiCfg.Address())
if err != nil {
return nil, err
}
listener := &DowngradingListener{rawListener, tlsCfg}
listener := &tlsutil.DowngradingListener{rawListener, tlsCfg}
return listener, nil
}
func (s *apiSvc) Serve() {
s.stop = make(chan struct{})
l.AddHandler(logger.LevelWarn, s.showGuiError)
// The GET handlers
getRestMux := http.NewServeMux()
getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
@@ -153,6 +156,9 @@ func (s *apiSvc) Serve() {
getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
// The POST handlers
postRestMux := http.NewServeMux()
@@ -161,7 +167,6 @@ func (s *apiSvc) Serve() {
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
postRestMux.HandleFunc("/rest/system/discovery", s.postSystemDiscovery) // device addr
postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
@@ -169,6 +174,9 @@ func (s *apiSvc) Serve() {
postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
// Debug endpoints, not for general use
getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
@@ -188,37 +196,38 @@ func (s *apiSvc) Serve() {
assets: auto.Assets(),
})
guiCfg := s.cfg.GUI()
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler := csrfMiddleware(s.id.String()[:5], "/rest", s.cfg.APIKey, mux)
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey(), mux)
// Add our version and ID as a header to responses
handler = withDetailsMiddleware(s.id, handler)
// Wrap everything in basic auth, if user/password is set.
if len(s.cfg.User) > 0 && len(s.cfg.Password) > 0 {
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], s.cfg, handler)
if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
}
// Redirect to HTTPS if we are supposed to
if s.cfg.UseTLS {
if guiCfg.UseTLS() {
handler = redirectToHTTPSMiddleware(handler)
}
if debugHTTP {
handler = debugMiddleware(handler)
}
handler = debugMiddleware(handler)
srv := http.Server{
Handler: handler,
ReadTimeout: 10 * time.Second,
}
s.fss = newFolderSummarySvc(s.model)
s.fss = newFolderSummarySvc(s.cfg, s.model)
defer s.fss.Stop()
s.fss.ServeBackground()
l.Infoln("API listening on", s.listener.Addr())
l.Infoln("GUI URL is", guiCfg.URL())
err := srv.Serve(s.listener)
// The return could be due to an intentional close. Wait for the stop
@@ -266,7 +275,6 @@ func (s *apiSvc) CommitConfiguration(from, to config.Configuration) bool {
// method.
return false
}
s.cfg = to.GUI
close(s.stop)
@@ -362,6 +370,36 @@ func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
})
}
func (s *apiSvc) getSystemDebug(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
names := l.Facilities()
enabled := l.FacilityDebugging()
sort.Strings(enabled)
json.NewEncoder(w).Encode(map[string]interface{}{
"facilities": names,
"enabled": enabled,
})
}
func (s *apiSvc) postSystemDebug(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
q := r.URL.Query()
for _, f := range strings.Split(q.Get("enable"), ",") {
if f == "" {
continue
}
l.SetDebug(f, true)
l.Infof("Enabled debug data for %q", f)
}
for _, f := range strings.Split(q.Get("disable"), ",") {
if f == "" {
continue
}
l.SetDebug(f, false)
l.Infof("Disabled debug data for %q", f)
}
}
func (s *apiSvc) getDBBrowse(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
@@ -402,12 +440,12 @@ func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getDBStatus(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
res := folderSummary(s.model, folder)
res := folderSummary(s.cfg, s.model, folder)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
func folderSummary(m *model.Model, folder string) map[string]interface{} {
func folderSummary(cfg *config.Wrapper, m *model.Model, folder string) map[string]interface{} {
var res = make(map[string]interface{})
res["invalid"] = cfg.Folders()[folder].Invalid
@@ -517,22 +555,21 @@ func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getSystemConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(cfg.Raw())
json.NewEncoder(w).Encode(s.cfg.Raw())
}
func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
s.systemConfigMut.Lock()
defer s.systemConfigMut.Unlock()
var to config.Configuration
err := json.NewDecoder(r.Body).Decode(&to)
to, err := config.ReadJSON(r.Body, myID)
if err != nil {
l.Warnln("decoding posted config:", err)
http.Error(w, err.Error(), 500)
return
}
if to.GUI.Password != cfg.GUI().Password {
if to.GUI.Password != s.cfg.GUI().Password {
if to.GUI.Password != "" {
hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
if err != nil {
@@ -547,7 +584,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
// Fixup usage reporting settings
if curAcc := cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
// UR was enabled
to.Options.URAccepted = usageReportVersion
to.Options.URUniqueID = randomString(8)
@@ -559,9 +596,9 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
// Activate and save
resp := cfg.Replace(to)
resp := s.cfg.Replace(to)
configInSync = !resp.RequiresRestart
cfg.Save()
s.cfg.Save()
}
func (s *apiSvc) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
@@ -579,7 +616,7 @@ func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
folder := qs.Get("folder")
if len(folder) > 0 {
if _, ok := cfg.Folders()[folder]; !ok {
if _, ok := s.cfg.Folders()[folder]; !ok {
http.Error(w, "Invalid folder ID", 500)
return
}
@@ -587,7 +624,7 @@ func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
if len(folder) == 0 {
// Reset all folders.
for folder := range cfg.Folders() {
for folder := range s.cfg.Folders() {
s.model.ResetFolder(folder)
}
s.flushResponse(`{"ok": "resetting database"}`, w)
@@ -625,8 +662,30 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["alloc"] = m.Alloc
res["sys"] = m.Sys - m.HeapReleased
res["tilde"] = tilde
if cfg.Options().GlobalAnnEnabled && discoverer != nil {
res["extAnnounceOK"] = discoverer.ExtAnnounceOK()
if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
res["discoveryEnabled"] = true
discoErrors := make(map[string]string)
discoMethods := 0
for disco, err := range s.discoverer.ChildErrors() {
discoMethods++
if err != nil {
discoErrors[disco] = err.Error()
}
}
res["discoveryMethods"] = discoMethods
res["discoveryErrors"] = discoErrors
}
if s.relaySvc != nil {
res["relaysEnabled"] = true
relayClientStatus := make(map[string]bool)
relayClientLatency := make(map[string]int)
for _, relay := range s.relaySvc.Relays() {
latency, ok := s.relaySvc.RelayStatus(relay)
relayClientStatus[relay] = ok
relayClientLatency[relay] = int(latency / time.Millisecond)
}
res["relayClientStatus"] = relayClientStatus
res["relayClientLatency"] = relayClientLatency
}
cpuUsageLock.RLock()
var cpusum float64
@@ -637,6 +696,7 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["cpuPercent"] = cpusum / float64(len(cpuUsagePercent)) / float64(runtime.NumCPU())
res["pathSeparator"] = string(filepath.Separator)
res["uptime"] = int(time.Since(startTime).Seconds())
res["startTime"] = startTime
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
@@ -644,51 +704,53 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getSystemError(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
guiErrorsMut.Lock()
json.NewEncoder(w).Encode(map[string][]guiError{"errors": guiErrors})
guiErrorsMut.Unlock()
json.NewEncoder(w).Encode(map[string][]logger.Line{
"errors": s.guiErrors.Since(time.Time{}),
})
}
func (s *apiSvc) postSystemError(w http.ResponseWriter, r *http.Request) {
bs, _ := ioutil.ReadAll(r.Body)
r.Body.Close()
s.showGuiError(0, string(bs))
l.Warnln(string(bs))
}
func (s *apiSvc) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
guiErrorsMut.Lock()
guiErrors = []guiError{}
guiErrorsMut.Unlock()
s.guiErrors.Clear()
}
func (s *apiSvc) showGuiError(l logger.LogLevel, err string) {
guiErrorsMut.Lock()
guiErrors = append(guiErrors, guiError{time.Now(), err})
if len(guiErrors) > 5 {
guiErrors = guiErrors[len(guiErrors)-5:]
}
guiErrorsMut.Unlock()
func (s *apiSvc) getSystemLog(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string][]logger.Line{
"messages": s.systemLog.Since(since),
})
}
func (s *apiSvc) postSystemDiscovery(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var device = qs.Get("device")
var addr = qs.Get("addr")
if len(device) != 0 && len(addr) != 0 && discoverer != nil {
discoverer.Hint(device, []string{addr})
func (s *apiSvc) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
for _, line := range s.systemLog.Since(since) {
fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
}
}
func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
devices := map[string][]discover.CacheEntry{}
devices := make(map[string]discover.CacheEntry)
if discoverer != nil {
if s.discoverer != nil {
// Device ids can't be marshalled as keys so we need to manually
// rebuild this map using strings. Discoverer may be nil if discovery
// has not started yet.
for device, entries := range discoverer.All() {
devices[device.String()] = entries
for device, entry := range s.discoverer.Cache() {
devices[device.String()] = entry
}
}
@@ -697,7 +759,7 @@ func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getReport(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(reportData(s.model))
json.NewEncoder(w).Encode(reportData(s.cfg, s.model))
}
func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
@@ -766,7 +828,7 @@ func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return
}
rel, err := upgrade.LatestRelease(Version)
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
if err != nil {
http.Error(w, err.Error(), 500)
return
@@ -809,7 +871,7 @@ func (s *apiSvc) getLang(w http.ResponseWriter, r *http.Request) {
}
func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestRelease(Version)
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
if err != nil {
l.Warnln("getting latest release:", err)
http.Error(w, err.Error(), 500)
@@ -830,6 +892,32 @@ func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
}
}
func (s *apiSvc) postSystemPause(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
s.model.PauseDevice(device)
}
func (s *apiSvc) postSystemResume(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
s.model.ResumeDevice(device)
}
func (s *apiSvc) postDBScan(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
@@ -881,7 +969,7 @@ func (s *apiSvc) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
tot := map[string]float64{}
count := map[string]float64{}
for _, folder := range cfg.Folders() {
for _, folder := range s.cfg.Folders() {
for _, device := range folder.DeviceIDs() {
deviceStr := device.String()
if s.model.ConnectedTo(device) {

View File

@@ -15,6 +15,7 @@ import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/sync"
"golang.org/x/crypto/bcrypt"
)
@@ -24,10 +25,17 @@ var (
sessionsMut = sync.NewMutex()
)
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
func emitLoginAttempt(success bool, username string) {
events.Default.Log(events.LoginAttempt, map[string]interface{}{
"success": success,
"username": username,
})
}
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
apiKey := cfg.APIKey()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
if apiKey != "" && r.Header.Get("X-API-Key") == apiKey {
next.ServeHTTP(w, r)
return
}
@@ -43,9 +51,7 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
}
}
if debugHTTP {
l.Debugln("Sessionless HTTP request with authentication; this is expensive.")
}
httpl.Debugln("Sessionless HTTP request with authentication; this is expensive.")
error := func() {
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
@@ -72,12 +78,15 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
return
}
if string(fields[0]) != cfg.User {
username := string(fields[0])
if username != cfg.User {
emitLoginAttempt(false, username)
error()
return
}
if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), fields[1]); err != nil {
emitLoginAttempt(false, username)
error()
return
}
@@ -92,6 +101,7 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
MaxAge: 0,
})
emitLoginAttempt(true, username)
next.ServeHTTP(w, r)
})
}

View File

@@ -7,7 +7,9 @@
package main
import (
"bytes"
"crypto/tls"
"errors"
"flag"
"fmt"
"io/ioutil"
@@ -21,31 +23,32 @@ import (
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"github.com/calmh/logger"
"github.com/juju/ratelimit"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/symlinks"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/thejerf/suture"
"golang.org/x/crypto/bcrypt"
)
var (
Version = "unknown-dev"
Codename = "Aluminium Ant"
Codename = "Beryllium Bedbug"
BuildEnv = "default"
BuildStamp = "0"
BuildDate time.Time
@@ -65,11 +68,21 @@ const (
)
const (
bepProtocolName = "bep/1.0"
pingEventInterval = time.Minute
bepProtocolName = "bep/1.0"
tlsDefaultCommonName = "syncthing"
tlsRSABits = 3072
pingEventInterval = time.Minute
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
)
var l = logger.DefaultLogger
// The discovery results are sorted by their source priority.
const (
ipv6LocalDiscoveryPriority = iota
ipv4LocalDiscoveryPriority
globalDiscoveryPriority
)
func init() {
if Version != "unknown-dev" {
@@ -102,16 +115,12 @@ func init() {
}
var (
cfg *config.Wrapper
myID protocol.DeviceID
confDir string
logFlags = log.Ltime
writeRateLimit *ratelimit.Bucket
readRateLimit *ratelimit.Bucket
stop = make(chan int)
discoverer *discover.Discoverer
cert tls.Certificate
lans []*net.IPNet
myID protocol.DeviceID
confDir string
logFlags = log.Ltime
stop = make(chan int)
cert tls.Certificate
lans []*net.IPNet
)
const (
@@ -141,25 +150,11 @@ 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:
- "beacon" (the beacon package)
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "http" (the main package; HTTP requests)
- "locks" (the sync package; trace long held locks)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "suture" (the suture package; service management)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)
facility strings listed below.
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
@@ -182,32 +177,38 @@ are mostly useful for developers. Use with care.
GOGC Percentage of heap growth at which to trigger GC. Default is
100. Lower numbers keep peak memory usage down, at the price
of CPU usage (ie. performance).`
of CPU usage (ie. performance).
Debugging Facilities
--------------------
The following are valid values for the STTRACE variable:
%s`
)
// Command line and environment options
var (
reset bool
showVersion bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
noConsole bool
generateDir string
logFile string
auditEnabled bool
verbose bool
noRestart = os.Getenv("STNORESTART") != ""
noUpgrade = os.Getenv("STNOUPGRADE") != ""
guiAddress = os.Getenv("STGUIADDRESS") // legacy
guiAuthentication = os.Getenv("STGUIAUTH") // legacy
guiAPIKey = os.Getenv("STGUIAPIKEY") // legacy
profiler = os.Getenv("STPROFILER")
guiAssets = os.Getenv("STGUIASSETS")
cpuProfile = os.Getenv("STCPUPROFILE") != ""
stRestarting = os.Getenv("STRESTART") != ""
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
reset bool
showVersion bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
noConsole bool
generateDir string
logFile string
auditEnabled bool
verbose bool
paused bool
noRestart = os.Getenv("STNORESTART") != ""
noUpgrade = os.Getenv("STNOUPGRADE") != ""
profiler = os.Getenv("STPROFILER")
guiAssets = os.Getenv("STGUIASSETS")
cpuProfile = os.Getenv("STCPUPROFILE") != ""
stRestarting = os.Getenv("STRESTART") != ""
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
)
func main() {
@@ -222,9 +223,9 @@ func main() {
flag.StringVar(&logFile, "logfile", "-", "Log file name (use \"-\" for stdout)")
}
var guiAddress, guiAPIKey string
flag.StringVar(&generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&guiAddress, "gui-address", guiAddress, "Override GUI address")
flag.StringVar(&guiAuthentication, "gui-authentication", guiAuthentication, "Override GUI authentication; username:password")
flag.StringVar(&guiAPIKey, "gui-apikey", guiAPIKey, "Override GUI API key")
flag.StringVar(&confDir, "home", "", "Set configuration directory")
flag.IntVar(&logFlags, "logflags", logFlags, "Select information in log line prefix")
@@ -237,10 +238,21 @@ func main() {
flag.StringVar(&upgradeTo, "upgrade-to", upgradeTo, "Force upgrade directly from specified URL")
flag.BoolVar(&auditEnabled, "audit", false, "Write events to audit file")
flag.BoolVar(&verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&paused, "paused", false, "Start with all devices paused")
flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, baseDirs["config"]))
longUsage := fmt.Sprintf(extraUsage, baseDirs["config"], debugFacilities())
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
if guiAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", guiAddress)
}
if guiAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", guiAPIKey)
}
if noConsole {
osutil.HideConsole()
}
@@ -293,10 +305,13 @@ func main() {
l.Warnln("Key exists; will not overwrite.")
l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0]))
} else {
cert, err = newCertificate(certFile, keyFile, tlsDefaultCommonName)
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, tlsRSABits)
if err != nil {
l.Fatalln("Create certificate:", err)
}
myID = protocol.NewDeviceID(cert.Certificate[0])
if err != nil {
l.Fatalln("load cert:", err)
l.Fatalln("Load certificate:", err)
}
if err == nil {
l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0]))
@@ -336,7 +351,11 @@ func main() {
}
if doUpgrade || doUpgradeCheck {
rel, err := upgrade.LatestRelease(Version)
releasesURL := "https://api.github.com/repos/syncthing/syncthing/releases?per_page=30"
if cfg, _, err := loadConfig(locations[locConfigFile]); err == nil {
releasesURL = cfg.Options().ReleasesURL
}
rel, err := upgrade.LatestRelease(releasesURL, Version)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
@@ -350,7 +369,7 @@ func main() {
if doUpgrade {
// Use leveldb database locks to protect against concurrent upgrades
_, err = leveldb.OpenFile(locations[locDatabase], &opt.Options{OpenFilesCacheCapacity: 100})
_, err = db.Open(locations[locDatabase])
if err != nil {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
@@ -383,21 +402,40 @@ func main() {
}
}
func debugFacilities() string {
facilities := l.Facilities()
// Get a sorted list of names
var names []string
maxLen := 0
for name := range facilities {
names = append(names, name)
if len(name) > maxLen {
maxLen = len(name)
}
}
sort.Strings(names)
// Format the choices
b := new(bytes.Buffer)
for _, name := range names {
fmt.Fprintf(b, " %-*s - %s\n", maxLen, name, facilities[name])
}
return b.String()
}
func upgradeViaRest() error {
cfg, err := config.Load(locations[locConfigFile], protocol.LocalDeviceID)
if err != nil {
return err
}
target := cfg.GUI().Address
if cfg.GUI().UseTLS {
target = "https://" + target
} else {
target = "http://" + target
}
target := cfg.GUI().URL()
r, _ := http.NewRequest("POST", target+"/rest/system/upgrade", nil)
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
r.Header.Set("X-API-Key", cfg.GUI().APIKey())
tr := &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
@@ -425,9 +463,7 @@ func syncthingMain() {
// We want any logging it does to go through our log system.
mainSvc := suture.New("main", suture.Spec{
Log: func(line string) {
if debugSuture {
l.Debugln(line)
}
l.Debugln(line)
},
})
mainSvc.ServeBackground()
@@ -444,6 +480,9 @@ func syncthingMain() {
mainSvc.Add(newVerboseSvc())
}
errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0)
systemLog := logger.NewRecorder(l, logger.LevelDebug, maxSystemLog, initialSystemLog)
// Event subscription for the API; must start early to catch the early events.
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000)
@@ -459,9 +498,10 @@ func syncthingMain() {
// Ensure that that we have a certificate and key.
cert, err := tls.LoadX509KeyPair(locations[locCertFile], locations[locKeyFile])
if err != nil {
cert, err = newCertificate(locations[locCertFile], locations[locKeyFile], tlsDefaultCommonName)
l.Infof("Generating RSA key and certificate for %s...", tlsDefaultCommonName)
cert, err = tlsutil.NewCertificate(locations[locCertFile], locations[locKeyFile], tlsDefaultCommonName, tlsRSABits)
if err != nil {
l.Fatalln("load cert:", err)
l.Fatalln(err)
}
}
@@ -474,6 +514,7 @@ func syncthingMain() {
l.Infoln(LongVersion)
l.Infoln("My ID:", myID)
printHashRate()
// Emit the Starting event, now that we know who we are.
@@ -486,33 +527,21 @@ func syncthingMain() {
cfgFile := locations[locConfigFile]
var myName string
// Load the configuration file, if it exists.
// If it does not, create a template.
if info, err := os.Stat(cfgFile); err == nil {
if !info.Mode().IsRegular() {
l.Fatalln("Config file is not a file?")
}
cfg, err = config.Load(cfgFile, myID)
if err == nil {
myCfg := cfg.Devices()[myID]
if myCfg.Name == "" {
myName, _ = os.Hostname()
} else {
myName = myCfg.Name
}
cfg, myName, err := loadConfig(cfgFile)
if err != nil {
if os.IsNotExist(err) {
l.Infoln("No config file; starting with empty defaults")
myName, _ = os.Hostname()
newCfg := defaultConfig(myName)
cfg = config.Wrap(cfgFile, newCfg)
cfg.Save()
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
} else {
l.Fatalln("Configuration:", err)
l.Fatalln("Loading config:", err)
}
} else {
l.Infoln("No config file; starting with empty defaults")
myName, _ = os.Hostname()
newCfg := defaultConfig(myName)
cfg = config.Wrap(cfgFile, newCfg)
cfg.Save()
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
}
if cfg.Raw().OriginalVersion != config.CurrentVersion {
@@ -567,54 +596,47 @@ func syncthingMain() {
symlinks.Supported = false
}
protocol.PingTimeout = time.Duration(opts.PingTimeoutS) * time.Second
protocol.PingIdleTime = time.Duration(opts.PingIdleTimeS) * time.Second
if opts.MaxSendKbps > 0 {
writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxSendKbps), int64(5*1000*opts.MaxSendKbps))
}
if opts.MaxRecvKbps > 0 {
readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
}
if (opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0) && !opts.LimitBandwidthInLan {
lans, _ = osutil.GetLans()
networks := make([]string, 0, len(lans))
for _, lan := range lans {
networks = append(networks, lan.String())
for _, lan := range opts.AlwaysLocalNets {
_, ipnet, err := net.ParseCIDR(lan)
if err != nil {
l.Infoln("Network", lan, "is malformed:", err)
continue
}
lans = append(lans, ipnet)
}
networks := make([]string, len(lans))
for i, lan := range lans {
networks[i] = lan.String()
}
l.Infoln("Local networks:", strings.Join(networks, ", "))
}
dbFile := locations[locDatabase]
ldb, err := leveldb.OpenFile(dbFile, dbOpts())
if leveldbIsCorrupted(err) {
ldb, err = leveldb.RecoverFile(dbFile, dbOpts())
}
if leveldbIsCorrupted(err) {
// The database is corrupted, and we've tried to recover it but it
// didn't work. At this point there isn't much to do beyond dropping
// the database and reindexing...
l.Infoln("Database corruption detected, unable to recover. Reinitializing...")
if err := resetDB(); err != nil {
l.Fatalln("Remove database:", err)
}
ldb, err = leveldb.OpenFile(dbFile, dbOpts())
}
ldb, err := db.Open(dbFile)
if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
}
protectedFiles := []string{
locations[locDatabase],
locations[locConfigFile],
locations[locCertFile],
locations[locKeyFile],
}
// Remove database entries for folders that no longer exist in the config
folders := cfg.Folders()
for _, folder := range db.ListFolders(ldb) {
for _, folder := range ldb.ListFolders() {
if _, ok := folders[folder]; !ok {
l.Infof("Cleaning data for dropped folder %q", folder)
db.DropFolder(ldb, folder)
}
}
m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb)
m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb, protectedFiles)
cfg.Subscribe(m)
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
@@ -623,7 +645,13 @@ func syncthingMain() {
m.StartDeadlockDetector(time.Duration(it) * time.Second)
}
} else if !IsRelease || IsBeta {
m.StartDeadlockDetector(20 * 60 * time.Second)
m.StartDeadlockDetector(20 * time.Minute)
}
if paused {
for device := range cfg.Devices() {
m.PauseDevice(device)
}
}
// Clear out old indexes for other devices. Otherwise we'll start up and
@@ -648,32 +676,88 @@ func syncthingMain() {
mainSvc.Add(m)
// GUI
setupGUI(mainSvc, cfg, m, apiSub)
// The default port we announce, possibly modified by setupUPnP next.
addr, err := net.ResolveTCPAddr("tcp", opts.ListenAddress[0])
uri, err := url.Parse(opts.ListenAddress[0])
if err != nil {
l.Fatalf("Failed to parse listen address %s: %v", opts.ListenAddress[0], err)
}
addr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
l.Fatalln("Bad listen address:", err)
}
// Start discovery
// The externalAddr tracks our external addresses for discovery purposes.
localPort := addr.Port
discoverer = discovery(localPort)
var addrList *addressLister
// Start UPnP. The UPnP service will restart global discovery if the
// external port changes.
// Start UPnP
if opts.UPnPEnabled {
upnpSvc := newUPnPSvc(cfg, localPort)
upnpSvc := newUPnPSvc(cfg, addr.Port)
mainSvc.Add(upnpSvc)
// The external address tracker needs to know about the UPnP service
// so it can check for an external mapped port.
addrList = newAddressLister(upnpSvc, cfg)
} else {
addrList = newAddressLister(nil, cfg)
}
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
cfg.Subscribe(connectionSvc)
// Start relay management
var relaySvc *relay.Svc
if opts.RelaysEnabled && (opts.GlobalAnnEnabled || opts.RelayWithoutGlobalAnn) {
relaySvc = relay.NewSvc(cfg, tlsCfg)
mainSvc.Add(relaySvc)
}
// Start discovery
cachedDiscovery := discover.NewCachingMux()
mainSvc.Add(cachedDiscovery)
if cfg.Options().GlobalAnnEnabled {
for _, srv := range cfg.GlobalDiscoveryServers() {
l.Infoln("Using discovery server", srv)
gd, err := discover.NewGlobal(srv, cert, addrList, relaySvc)
if err != nil {
l.Warnln("Global discovery:", err)
continue
}
// Each global discovery server gets its results cached for five
// minutes, and is not asked again for a minute when it's returned
// unsuccessfully.
cachedDiscovery.Add(gd, 5*time.Minute, time.Minute, globalDiscoveryPriority)
}
}
if cfg.Options().LocalAnnEnabled {
// v4 broadcasts
bcd, err := discover.NewLocal(myID, fmt.Sprintf(":%d", cfg.Options().LocalAnnPort), addrList, relaySvc)
if err != nil {
l.Warnln("IPv4 local discovery:", err)
} else {
cachedDiscovery.Add(bcd, 0, 0, ipv4LocalDiscoveryPriority)
}
// v6 multicasts
mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, addrList, relaySvc)
if err != nil {
l.Warnln("IPv6 local discovery:", err)
} else {
cachedDiscovery.Add(mcd, 0, 0, ipv6LocalDiscoveryPriority)
}
}
// GUI
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc, errors, systemLog)
// Start connection management
connectionSvc := connections.NewConnectionSvc(cfg, myID, m, tlsCfg, cachedDiscovery, relaySvc, bepProtocolName, tlsDefaultCommonName, lans)
mainSvc.Add(connectionSvc)
if cpuProfile {
@@ -709,7 +793,7 @@ func syncthingMain() {
// The usageReportingManager registers itself to listen to configuration
// changes, and there's nothing more we need to tell it from the outside.
// Hence we don't keep the returned pointer.
newUsageReportingManager(m, cfg)
newUsageReportingManager(cfg, m)
if opts.RestartOnWakeup {
go standbyMonitor()
@@ -719,7 +803,7 @@ func syncthingMain() {
if noUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
} else if IsRelease {
go autoUpgrade()
go autoUpgrade(cfg)
} else {
l.Infof("No automatic upgrades; %s is not a release version.", Version)
}
@@ -745,38 +829,43 @@ func syncthingMain() {
os.Exit(code)
}
func dbOpts() *opt.Options {
// Calculate a suitable database block cache capacity.
// printHashRate prints the hashing performance in MB/s, formatting it with
// appropriate precision for the value, i.e. 182 MB/s, 18 MB/s, 1.8 MB/s, 0.18
// MB/s.
func printHashRate() {
hashRate := cpuBench(3, 100*time.Millisecond)
// Default is 8 MiB.
blockCacheCapacity := 8 << 20
// Increase block cache up to this maximum:
const maxCapacity = 64 << 20
// ... which we reach when the box has this much RAM:
const maxAtRAM = 8 << 30
if v := cfg.Options().DatabaseBlockCacheMiB; v != 0 {
// Use the value from the config, if it's set.
blockCacheCapacity = v << 20
} else if bytes, err := memorySize(); err == nil {
// We start at the default of 8 MiB and use larger values for machines
// with more memory.
if bytes > maxAtRAM {
// Cap the cache at maxCapacity when we reach maxAtRam amount of memory
blockCacheCapacity = maxCapacity
} else if bytes > maxAtRAM/maxCapacity*int64(blockCacheCapacity) {
// Grow from the default to maxCapacity at maxAtRam amount of memory
blockCacheCapacity = int(bytes * maxCapacity / maxAtRAM)
}
l.Infoln("Database block cache capacity", blockCacheCapacity/1024, "KiB")
decimals := 0
if hashRate < 1 {
decimals = 2
} else if hashRate < 10 {
decimals = 1
}
return &opt.Options{
OpenFilesCacheCapacity: 100,
BlockCacheCapacity: blockCacheCapacity,
WriteBuffer: 4 << 20,
l.Infof("Single thread hash performance is ~%.*f MB/s", decimals, hashRate)
}
func loadConfig(cfgFile string) (*config.Wrapper, string, error) {
info, err := os.Stat(cfgFile)
if err != nil {
return nil, "", err
}
if !info.Mode().IsRegular() {
return nil, "", errors.New("configuration is not a file")
}
cfg, err := config.Load(cfgFile, myID)
if err != nil {
return nil, "", err
}
myCfg := cfg.Devices()[myID]
myName := myCfg.Name
if myName == "" {
myName, _ = os.Hostname()
}
return cfg, myName, nil
}
func startAuditing(mainSvc *suture.Supervisor) {
@@ -796,82 +885,53 @@ func startAuditing(mainSvc *suture.Supervisor) {
l.Infoln("Audit log in", auditFile)
}
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription) {
opts := cfg.Options()
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) {
guiCfg := cfg.GUI()
if guiCfg.Enabled && guiCfg.Address != "" {
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
if err != nil {
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
} else {
var hostOpen, hostShow string
switch {
case addr.IP == nil:
hostOpen = "localhost"
hostShow = "0.0.0.0"
case addr.IP.IsUnspecified():
hostOpen = "localhost"
hostShow = addr.IP.String()
default:
hostOpen = addr.IP.String()
hostShow = hostOpen
}
if !guiCfg.Enabled {
return
}
var proto = "http"
if guiCfg.UseTLS {
proto = "https"
}
api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow)
api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
if opts.StartBrowser && !noBrowser && !stRestarting {
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(urlOpen)
}
}
if cfg.Options().StartBrowser && !noBrowser && !stRestarting {
// 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(guiCfg.URL())
}
}
func defaultConfig(myName string) config.Configuration {
defaultFolder := config.NewFolderConfiguration("default", locations[locDefFolder])
defaultFolder.RescanIntervalS = 60
defaultFolder.MinDiskFreePct = 1
defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}}
defaultFolder.AutoNormalize = true
defaultFolder.MaxConflicts = -1
thisDevice := config.NewDeviceConfiguration(myID, myName)
thisDevice.Addresses = []string{"dynamic"}
newCfg := config.New(myID)
newCfg.Folders = []config.FolderConfiguration{
{
ID: "default",
RawPath: locations[locDefFolder],
RescanIntervalS: 60,
MinDiskFreePct: 1,
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
},
}
newCfg.Devices = []config.DeviceConfiguration{
{
DeviceID: myID,
Addresses: []string{"dynamic"},
Name: myName,
},
}
newCfg.Folders = []config.FolderConfiguration{defaultFolder}
newCfg.Devices = []config.DeviceConfiguration{thisDevice}
port, err := getFreePort("127.0.0.1", 8384)
if err != nil {
l.Fatalln("get free port (GUI):", err)
}
newCfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port)
newCfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port)
port, err = getFreePort("0.0.0.0", 22000)
if err != nil {
l.Fatalln("get free port (BEP):", err)
}
newCfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)}
newCfg.Options.ListenAddress = []string{fmt.Sprintf("tcp://0.0.0.0:%d", port)}
return newCfg
}
@@ -896,23 +956,6 @@ func shutdown() {
stop <- exitSuccess
}
func discovery(extPort int) *discover.Discoverer {
opts := cfg.Options()
disc := discover.NewDiscoverer(myID, opts.ListenAddress)
if opts.LocalAnnEnabled {
l.Infoln("Starting local discovery announcements")
disc.StartLocal(opts.LocalAnnPort, opts.LocalAnnMCAddr)
}
if opts.GlobalAnnEnabled {
l.Infoln("Starting global discovery announcements")
disc.StartGlobal(opts.GlobalAnnServers, uint16(extPort))
}
return disc
}
func ensureDir(dir string, mode int) {
fi, err := os.Stat(dir)
if os.IsNotExist(err) {
@@ -950,48 +993,6 @@ func getFreePort(host string, ports ...int) (int, error) {
return addr.Port, nil
}
func overrideGUIConfig(cfg config.GUIConfiguration, address, authentication, apikey string) config.GUIConfiguration {
if address != "" {
cfg.Enabled = true
if !strings.Contains(address, "//") {
// Assume just an IP was given. Don't touch he TLS setting.
cfg.Address = address
} else {
parsed, err := url.Parse(address)
if err != nil {
l.Fatalln(err)
}
cfg.Address = parsed.Host
switch parsed.Scheme {
case "http":
cfg.UseTLS = false
case "https":
cfg.UseTLS = true
default:
l.Fatalln("Unknown scheme:", parsed.Scheme)
}
}
}
if authentication != "" {
authenticationParts := strings.SplitN(authentication, ":", 2)
hash, err := bcrypt.GenerateFromPassword([]byte(authenticationParts[1]), 0)
if err != nil {
l.Fatalln("Invalid GUI password:", err)
}
cfg.User = authenticationParts[0]
cfg.Password = string(hash)
}
if apikey != "" {
cfg.APIKey = apikey
}
return cfg
}
func standbyMonitor() {
restartDelay := time.Duration(60 * time.Second)
now := time.Now()
@@ -1012,7 +1013,7 @@ func standbyMonitor() {
}
}
func autoUpgrade() {
func autoUpgrade(cfg *config.Wrapper) {
timer := time.NewTimer(0)
sub := events.Default.Subscribe(events.DeviceConnected)
for {
@@ -1026,7 +1027,7 @@ func autoUpgrade() {
case <-timer.C:
}
rel, err := upgrade.LatestRelease(Version)
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
if err == upgrade.ErrUpgradeUnsupported {
events.Default.Unsubscribe(sub)
return
@@ -1112,19 +1113,3 @@ func checkShortIDs(cfg *config.Wrapper) error {
}
return nil
}
// A "better" version of leveldb's errors.IsCorrupted.
func leveldbIsCorrupted(err error) bool {
switch {
case err == nil:
return false
case errors.IsCorrupted(err):
return true
case strings.Contains(err.Error(), "corrupted"):
return true
}
return false
}

View File

@@ -10,13 +10,10 @@ import (
"os"
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestFolderErrors(t *testing.T) {
@@ -38,11 +35,11 @@ func TestFolderErrors(t *testing.T) {
}
}
ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
ldb := db.OpenMemory()
// Case 1 - new folder, directory and marker created
m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
if err := m.CheckFolderHealth("folder"); err != nil {
@@ -73,7 +70,7 @@ func TestFolderErrors(t *testing.T) {
Folders: []config.FolderConfiguration{fcfg},
})
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
if err := m.CheckFolderHealth("folder"); err != nil {
@@ -96,7 +93,7 @@ func TestFolderErrors(t *testing.T) {
{Name: "dummyfile"},
})
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
@@ -127,7 +124,7 @@ func TestFolderErrors(t *testing.T) {
Folders: []config.FolderConfiguration{fcfg},
})
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder path missing" {

View File

@@ -28,8 +28,10 @@ var (
)
const (
countRestarts = 4
loopThreshold = 60 * time.Second
countRestarts = 4
loopThreshold = 60 * time.Second
logFileAutoCloseDelay = 5 * time.Second
logFileMaxOpenTime = time.Minute
)
func monitorMain() {
@@ -37,16 +39,10 @@ func monitorMain() {
os.Setenv("STMONITORED", "yes")
l.SetPrefix("[monitor] ")
var err error
var dst io.Writer = os.Stdout
if logFile != "-" {
var fileDst io.Writer
fileDst, err = os.Create(logFile)
if err != nil {
l.Fatalln("log file:", err)
}
var fileDst io.Writer = newAutoclosedFile(logFile, logFileAutoCloseDelay, logFileMaxOpenTime)
if runtime.GOOS == "windows" {
// Translate line breaks to Windows standard
@@ -262,3 +258,114 @@ func restartMonitorWindows(args []string) error {
cmd.Stdin = os.Stdin
return cmd.Start()
}
// An autoclosedFile is an io.WriteCloser that opens itself for appending on
// Write() and closes itself after an interval of no writes (closeDelay) or
// when the file has been open for too long (maxOpenTime). A call to Write()
// will return any error that happens on the resulting Open() call too. Errors
// on automatic Close() calls are silently swallowed...
type autoclosedFile struct {
name string // path to write to
closeDelay time.Duration // close after this long inactivity
maxOpenTime time.Duration // or this long after opening
fd io.WriteCloser // underlying WriteCloser
opened time.Time // timestamp when the file was last opened
closed chan struct{} // closed on Close(), stops the closerLoop
closeTimer *time.Timer // fires closeDelay after a write
mut sync.Mutex
}
func newAutoclosedFile(name string, closeDelay, maxOpenTime time.Duration) *autoclosedFile {
f := &autoclosedFile{
name: name,
closeDelay: closeDelay,
maxOpenTime: maxOpenTime,
mut: sync.NewMutex(),
closed: make(chan struct{}),
closeTimer: time.NewTimer(time.Minute),
}
go f.closerLoop()
return f
}
func (f *autoclosedFile) Write(bs []byte) (int, error) {
f.mut.Lock()
defer f.mut.Unlock()
// Make sure the file is open for appending
if err := f.ensureOpen(); err != nil {
return 0, err
}
// If we haven't run into the maxOpenTime, postpone close for another
// closeDelay
if time.Since(f.opened) < f.maxOpenTime {
f.closeTimer.Reset(f.closeDelay)
}
return f.fd.Write(bs)
}
func (f *autoclosedFile) Close() error {
f.mut.Lock()
defer f.mut.Unlock()
// Stop the timer and closerLoop() routine
f.closeTimer.Stop()
close(f.closed)
// Close the file, if it's open
if f.fd != nil {
return f.fd.Close()
}
return nil
}
// Must be called with f.mut held!
func (f *autoclosedFile) ensureOpen() error {
if f.fd != nil {
// File is already open
return nil
}
// We open the file for write only, and create it if it doesn't exist.
flags := os.O_WRONLY | os.O_CREATE
if f.opened.IsZero() {
// This is the first time we are opening the file. We should truncate
// it to better emulate an os.Create() call.
flags |= os.O_TRUNC
} else {
// The file was already opened once, so we should append to it.
flags |= os.O_APPEND
}
fd, err := os.OpenFile(f.name, flags, 0644)
if err != nil {
return err
}
f.fd = fd
f.opened = time.Now()
return nil
}
func (f *autoclosedFile) closerLoop() {
for {
select {
case <-f.closeTimer.C:
// Close the file when the timer expires.
f.mut.Lock()
if f.fd != nil {
f.fd.Close() // errors, schmerrors
f.fd = nil
}
f.mut.Unlock()
case <-f.closed:
return
}
}
}

View File

@@ -15,7 +15,7 @@ import (
"syscall"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
)
func init() {

View File

@@ -9,6 +9,7 @@ package main
import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/sync"
@@ -20,6 +21,7 @@ import (
type folderSummarySvc struct {
*suture.Supervisor
cfg *config.Wrapper
model *model.Model
stop chan struct{}
immediate chan string
@@ -33,9 +35,10 @@ type folderSummarySvc struct {
lastEventReqMut sync.Mutex
}
func newFolderSummarySvc(m *model.Model) *folderSummarySvc {
func newFolderSummarySvc(cfg *config.Wrapper, m *model.Model) *folderSummarySvc {
svc := &folderSummarySvc{
Supervisor: suture.NewSimple("folderSummarySvc"),
cfg: cfg,
model: m,
stop: make(chan struct{}),
immediate: make(chan string),
@@ -162,13 +165,13 @@ func (c *folderSummarySvc) foldersToHandle() []string {
func (c *folderSummarySvc) sendSummary(folder string) {
// The folder summary contains how many bytes, files etc
// are in the folder and how in sync we are.
data := folderSummary(c.model, folder)
data := folderSummary(c.cfg, c.model, folder)
events.Default.Log(events.FolderSummary, map[string]interface{}{
"folder": folder,
"summary": data,
})
for _, devCfg := range cfg.Folders()[folder].Devices {
for _, devCfg := range c.cfg.Folders()[folder].Devices {
if devCfg.DeviceID.Equals(myID) {
// We already know about ourselves.
continue

View File

@@ -11,26 +11,30 @@ import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/upnp"
)
// The UPnP service runs a loop for discovery of IGDs (Internet Gateway
// Devices) and setup/renewal of a port mapping.
type upnpSvc struct {
cfg *config.Wrapper
localPort int
stop chan struct{}
cfg *config.Wrapper
localPort int
extPort int
extPortMut sync.Mutex
stop chan struct{}
}
func newUPnPSvc(cfg *config.Wrapper, localPort int) *upnpSvc {
return &upnpSvc{
cfg: cfg,
localPort: localPort,
cfg: cfg,
localPort: localPort,
extPortMut: sync.NewMutex(),
}
}
func (s *upnpSvc) Serve() {
extPort := 0
foundIGD := true
s.stop = make(chan struct{})
@@ -38,7 +42,15 @@ func (s *upnpSvc) Serve() {
igds := upnp.Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
if len(igds) > 0 {
foundIGD = true
extPort = s.tryIGDs(igds, extPort)
s.extPortMut.Lock()
oldExtPort := s.extPort
s.extPortMut.Unlock()
newExtPort := s.tryIGDs(igds, oldExtPort)
s.extPortMut.Lock()
s.extPort = newExtPort
s.extPortMut.Unlock()
} else if foundIGD {
// Only print a notice if we've previously found an IGD or this is
// the first time around.
@@ -64,6 +76,13 @@ func (s *upnpSvc) Stop() {
close(s.stop)
}
func (s *upnpSvc) ExternalPort() int {
s.extPortMut.Lock()
port := s.extPort
s.extPortMut.Unlock()
return port
}
func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
// Lets try all the IGDs we found and use the first one that works.
// TODO: Use all of them, and sort out the resulting mess to the
@@ -76,17 +95,10 @@ func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
}
if extPort != prevExtPort {
// External port changed; refresh the discovery announcement.
// TODO: Don't reach out to some magic global here?
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
if s.cfg.Options().GlobalAnnEnabled {
discoverer.StopGlobal()
discoverer.StartGlobal(s.cfg.Options().GlobalAnnServers, uint16(extPort))
}
}
if debugNet {
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
}
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
return extPort
}

View File

@@ -10,30 +10,37 @@ import (
"bytes"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"runtime"
"sort"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/thejerf/suture"
)
// Current version number of the usage report, for acceptance purposes. If
// fields are added or changed this integer must be incremented so that users
// are prompted for acceptance of the new report.
const usageReportVersion = 1
const usageReportVersion = 2
type usageReportingManager struct {
cfg *config.Wrapper
model *model.Model
sup *suture.Supervisor
}
func newUsageReportingManager(m *model.Model, cfg *config.Wrapper) *usageReportingManager {
func newUsageReportingManager(cfg *config.Wrapper, m *model.Model) *usageReportingManager {
mgr := &usageReportingManager{
cfg: cfg,
model: m,
}
@@ -54,9 +61,7 @@ func (m *usageReportingManager) VerifyConfiguration(from, to config.Configuratio
func (m *usageReportingManager) CommitConfiguration(from, to config.Configuration) bool {
if to.Options.URAccepted >= usageReportVersion && m.sup == nil {
// Usage reporting was turned on; lets start it.
svc := &usageReportingService{
model: m.model,
}
svc := newUsageReportingService(m.cfg, m.model)
m.sup = suture.NewSimple("usageReporting")
m.sup.Add(svc)
m.sup.ServeBackground()
@@ -75,8 +80,9 @@ func (m *usageReportingManager) String() string {
// reportData returns the data to be sent in a usage report. It's used in
// various places, so not part of the usageReportingSvc object.
func reportData(m *model.Model) map[string]interface{} {
func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
res := make(map[string]interface{})
res["urVersion"] = usageReportVersion
res["uniqueID"] = cfg.Options().URUniqueID
res["version"] = Version
res["longVersion"] = LongVersion
@@ -106,45 +112,158 @@ func reportData(m *model.Model) map[string]interface{} {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
res["memoryUsageMiB"] = (mem.Sys - mem.HeapReleased) / 1024 / 1024
var perf float64
for i := 0; i < 5; i++ {
p := cpuBench()
if p > perf {
perf = p
}
}
res["sha256Perf"] = perf
res["sha256Perf"] = cpuBench(5, 125*time.Millisecond)
bytes, err := memorySize()
if err == nil {
res["memorySize"] = bytes / 1024 / 1024
}
res["numCPU"] = runtime.NumCPU()
var rescanIntvs []int
folderUses := map[string]int{
"readonly": 0,
"ignorePerms": 0,
"ignoreDelete": 0,
"autoNormalize": 0,
}
for _, cfg := range cfg.Folders() {
rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS)
if cfg.ReadOnly {
folderUses["readonly"]++
}
if cfg.IgnorePerms {
folderUses["ignorePerms"]++
}
if cfg.IgnoreDelete {
folderUses["ignoreDelete"]++
}
if cfg.AutoNormalize {
folderUses["autoNormalize"]++
}
}
sort.Ints(rescanIntvs)
res["rescanIntvs"] = rescanIntvs
res["folderUses"] = folderUses
deviceUses := map[string]int{
"introducer": 0,
"customCertName": 0,
"compressAlways": 0,
"compressMetadata": 0,
"compressNever": 0,
"dynamicAddr": 0,
"staticAddr": 0,
}
for _, cfg := range cfg.Devices() {
if cfg.Introducer {
deviceUses["introducer"]++
}
if cfg.CertName != "" && cfg.CertName != "syncthing" {
deviceUses["customCertName"]++
}
if cfg.Compression == protocol.CompressAlways {
deviceUses["compressAlways"]++
} else if cfg.Compression == protocol.CompressMetadata {
deviceUses["compressMetadata"]++
} else if cfg.Compression == protocol.CompressNever {
deviceUses["compressNever"]++
}
for _, addr := range cfg.Addresses {
if addr == "dynamic" {
deviceUses["dynamicAddr"]++
} else {
deviceUses["staticAddr"]++
}
}
}
res["deviceUses"] = deviceUses
defaultAnnounceServersDNS, defaultAnnounceServersIP, otherAnnounceServers := 0, 0, 0
for _, addr := range cfg.Options().GlobalAnnServers {
if addr == "default" || addr == "default-v4" || addr == "default-v6" {
defaultAnnounceServersDNS++
} else if stringIn(addr, config.DefaultDiscoveryServersIP) {
defaultAnnounceServersIP++
} else {
otherAnnounceServers++
}
}
res["announce"] = map[string]interface{}{
"globalEnabled": cfg.Options().GlobalAnnEnabled,
"localEnabled": cfg.Options().LocalAnnEnabled,
"defaultServersDNS": defaultAnnounceServersDNS,
"defaultServersIP": defaultAnnounceServersIP,
"otherServers": otherAnnounceServers,
}
defaultRelayServers, otherRelayServers := 0, 0
for _, addr := range cfg.Options().RelayServers {
switch addr {
case "dynamic+https://relays.syncthing.net/endpoint":
defaultRelayServers++
default:
otherRelayServers++
}
}
res["relays"] = map[string]interface{}{
"enabled": cfg.Options().RelaysEnabled,
"defaultServers": defaultRelayServers,
"otherServers": otherRelayServers,
}
res["usesRateLimit"] = cfg.Options().MaxRecvKbps > 0 || cfg.Options().MaxSendKbps > 0
res["upgradeAllowedManual"] = !(upgrade.DisabledByCompilation || noUpgrade)
res["upgradeAllowedAuto"] = !(upgrade.DisabledByCompilation || noUpgrade) && cfg.Options().AutoUpgradeIntervalH > 0
return res
}
func stringIn(needle string, haystack []string) bool {
for _, s := range haystack {
if needle == s {
return true
}
}
return false
}
type usageReportingService struct {
cfg *config.Wrapper
model *model.Model
stop chan struct{}
}
func newUsageReportingService(cfg *config.Wrapper, model *model.Model) *usageReportingService {
return &usageReportingService{
cfg: cfg,
model: model,
stop: make(chan struct{}),
}
}
func (s *usageReportingService) sendUsageReport() error {
d := reportData(s.model)
d := reportData(s.cfg, s.model)
var b bytes.Buffer
json.NewEncoder(&b).Encode(d)
var client = http.DefaultClient
transp := &http.Transport{}
client := &http.Client{Transport: transp}
if BuildEnv == "android" {
// This works around the lack of DNS resolution on Android... :(
tr := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
return net.Dial(network, "194.126.249.13:443")
},
transp.Dial = func(network, addr string) (net.Conn, error) {
return dialer.Dial(network, "194.126.249.13:443")
}
client = &http.Client{Transport: tr}
}
_, err := client.Post("https://data.syncthing.net/newdata", "application/json", &b)
if s.cfg.Options().URPostInsecurely {
transp.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
_, err := client.Post(s.cfg.Options().URURL, "application/json", &b)
return err
}
@@ -154,7 +273,7 @@ func (s *usageReportingService) Serve() {
l.Infoln("Starting usage reporting")
defer l.Infoln("Stopping usage reporting")
t := time.NewTimer(10 * time.Minute) // time to initial report at start
t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second) // time to initial report at start
for {
select {
case <-s.stop:
@@ -174,7 +293,17 @@ func (s *usageReportingService) Stop() {
}
// cpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s
func cpuBench() float64 {
func cpuBench(iterations int, duration time.Duration) float64 {
var perf float64
for i := 0; i < iterations; i++ {
if v := cpuBenchOnce(duration); v > perf {
perf = v
}
}
return perf
}
func cpuBenchOnce(duration time.Duration) float64 {
chunkSize := 100 * 1 << 10
h := sha256.New()
bs := make([]byte, chunkSize)
@@ -182,7 +311,7 @@ func cpuBench() float64 {
t0 := time.Now()
b := 0
for time.Since(t0) < 125*time.Millisecond {
for time.Since(t0) < duration {
h.Write(bs)
b += chunkSize
}

View File

@@ -8,6 +8,7 @@ package main
import (
"fmt"
"strings"
"github.com/syncthing/syncthing/lib/events"
)
@@ -60,7 +61,7 @@ func (s *verboseSvc) WaitForStart() {
func (s *verboseSvc) formatEvent(ev events.Event) string {
switch ev.Type {
case events.Ping, events.DownloadProgress:
case events.Ping, events.DownloadProgress, events.LocalIndexUpdated:
// Skip
return ""
@@ -74,7 +75,7 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
return fmt.Sprintf("Discovered device %v at %v", data["device"], data["addrs"])
case events.DeviceConnected:
data := ev.Data.(map[string]string)
return fmt.Sprintf("Connected to device %v at %v", data["id"], data["addr"])
return fmt.Sprintf("Connected to device %v at %v (type %s)", data["id"], data["addr"], data["type"])
case events.DeviceDisconnected:
data := ev.Data.(map[string]string)
return fmt.Sprintf("Disconnected from device %v", data["id"])
@@ -86,9 +87,6 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
case events.RemoteIndexUpdated:
data := ev.Data.(map[string]interface{})
return fmt.Sprintf("Device %v sent an index update for %q with %d items", data["device"], data["folder"], data["items"])
case events.LocalIndexUpdated:
data := ev.Data.(map[string]interface{})
return fmt.Sprintf("Updated index for folder %q with %v items", data["folder"], data["items"])
case events.DeviceRejected:
data := ev.Data.(map[string]interface{})
@@ -123,6 +121,45 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
delete(sum, "ignorePatterns")
delete(sum, "stateChanged")
return fmt.Sprintf("Summary for folder %q is %v", data["folder"], data["summary"])
case events.FolderScanProgress:
data := ev.Data.(map[string]interface{})
folder := data["folder"].(string)
current := data["current"].(int64)
total := data["total"].(int64)
var pct int64
if total > 0 {
pct = 100 * current / total
}
return fmt.Sprintf("Scanning folder %q, %d%% done", folder, pct)
case events.DevicePaused:
data := ev.Data.(map[string]string)
device := data["device"]
return fmt.Sprintf("Device %v was paused", device)
case events.DeviceResumed:
data := ev.Data.(map[string]string)
device := data["device"]
return fmt.Sprintf("Device %v was resumed", device)
case events.ExternalPortMappingChanged:
data := ev.Data.(map[string]int)
port := data["port"]
return fmt.Sprintf("External port mapping changed; new port is %d.", port)
case events.RelayStateChanged:
data := ev.Data.(map[string][]string)
newRelays := data["new"]
return fmt.Sprintf("Relay state changed; connected relay(s) are %s.", strings.Join(newRelays, ", "))
case events.LoginAttempt:
data := ev.Data.(map[string]interface{})
username := data["username"].(string)
var success string
if data["success"].(bool) {
success = "successful"
} else {
success = "failed"
}
return fmt.Sprintf("Login %s for username %s.", success, username)
}
return fmt.Sprintf("%s %#v", ev.Type, ev)

View File

@@ -1,8 +1,8 @@
# Systemd Configuration
This directory contains configuration files for running syncthing under the
This directory contains configuration files for running Syncthing under the
"systemd" service manager on Linux both under either a systemd system service or
systemd user service. For further documentation take a look at the [systemd
section][1] on the Github Wiki.
section][1] on http://docs.syncthing.net.
[1]: http://docs.syncthing.net/users/autostart.html#systemd
[1]: http://docs.syncthing.net/users/autostart.html#systemd

View File

@@ -2,6 +2,7 @@
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=http://docs.syncthing.net/
After=network.target
Wants=syncthing-inotify@.service
[Service]
User=%i

View File

@@ -2,6 +2,7 @@
Description=Syncthing - Open Source Continuous File Synchronization
Documentation=http://docs.syncthing.net/
After=network.target
Wants=syncthing-inotify.service
[Service]
Environment=STNORESTART=yes

View File

@@ -0,0 +1,16 @@
# Upstart Configuration
This directory contains example configuration files for running Syncthing under
the "Upstart" service manager on Linux. To have syncthing start when you login
place "user/syncthing.conf" in the "/home/[username]/.config/upstart/" folder.
To have syncthing start when the system boots place "system/syncthing.conf"
in the "/etc/init/" folder.
To manualy start syncthing via Upstart when using the system configuration use:
```
sudo initctl start syncthing
```
For further documentation see [http://docs.syncthing.net/users/autostart.html][1].
[1]: http://docs.syncthing.net/users/autostart.html#Upstart

View File

@@ -0,0 +1,13 @@
description "Syncthing"
start on (local-filesystems and net-device-up IFACE!=lo)
stop on runlevel [!2345]
env STNORESTART=yes
env HOME=/home/$USER
setuid "$USER"
setgid "$USER"
exec /usr/local/bin/syncthing
respawn

View File

@@ -0,0 +1,21 @@
# Location of the syncthing executable
env SYNCTHING_EXE="/usr/local/bin"
# Set the name of the application
description "Syncthing"
# Start syncthing you login to your desktop
start on desktop-start
# Stop syncthing you logout of your desktop
stop on desktop-end
# Set STNORESTART to yes to have Upstart monitor the process instead
# of having a separate syncthing process do the monitoring
env STNORESTART=yes
# If Upstart detects syncthing has failed - it should restart it
respawn
# the syncthing command Upstart is to execute when it is started up
exec $SYNCTHING_EXE -no-browser

View File

@@ -21,6 +21,10 @@ ul+h5 {
margin-top: 1.5em;
}
#content {
margin-bottom: 50px;
}
.panel-progress {
background: #3498db;
height: 3px;
@@ -41,8 +45,17 @@ identicon {
position: relative;
width: 1em;
height: 1em;
line-height: 1em;
overflow: visible;
line-height: 1;
margin-right: 5px;
}
.identicon {
width: 1em;
height: 1em;
}
.identicon rect {
fill: #333;
}
.checkbox {
@@ -55,17 +68,7 @@ identicon {
.popover {
max-width: none;
}
.identicon {
width: 20px;
height: 20px;
}
.panel-heading .identicon {
display: block;
position: absolute;
top: 1px;
min-width: 250px;
}
.panel-heading .fa, .modal-header .fa {
@@ -77,10 +80,6 @@ identicon {
overflow: hidden;
}
.identicon rect {
fill: #666;
}
.text-monospace {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}

View File

Binary file not shown.

View File

@@ -2,5 +2,5 @@
font-family: 'Raleway';
font-style: normal;
font-weight: 500;
src: local('Raleway'), url(raleway-500.woff) format('woff');
src: local('Raleway Medium'), local('Raleway-Medium'), url(raleway-500.woff) format('woff');
}

View File

@@ -1,8 +1,8 @@
{
"A negative number of days doesn't make sense.": "Няма логика на зададен отрицателен брой дни.",
"A negative number of days doesn't make sense.": "Няма логика в задаването на отрицателен брой дни.",
"A new major version may not be compatible with previous versions.": "Нова основна версия, която може да не е съвмеситима с предишни версии.",
"API Key": "API Ключ",
"About": "За Програмата",
"About": "За програмата",
"Actions": "Действия",
"Add": "Добави",
"Add Device": "Добави устройство",
@@ -13,35 +13,36 @@
"Advanced": "Допълнителни",
"Advanced Configuration": "Допълнителни настройки",
"All Data": "Всички данни",
"Allow Anonymous Usage Reporting?": "Разреши анонимен доклад за ползване на програмата?",
"Allow Anonymous Usage Reporting?": "Разреши анонимно докладване за употребата на програмата?",
"Alphabetic": "Азбучен ред",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Друга команда се занимава с версиите. Тази команда трябва да премахни файла от синхронизираната папка.",
"Anonymous Usage Reporting": "Анонимен Доклад",
"Anonymous Usage Reporting": "Анонимен доклад",
"Any devices configured on an introducer device will be added to this device as well.": "Устройства настроени на introducer компютъра също ще бъдат добавени към този компютър.",
"Automatic upgrades": "Автоматични ъпдейти",
"Be careful!": "Внимавай!",
"Automatic upgrades": "Автоматично обновяване",
"Be careful!": "Внимание!",
"Bugs": "Бъгове",
"CPU Utilization": "Натоварване на Процесора",
"Changelog": "Сипъск с промени",
"CPU Utilization": "Процесор в употреба",
"Changelog": "Списък с промени",
"Clean out after": "Изчисти след",
"Close": "Затвори",
"Command": "Команда",
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
"Compression": "Компресия",
"Connection Error": "Грешка при Свързването",
"Compression": "Компресиране",
"Connection Error": "Грешка при свързването",
"Copied from elsewhere": "Копиране от някъде другаде",
"Copied from original": "Копиран от оригинала",
"Copyright © 2015 the following Contributors:": "Правата запазени © 2015 Сътрудници:",
"Copyright © 2015 the following Contributors:": "Всички правата запазени © 2015 Сътрудници:",
"Delete": "Изтрий",
"Deleted": "Изтрито",
"Device ID": "Идентификатор на устройство",
"Device Identification": "Идентификация на устройство",
"Device Identification": "Идентификатор на устройство",
"Device Name": "Име на устройство",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) желае да се свърже. Добави ново устройство?",
"Devices": "Устройства",
"Disconnected": "Прекрати Връзката",
"Disconnected": "Не е свързано",
"Discovery": "Откриване",
"Documentation": "Документация",
"Download Rate": "Скорост на Теглене",
"Download Rate": "Скорост на сваляне",
"Downloaded": "Изтеглен",
"Downloading": "Изтегляне",
"Edit": "Промени",
@@ -49,49 +50,49 @@
"Edit Folder": "Промени папка",
"Editing": "Променяне",
"Enable UPnP": "Включи UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Въведи адреси разделени със запетая (\"tcp://ip:port\", \"tcp://host:port\") или \"dynamic\", за да извършиш автоматична връзка на адреси.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Въведете адреси разделени със запетая (\"tcp://ip:port\", \"tcp://host:port\") или \"dynamic\", за да автоматично откриване на наличните адреси.",
"Enter ignore patterns, one per line.": "Добави шаблони за игнориране, по един на ред.",
"Error": "Грешка",
"External File Versioning": "Външно упраление на версиите",
"External File Versioning": "Външно управление на версиите",
"Failed Items": "Неуспешни",
"File Pull Order": "По ред на дърпане",
"File Versioning": "Файлови Версии",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Битовете за права за достъп са игнорирани, когато се проверява за промени. Използвай с файлови системи тип FAT.",
"File Pull Order": "Ред на сваляне",
"File Versioning": "Версии на файловете",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Битовете за права за достъп ще бъдат игнорирани, когато се проверява за промени. Ползвайте за файлови системи тип FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Файловете биват преместени в .stversions папка, когато са заменен или изтрити от Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Когато syncthing замени или изтрие файл той се премества в .stversions и преименува с дабавени дата и час.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файловете са защитени от промени направени на други устройства, но промени направени на това устройство ще бъдат синхронизирани с другите устройства.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Когато syncthing замени или изтрие файл той се премества в .stversions и преименува с набавени дата и час.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Защитава файловете от промени направени на други устройства, но промените направени на това устройство ще бъдат синхронизирани с останалите устройства.",
"Folder": "Папка",
"Folder ID": "Идентификатор на папка",
"Folder ID": "Идентификатор на папката",
"Folder Master": "Главна папка",
"Folder Path": "Път до папката",
"Folders": "Папки",
"GUI": "Потребителски интефейс",
"GUI Authentication Password": "Парола за Потребителския Интерфейс",
"GUI Authentication User": "Потребител за Потребителския Интерфейс",
"GUI Listen Addresses": "Адрес за Свързване с Потребителския Интерфейс",
"GUI": "Потребителски интерфейс",
"GUI Authentication Password": "Парола за потребителския интерфейс",
"GUI Authentication User": "Потребител за потребителския интерфейс",
"GUI Listen Addresses": "Адрес за свързване с потребителския интерфейс",
"Generate": "Генерирай",
"Global Discovery": "Глобавно Откриване",
"Global Discovery Server": "Сървър за Глобално Откриване",
"Global Discovery": "Глобално откриване",
"Global Discovery Server": "Сървър за глобално откриване",
"Global State": "Глобално състояние",
"Help": "Помощ",
"Home page": "Начална страница",
"Ignore": "Игнорирай",
"Ignore Patterns": "Шаблони за Игнориране",
"Ignore Permissions": "Игнорирай Права за Достъп",
"Incoming Rate Limit (KiB/s)": "Входящ Лимит на Скоростта (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправилни настройки могат да повредят съдържанието на папката и да попречат на по-нататъшно синхронизиране.",
"Ignore Patterns": "Шаблони за игнориране",
"Ignore Permissions": "Игнорирай правата за достъп",
"Incoming Rate Limit (KiB/s)": "Входящ лимит на скоростта (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправилни настройки могат да повредят файловете и да попречат на синхронизирането.",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
"Keep Versions": "Пази Версии",
"Largest First": "Най-големите първо",
"Keep Versions": "Пази версии",
"Largest First": " Първо най-големите",
"Last File Received": "Последния получен файл",
"Last seen": "Последно видян",
"Later": "По-късно",
"Local Discovery": "Локално Откриване",
"Local Discovery": "Локално откриване",
"Local State": "Локално състояние",
"Local State (Total)": "Локално Състояние (Общо)",
"Local State (Total)": "Локално състояние (Общо)",
"Major Upgrade": "Основно Обновяване",
"Maximum Age": "Максимална Възраст",
"Maximum Age": "Максимална възраст",
"Metadata Only": "Само мета информация",
"Minimum Free Disk Space": "Минимално свободно дисково пространство",
"Move to top of queue": "Премести в началото на опашката",
@@ -99,66 +100,66 @@
"Never": "Никога",
"New Device": "Ново устройство",
"New Folder": "Нова папка",
"Newest First": "Най-новите първо",
"Newest First": "Първо най-новите",
"No": "Не",
"No File Versioning": "Няма Файлови Версии",
"No File Versioning": "Без версии",
"Notice": "Известие",
"OK": "ОК",
"Off": "Изключено",
"Oldest First": "Най-старите първо",
"Oldest First": "Първо най-старите",
"Options": "Настройки",
"Out of Sync": "Не синхронизиран",
"Out of Sync": "Несинхронизирано",
"Out of Sync Items": "Несинхронизирани елементи",
"Outgoing Rate Limit (KiB/s)": "Лимит на Изходящата Скорост (KiB/s)",
"Override Changes": "Замени Промените",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Пътят до папката на този компютър. Ще бъде създадена ако не съществува. Символът тилда (~) може да бъде използван като заместител на",
"Outgoing Rate Limit (KiB/s)": "Лимит на изходящата скорост (KiB/s)",
"Override Changes": "Наложи локалните промени",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Път до папката на това устройство. Ако не съществува ще бъде създадена. Символът тилда (~) може да бъде използван като заместител на",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Пътят, където версиите да бъдат складирани(остави празно за папката .stversions).",
"Pause": "Пауза",
"Paused": "На пауза",
"Please consult the release notes before performing a major upgrade.": "Моля прочети бележките по обновяването преди да започнеш.",
"Please wait": "Моля изчакай",
"Preview": "Преглед",
"Preview Usage Report": "Разгледай Доклада за Използване",
"Preview Usage Report": "Разгледай доклада за използване",
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
"RAM Utilization": "RAM Натоварване",
"Random": "Произволно",
"RAM Utilization": "RAM в употреба",
"Random": "Произволен",
"Relayed via": "Препратено през",
"Relays": "Препращачи",
"Release Notes": "Бележки по обновяването",
"Remove": "Премахни",
"Rescan": "Повторно Сканиране",
"Rescan All": "Пълно повторно сканиране",
"Rescan Interval": "Интервал за Повторно Сканиране",
"Rescan": "Сканирай повторно",
"Rescan All": "Сканирай повторно всички",
"Rescan Interval": "Интервал за повторно сканиране",
"Restart": "Рестартирай",
"Restart Needed": "Изискава се Рестартиране",
"Restart Needed": "Изисква се рестартиране",
"Restarting": "Рестартиране",
"Resume": "Пусни",
"Reused": "Повторно използван",
"Save": "Запази",
"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.": "Изберете папките за споделяне с това устройство.",
"Settings": "Настройки",
"Share": "Сподели",
"Share Folder": "Сподели папка",
"Share Folders With Device": "Сподели папки с това устройство",
"Share With Devices": "Сподели с устройства",
"Share With Devices": "Споделяне с устройства",
"Share this folder?": "Сподели тази папка?",
"Shared With": "Споделена със",
"Short identifier for the folder. Must be the same on all cluster devices.": "Кратък идентификатор на папката. Трябва да бъде същият на всички компютри.",
"Show ID": "Покажи Идентификатора",
"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": "Спри програмата",
"Shutdown Complete": "Спирането завършено",
"Simple File Versioning": "Опростени Файлови Версии",
"Simple File Versioning": "Опростени версии",
"Single level wildcard (matches within a directory only)": "Маска на едно ниво (покрива само в папка)",
"Smallest First": "Най-малките първо",
"Source Code": "Сорс Код",
"Staggered File Versioning": "Наслагващи се Файлови Версии",
"Start Browser": "Стартирай Браузъра",
"Smallest First": "Първо най-малките",
"Source Code": "Сорс код",
"Staggered File Versioning": "Наслагващи се версии",
"Start Browser": "Стартирай браузъра",
"Statistics": "Статистика",
"Stopped": "Спряна",
"Stopped": "Без синхронизиране",
"Support": "Помощ",
"Sync Protocol Listen Addresses": "Адрес за слушане на синхронизиращия протокол",
"Syncing": "Синхронизиране",
@@ -171,12 +172,12 @@
"The aggregated statistics are publicly available at {%url%}.": "Сумарната статистика е публично достъпна на {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурацията е запазена, но не е активирана. Syncthing трябва да рестартира, за да се активира новата конфигурация.",
"The device ID cannot be blank.": "Полето идентификатор на устройство не може да бъде празно.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор на устройство за въвеждане тук, може да бъде намерен в \"Промени > Покажи Идентификатора\". Интервалите и тиретата са пожелание(биват прескачани).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Криптираният доклад се изпраща дневно. Използва се, за да следи общи платформи, размери на папки и версии на приложението. Ако събираните данни се променят, ще бъдете информиран с подобен на този диалог.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Въведни идентификатор на устройство не е валиден. Трябва да бъде 52 или 56 символа и да се състои от букви и цифри, като интервалите и тиретата са пожелание.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор на устройство за въвеждане тук, може да бъде намерен в \"Промени > Покажи идентификатора\". Интервалите и тиретата са пожелание (биват прескачани).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Криптираният доклад се изпраща ежедневно. Използва се, за отичане на ползваните платформи, размер на папки и версии на приложението. Ако събираните данни се променят, ще бъдете информиран с подобен на този диалог.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Въведеният идентификатор на устройство не е валиден. Трябва да бъде 52 или 56 символа и да се състои от букви и цифри, като интервалите и тиретата са пожелание.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Първият параметър за командата е пътя до папката, а вторият е релативния път в самата папка.",
"The folder ID cannot be blank.": "Полето идентификатор на папка неможе да бъде празно.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Идентификаторът на папка трябва да бъде къс (64 символа или по-малко) състоящ се само от букви, цифри, точка(.), тире(-) и подчерта (_).",
"The folder ID cannot be blank.": "Полето идентификатор на папка не може да бъде празно.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Идентификаторът на папка трябва да бъде къс (64 символа или по-малко) състоящ се само от букви, цифри, точка (.), тире (-) и подчерта (_).",
"The folder ID must be unique.": "Идентификаторът на папката тряба да бъде уникален.",
"The folder path cannot be blank.": "Пътят до папката не може да бъде празен.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Използва се следния интервал: за първия час се пази версия всеки 30 секунди, за първия ден се пази версия всеки час, за първите 30 дена се пази версия всеки ден, до максимума се пази една версия всяка седмица.",
@@ -184,35 +185,36 @@
"The maximum age must be a number and cannot be blank.": "Максималната възраст трябва да е число и не може д ае празна.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максималното време да се пазят весрсии (в дни, сложи 0, за да пазиш версии завинаги).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Минималното свободно дисково пространство в проценти трябва да е между 0 и 100 (включително).",
"The number of days must be a number and cannot be blank.": "Броят дни трябва да бъде число и неможе да бъде празно.",
"The number of days must be a number and cannot be blank.": "Броят дни трябва да бъде число и не може да бъде празно.",
"The number of days to keep files in the trash can. Zero means forever.": "Броят дни за запазване на файловете в кошчето. Нула значи завинаги.",
"The number of old versions to keep, per file.": "Броят стари версии, които да бъдат пазени за всеки файл.",
"The number of versions must be a number and cannot be blank.": "Броят версии трябва да бъде число и не може да бъде празно.",
"The path cannot be blank.": "Пътят неможе да бъде празен.",
"The rate limit must be a non-negative number (0: no limit)": "Ограничението на скорста трябва да бъде положително число (0: неограничено)",
"The path cannot be blank.": "Пътят не може да бъде празен.",
"The rate limit must be a non-negative number (0: no limit)": "Ограничението на скоростта трябва да бъде положително число (0: неограничено)",
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
"They are retried automatically and will be synced when the error is resolved.": "Ще бъдат спрени и автоматично синхронизирани, когато грешката бъде оправена.",
"This is a major version upgrade.": "Това е нова основна версия.",
"Trash Can File Versioning": "Версии на файлове в кошчето",
"Unknown": "Неясен",
"Unshared": "Споделянето прекратено",
"Trash Can File Versioning": "Само на файловете в кошчето",
"Unknown": "Неясно",
"Unshared": "Несподелена",
"Unused": "Неизползван",
"Up to Date": "Актуален",
"Up to Date": "Синхронизирано",
"Updated": "Обновено",
"Upgrade": "Обнови",
"Upgrade To {%version%}": "Обновен До {{version}}",
"Upgrade To {%version%}": "Обновен до {{version}}",
"Upgrading": "Обновяване",
"Upload Rate": "Скорост на Качване",
"Uptime": "Работи вече",
"Use HTTPS for GUI": "Използвай HTTPS за Потребителския Интерфейс",
"Upload Rate": "Скорост на качване",
"Uptime": "Работи от",
"Use HTTPS for GUI": "Използвай HTTPS за потребителския интерфейс",
"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.": "Версиите биват изтривани автоматично, когато са по-стари от максималната възраст или надминават броя файлове разрешени в даден интервал.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Когато добавяш ново устройство помни, че твоето устройство също трябва да бъде добавено от другата страна.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когато добавяш нов идентификатор на папка помни, че той се използва за свързване на папките на различни устройства. Главни/малки букви са от значение и трябва да са еднакви на всички устройства.",
"Yes": "Да",
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
"days": "дни",
"full documentation": "пълна документация",
"items": "артикула",
"items": "елемента",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папка \"{{folder}}\"."
}

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol conectar-se. Afegir nou dispositiu?",
"Devices": "Dispositius",
"Disconnected": "Desconnectat",
"Discovery": "Discovery",
"Documentation": "Documentació",
"Download Rate": "Tasca de descarrega",
"Downloaded": "Descarregat",
@@ -212,6 +213,7 @@
"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ó.",
"days": "days",
"full documentation": "documentació sencera",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartir la carpeta \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol connectar-se. Afegir nou dispositiu?",
"Devices": "Dispositius",
"Disconnected": "Desconnectat",
"Discovery": "Discovery",
"Documentation": "Documentació",
"Download Rate": "Velocitat de descàrrega",
"Downloaded": "Descarregat",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quant s'afig una nova carpeta, hi ha que tindre en compte que l'ID de la carpeta s'utilitza per a juntar les carpetes entre dispositius. Són sensibles a les majúscules i deuen coincidir exactament entre tots els dispositius.",
"Yes": "Sí",
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
"days": "days",
"full documentation": "Documentació completa",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"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": "Odpojen",
"Discovery": "Oznamování",
"Documentation": "Dokumentace",
"Download Rate": "Rychlost stahování",
"Downloaded": "Staženo",
@@ -79,7 +80,7 @@
"Ignore Patterns": "Ignorované vzory",
"Ignore Permissions": "Ignorovat oprávnění",
"Incoming Rate Limit (KiB/s)": "Omezení příchozí rychlosti (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Nesprávné nastavení může poškodit obsah Vašich složek a učinit Syncthing nefunkční.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Nesprávné nastavení může poškodit obsah Vašich adresářů a učinit Syncthing nefunkční.",
"Introducer": "Zavaděč",
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
"Keep Versions": "Ponechat verze",
@@ -157,7 +158,7 @@
"Source Code": "Zdrojový kód",
"Staggered File Versioning": "Postupné verzování souborů",
"Start Browser": "Otevřít prohlížeč",
"Statistics": "Statistika",
"Statistics": "Statistiky",
"Stopped": "Pozastaveno",
"Support": "Podpora",
"Sync Protocol Listen Addresses": "Adresa naslouchání synchronizačního protokolu",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
"Yes": "Ano",
"You must keep at least one version.": "Je třeba ponechat alespoň jednu verzi.",
"days": "dní",
"full documentation": "plná dokumentace",
"items": "položky",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce sdílet adresář \"{{folder}}\"."

View File

@@ -23,7 +23,7 @@
"Bugs": "Fehler",
"CPU Utilization": "Prozessorauslastung",
"Changelog": "Änderungsprotokoll",
"Clean out after": "Aufräumen nach",
"Clean out after": "Löschen nach",
"Close": "Schließen",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
@@ -35,21 +35,22 @@
"Delete": "Löschen",
"Deleted": "Gelöscht",
"Device ID": "Geräte ID",
"Device Identification": "Gerät Identifikation",
"Device Identification": "Geräte 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",
"Discovery": "Gerätesuche",
"Documentation": "Dokumentation",
"Download Rate": "Download-Rate",
"Download Rate": "Download",
"Downloaded": "Heruntergeladen",
"Downloading": "Lädt herunter",
"Edit": "Bearbeiten",
"Edit Device": "Gerät bearbeiten",
"Edit Folder": "Verzeichnis bearbeiten",
"Editing": "Bearbeiten",
"Editing": "Bearbeitet",
"Enable UPnP": "UPnP aktivieren",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Geben Sie durch Komma getrennte Adressen an (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\", um die Adresse automatisch zu ermitteln.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Kommagetrennte Adressen (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\" eingeben, um die Adresse automatisch zu ermitteln.",
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
"Error": "Fehler",
"External File Versioning": "Externe Dateiversionierung",
@@ -57,9 +58,9 @@
"File Pull Order": "Dateiübertragungsreihenfolge",
"File Versioning": "Dateiversionierung",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Wenn Dateien von Syncthing ersetzt oder gelöscht werden sollen, werden sie vorher in den .stversions Ordner verschoben.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, datiert in das Verzeichnis .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind auf diesem Gerät schreibgeschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Folder": "Verzeichnis",
"Folder ID": "Verzeichnis ID",
"Folder Master": "Master Verzeichnis - schreibgeschützt",
@@ -71,7 +72,7 @@
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
"Generate": "Generieren",
"Global Discovery": "Globale Gerätesuche",
"Global Discovery Server": "Globale(r) Indexserver",
"Global Discovery Server": "Globaler Gerätesuchserver",
"Global State": "Globaler Status",
"Help": "Hilfe",
"Home page": "Homepage",
@@ -89,7 +90,7 @@
"Later": "Später",
"Local Discovery": "Lokale Gerätesuche",
"Local State": "Lokaler Status",
"Local State (Total)": "Lokaler Status (total)",
"Local State (Total)": "Lokaler Status (Gesamt)",
"Major Upgrade": "Hauptversionsupgrade",
"Maximum Age": "Höchstalter",
"Metadata Only": "Nur Metadaten",
@@ -111,18 +112,18 @@
"Out of Sync Items": "Nicht synchronisierte Objekte",
"Outgoing Rate Limit (KiB/s)": "Limit Datenrate (ausgehend) (KB/s)",
"Override Changes": "Änderungen überschreiben",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem die Versionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
"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 Gerät. Ordner werden erzeugt, wenn sie nicht existieren. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem alte Dateiversionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
"Pause": "Pause",
"Paused": "Pausiert",
"Please consult the release notes before performing a major upgrade.": "BItte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
"Please wait": "Bitte warten",
"Preview": "Vorschau",
"Preview Usage Report": "Vorschau des Nutzungsberichts",
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Suchstrukturen",
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"RAM Utilization": "RAM Auslastung",
"Random": "Zufall",
"Relayed via": "Weiterleitung über",
"Relayed via": "Weitergeleitet über",
"Relays": "Weiterleitungen",
"Release Notes": "Veröffentlichungsnotizen",
"Remove": "Entfernen",
@@ -135,7 +136,7 @@
"Resume": "Fortfahren",
"Reused": "Erneut benutzt",
"Save": "Speichern",
"Scanning": "Wird gescannt",
"Scanning": "Scannen",
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du dieses Verzeichnis teilen willst.",
"Select the folders to share with this device.": "Wähle die Verzeichnisse aus, die du mit diesem Gerät teilen möchtest",
"Settings": "Einstellungen",
@@ -147,8 +148,8 @@
"Shared With": "Geteilt mit",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kurze ID für das Verzeichnis. Muss auf allen Verbunds-Geräten gleich sein.",
"Show ID": "ID anzeigen",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients weitergegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird auf den Namen aktualisiert, den das Gerät angibt.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients im Cluster weitergegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird auf diesem Gerät als Gerätename angezeigt und an die anderen Geräte im Geräte-Verbund weitergegeben. Wenn kein Gerätename anegegeben wird, wird der Name des entfernten Gerätes genommen.",
"Shutdown": "Herunterfahren",
"Shutdown Complete": "Vollständig Heruntergefahren",
"Simple File Versioning": "Einfache Dateiversionierung",
@@ -163,13 +164,13 @@
"Sync Protocol Listen Addresses": "Adresse(n) für das Synchronisierungsprotokoll",
"Syncing": "Synchronisiere",
"Syncthing has been shut down.": "Syncthing wurde heruntergefahren.",
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile davon:",
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile von:",
"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.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing scheint ein Problem mit der Verarbeitung Deiner Eingabe zu haben. Bitte lade die Seite neu oder führe einen Neustart 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 übernehmen.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber noch nicht aktiviert. Syncthing muss neugestartet werden, um die neue Konfiguration zu übernehmen.",
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird verwendet, um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
@@ -179,18 +180,18 @@
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Bezeichnung haben (64 Zeichen oder weniger). Sie darf nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
"The folder ID must be unique.": "Die Verzeichnis ID darf nur einmal existieren.",
"The folder path cannot be blank.": "Der Verzeichnispfad darf nicht leer sein.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es wird in folgenden Abständen versioniert: in der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag, danach wird bis zum Höchstalter eine Version pro Woche beibehalten.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es wird in folgenden Abständen versioniert: In der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag. Danach wird bis zum angegebenen Höchstalter eine Version pro Woche behalten.",
"The following items could not be synchronized.": "Die folgenden Objekte konnten nicht synchronisiert werden.",
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen) (0 um alte Versionen für immer zu behalten).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Der Prozentsatz des minimal freien Festplattenspeichers muss eine nichtnegative Zahl zwischen (inklusive) 0 und 100 sein. ",
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
"The number of days to keep files in the trash can. Zero means forever.": "Dauer in Tagen für welche die Dateien aufgehoben werden sollen. 0 bedeutet für immer.",
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei behalten werden sollen.",
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
"The path cannot be blank.": "Der Pfad darf nicht leer sein.",
"The rate limit must be a non-negative number (0: no limit)": "Das Daterate-Limit muss eine nicht negative Anzahl sein (0: kein Limit)",
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl von Sekunden sein.",
"The rate limit must be a non-negative number (0: no limit)": "Das Daterate-Limit muss eine nicht negative Anzahl sein (0 = kein Limit).",
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl (in Sekunden) sein.",
"They are retried automatically and will be synced when the error is resolved.": "Sie werden automatisch heruntergeladen und werden synchronisiert, wenn der Fehler behoben wurde.",
"This is a major version upgrade.": "Dies ist eine neue Hauptversion.",
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
@@ -207,11 +208,12 @@
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
"Version": "Version",
"Versions Path": "Versionierungspfad",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Versionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die Höchstzahl der Dateien je Zeitabschnitt überschritten wird.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf der Gegenseite hinzugefügt werden muss.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beim Hinzufügen eines neuen Verzeichnisses beachte, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Dateiversionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die angegebene Höchstzahl an Dateien erreicht ist.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf den anderen Geräten hinzugefügt werden muss.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beachte bitte beim Hinzufügen eines neuen Verzeichnisses, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
"Yes": "Ja",
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
"days": "Tage",
"full documentation": "Komplette Dokumentation",
"items": "Objekte",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Η συσκευή {{device}} ({{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής1",
"Devices": "Συσκευές",
"Disconnected": "Αποσυνδεδεμένος",
"Discovery": "Discovery",
"Documentation": "Τεκμηρίωση",
"Download Rate": "Ταχύτητα λήψης",
"Downloaded": "Έχει ληφθεί",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
"Yes": "Ναι",
"You must keep at least one version.": "Πρέπει να τηρήσεις τουλάχιστον μια έκδοση.",
"days": "days",
"full documentation": "πλήρης τεκμηρίωση",
"items": "εγγραφές",
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}»."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Disconnected",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Downloaded": "Downloaded",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"days": "days",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Disconnected",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Downloaded": "Downloaded",
@@ -49,7 +50,7 @@
"Edit Folder": "Edit Folder",
"Editing": "Editing",
"Enable UPnP": "Enable UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error",
"External File Versioning": "External File Versioning",
@@ -113,6 +114,8 @@
"Override Changes": "Override Changes",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
"Pause": "Pause",
"Paused": "Paused",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Please wait": "Please wait",
"Preview": "Preview",
@@ -120,6 +123,8 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Random": "Random",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Release Notes": "Release Notes",
"Remove": "Remove",
"Rescan": "Rescan",
@@ -128,6 +133,7 @@
"Restart": "Restart",
"Restart Needed": "Restart Needed",
"Restarting": "Restarting",
"Resume": "Resume",
"Reused": "Reused",
"Save": "Save",
"Scanning": "Scanning",
@@ -207,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"days": "days",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."

View File

@@ -10,8 +10,8 @@
"Add new folder?": "¿Añadir una nueva carpeta?",
"Address": "Dirección",
"Addresses": "Direcciones",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Avanzado",
"Advanced Configuration": "Configuración Avanzada",
"All Data": "Todos los datos",
"Allow Anonymous Usage Reporting?": "¿Deseas permitir el envío anónimo de informes de uso?",
"Alphabetic": "Alfabético",
@@ -19,7 +19,7 @@
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo de introducción será añadido también.",
"Automatic upgrades": "Actualizaciones automáticas",
"Be careful!": "Be careful!",
"Be careful!": "¡Ten cuidado!",
"Bugs": "Errores (bugs)",
"CPU Utilization": "Uso de CPU",
"Changelog": "Informe de cambios",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) quiere conectarse. ¿Añadir nuevo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Descubrimiento",
"Documentation": "Documentación",
"Download Rate": "Velocidad de descarga",
"Downloaded": "Descargado",
@@ -49,18 +50,18 @@
"Edit Folder": "Editar repositorio",
"Editing": "Editando",
"Enable UPnP": "Habilitar UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduzca separados por comas (\"tcp://ip:port\", \"tcp://host:port\") direcciones o \"dynamic\" para llevar a cabo la detección automática de la dirección.",
"Enter ignore patterns, one per line.": "Introducir patrones a ignorar, uno por línea.",
"Error": "Error",
"External File Versioning": "Versionado externo de fichero",
"Failed Items": "Failed Items",
"Failed Items": "Elementos fallidos",
"File Pull Order": "Orden de ficheros del pull",
"File Versioning": "Versionado de ficheros",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Los bits de permiso de ficheros son ignorados cuando se buscan cambios. Utilizar en sistemas de ficheros FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Los archivos serán movidos a la carpeta .stversions cuando sean reemplazados o borrados por Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Los ficheros son cambiados a versiones con indicación de fecha en una carpeta \".stversions\" cuando son reemplazados o borrados por Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Los ficheros son protegidos por los cambios hechos en otros dispositivos, pero los cambios hechos en este dispositivo serán enviados al resto del grupo (cluster).",
"Folder": "Folder",
"Folder": "Carpeta",
"Folder ID": "ID de carpeta",
"Folder Master": "Carpeta principal",
"Folder Path": "Ruta de la carpeta",
@@ -74,12 +75,12 @@
"Global Discovery Server": "Servidor de descubrimiento global",
"Global State": "Estado global",
"Help": "Ayuda",
"Home page": "Home page",
"Home page": "Página de inicio",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrones a ignorar",
"Ignore Permissions": "Permisos a ignorar",
"Incoming Rate Limit (KiB/s)": "Límite de descarga (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configuración incorrecta puede dañar los contenidos de la carpeta y hacer que Syncthing no funcione.",
"Introducer": "Presentador",
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (por ejemplo, \"no excluir\")",
"Keep Versions": "Mantener versiones",
@@ -93,7 +94,7 @@
"Major Upgrade": "Actualización importante",
"Maximum Age": "Edad máxima",
"Metadata Only": "Sólo metadatos",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
"Move to top of queue": "Mover al principio de la cola",
"Multi level wildcard (matches multiple directory levels)": "Comodín multinivel (coincide con múltiples niveles de directorio)",
"Never": "Nunca",
@@ -106,15 +107,15 @@
"OK": "OK",
"Off": "Desconectar",
"Oldest First": "El más antiguo primero",
"Options": "Options",
"Out of Sync": "Out of Sync",
"Options": "Opciones",
"Out of Sync": "No sincronizado",
"Out of Sync Items": "Elementos no sincronizados",
"Outgoing Rate Limit (KiB/s)": "Límite de subida (KiB/s)",
"Override Changes": "Anular cambios",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta a la carpeta en la máquina local. Se creará si no existe. El carácter de la tilde (~) puede usarse como atajo.",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta donde se almacenarán las versiones (dejar vacío para usar la carpeta por defecto \".stversions\").",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Pausar",
"Paused": "Pausado",
"Please consult the release notes before performing a major upgrade.": "Por favor, consultar las notas de la versión antes de realizar una actualización importante.",
"Please wait": "Por favor, espere",
"Preview": "Vista previa",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Guía rápida de patrones soportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatorio",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Respaldada a través",
"Relays": "Respaldos",
"Release Notes": "Notas de la versión",
"Remove": "Remove",
"Remove": "Eliminar",
"Rescan": "Volver a buscar",
"Rescan All": "Volver a buscar todo",
"Rescan Interval": "Intervalo de nueva búsqueda",
"Restart": "Reiniciar",
"Restart Needed": "Reinicio necesario",
"Restarting": "Reiniciando",
"Resume": "Resume",
"Resume": "Continuar",
"Reused": "Reutilizado",
"Save": "Guardar",
"Scanning": "Rastreando",
@@ -157,7 +158,7 @@
"Source Code": "Código fuente",
"Staggered File Versioning": "Versionado escalonado de fichero",
"Start Browser": "Iniciar el navegador",
"Statistics": "Statistics",
"Statistics": "Estadísticas",
"Stopped": "Detenido",
"Support": "Soporte",
"Sync Protocol Listen Addresses": "Direcciones de escucha del protocolo de sincronización",
@@ -180,18 +181,18 @@
"The folder ID must be unique.": "La ID de la carpeta debe ser única.",
"The folder path cannot be blank.": "La ruta de la carpeta no puede estar en blanco.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Se utilizan los siguientes intervalos: para la primera hora se mantiene una versión cada 30 segundos, para el primer día se mantiene una versión cada hora, para los primeros 30 días se mantiene una versión diaria hasta la edad máxima de una semana.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Los siguientes artículos no pueden ser sincronizados.",
"The maximum age must be a number and cannot be blank.": "La edad máxima debe ser un número y no puede estar vacía.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El tiempo máximo para mantener una versión en días (introducir 0 para mantener las versiones indefinidamente).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "El porcentaje de espacio libre mínimo debe ser un número no negativo entre 0 y 100 (ambos inclusive).",
"The number of days must be a number and cannot be blank.": "El número de días debe ser un número y no puede estar en blanco.",
"The number of days to keep files in the trash can. Zero means forever.": "El número de días para mantener los archivos en la papelera. Cero significa \"para siempre\".",
"The number of old versions to keep, per file.": "El número de versiones a antiguas a mantener para cada fichero.",
"The number of versions must be a number and cannot be blank.": "El número de versiones debe ser un número y no puede estar vacío.",
"The path cannot be blank.": "La ruta no puede estar vacía.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "El límite de velocidad debe ser un número no negativo (0: sin límite)",
"The rescan interval must be a non-negative number of seconds.": "El intervalo de actualización debe ser un número positivo de segundos.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "Se reintentarán de forma automática y se sincronizarán cuando se resuelva el error.",
"This is a major version upgrade.": "Hay una actualización importante.",
"Trash Can File Versioning": "Versionado de archivos de la papelera",
"Unknown": "Desconocido",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cuando añada una nueva carpeta, tenga en cuenta que su ID se usa para unir carpetas entre dispositivos. Son sensibles a las mayúsculas y deben coincidir exactamente entre todos los dispositivos.",
"Yes": "Si",
"You must keep at least one version.": "Debes mantener al menos una versión.",
"days": "días",
"full documentation": "Documentación completa",
"items": "Elementos",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) se quiere conectar. ¿Agregar nuevo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Búsqueda",
"Documentation": "Documentación",
"Download Rate": "Tasa de descarga",
"Downloaded": "Descargado",
@@ -74,12 +75,12 @@
"Global Discovery Server": "Servidor global de identificación",
"Global State": "Estado global",
"Help": "Ayuda",
"Home page": "Home page",
"Home page": "Pagina de inicio",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrones de exclusión",
"Ignore Permissions": "Ignorar permisos",
"Incoming Rate Limit (KiB/s)": "Límite de velocidad de entrada (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Configuración incorrecta puede dañar los contenidos de la carpeta y hacer Syncthing inoperable.",
"Introducer": "Introductor",
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (es decir, no excluir)",
"Keep Versions": "Conservar versiones",
@@ -93,7 +94,7 @@
"Major Upgrade": "Actualización mayor",
"Maximum Age": "Edad máxima",
"Metadata Only": "Sólo metadatos",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
"Move to top of queue": "Mover al principio de la cola.",
"Multi level wildcard (matches multiple directory levels)": "Carácter comodín multinivel (coincide en el directorio y sus subdirectorios)",
"Never": "Nunca",
@@ -107,7 +108,7 @@
"Off": "Apagado",
"Oldest First": "Antiguo primero",
"Options": "Opciones",
"Out of Sync": "Out of Sync",
"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",
@@ -122,8 +123,8 @@
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
"RAM Utilization": "Utilización de RAM",
"Random": "Aleatorio",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "retransmitida vía",
"Relays": "Retransmisores",
"Release Notes": "Notas de lanzamiento",
"Remove": "Eliminar",
"Rescan": "Reescanear",
@@ -132,7 +133,7 @@
"Restart": "Reiniciar",
"Restart Needed": "Es necesario reiniciar",
"Restarting": "Reiniciando",
"Resume": "Resume",
"Resume": "Reanudar",
"Reused": "Reutilizado",
"Save": "Guardar",
"Scanning": "Actualización",
@@ -212,6 +213,7 @@
"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.": "Al agregar un nuevo repositorio, tenga en cuenta que la ID del repositorio se utiliza para conectar los repositorios entre dispositivos. Se distingue entre mayúsculas y minúsculas y debe ser exactamente igual en todos los dispositivos.",
"Yes": "Sí",
"You must keep at least one version.": "Debe mantener al menos una versión",
"days": "días",
"full documentation": "documentación completa",
"items": "ítems",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."

View File

@@ -1,5 +1,5 @@
{
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
"A negative number of days doesn't make sense.": "Negatiivinen määrä päiviä ei ole järjellinen.",
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
"API Key": "API-avain",
"About": "Tietoja",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Laite {{device}} ({{address}}) haluaa yhdistää. Lisää uusi laite?",
"Devices": "Laitteet",
"Disconnected": "Yhteys katkaistu",
"Discovery": "Discovery",
"Documentation": "Dokumentaatio",
"Download Rate": "Latausmäärä",
"Downloaded": "Ladattu",
@@ -49,7 +50,7 @@
"Edit Folder": "Muokkaa kansiota",
"Editing": "Muokkaus",
"Enable UPnP": "Ota UPnP käyttöön",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Syötä osoitteet pilkuilla erotettuina (\"tcp://ip:portti, tcp://nimi:portti\") tai \"dynamic\" käyttääksesi osoitteen automaattista selvitystä.",
"Enter ignore patterns, one per line.": "Syötä ohituslausekkeet, yksi riviä kohden.",
"Error": "Virhe",
"External File Versioning": "Ulkoinen tiedostoversionti",
@@ -57,10 +58,10 @@
"File Pull Order": "File Pull Order",
"File Versioning": "Tiedostoversiointi",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Tiedostojen oikeusbitit jätetään huomiotta etsittäessä muutoksia. Käytä FAT-tiedostojärjestelmissä.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Kun Syncthing poistaa tai korvaa tiedostoja, ne siirretään .stversions-hakemistoon.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Tiedostot siirretään päivämäärällä merkityiksi versioiksi .stversions-kansioon, kun Syncthing korvaa tai poistaa ne.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Tiedostot on suojattu muilla laitteilla tehdyiltä muutoksilta, mutta tällä laitteella tehdyt muutokset lähetetään muuhun ryhmään.",
"Folder": "Folder",
"Folder": "Kansio",
"Folder ID": "Kansion ID",
"Folder Master": "Hallitsijakansio",
"Folder Path": "Kansion polku",
@@ -93,7 +94,7 @@
"Major Upgrade": "Major Upgrade",
"Maximum Age": "Maksimi-ikä",
"Metadata Only": "Vain metadata",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Vapaan levytilan vähimmäismäärä",
"Move to top of queue": "Siirrä jonon alkuun",
"Multi level wildcard (matches multiple directory levels)": "Monitasoinen jokerimerkki (vaikuttaa useassa kansiotasossa)",
"Never": "Ei koskaan",
@@ -113,8 +114,8 @@
"Override Changes": "Ohita muutokset",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Polku kansioon paikallisella tietokoneella. Kansio luodaan, ellei sitä ole olemassa. Tilde-merkkiä (~) voidaan käyttää oikotienä polulle",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Polku jonne versiot tullaan tallentamaan (jätä tyhjäksi oletusvalintaa .stversions varten).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Keskeytä",
"Paused": "Keskeytetty",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Please wait": "Ole hyvä ja odota",
"Preview": "Esikatselu",
@@ -123,16 +124,16 @@
"RAM Utilization": "RAM:n käyttö",
"Random": "Satunnaien",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Release Notes": "Release Notes",
"Remove": "Remove",
"Relays": "Välityspalvelimet",
"Release Notes": "Julkaisutiedot",
"Remove": "Poista",
"Rescan": "Skannaa uudelleen",
"Rescan All": "Skannaa kaikki uudelleen",
"Rescan Interval": "Uudelleenskannauksen aikaväli",
"Restart": "Käynnistä uudelleen",
"Restart Needed": "Uudelleenkäynnistys tarvitaan",
"Restarting": "Käynnistetään uudelleen",
"Resume": "Resume",
"Resume": "Jatka",
"Reused": "Uudelleenkäytetty",
"Save": "Tallenna",
"Scanning": "Skannataan",
@@ -153,11 +154,11 @@
"Shutdown Complete": "Sammutus valmis",
"Simple File Versioning": "Yksinkertainen tiedostoversiointi",
"Single level wildcard (matches within a directory only)": "Yksitasoinen jokerimerkki (vaikuttaa vain kyseisen kansion sisällä)",
"Smallest First": "Smallest First",
"Smallest First": "Pienin ensin",
"Source Code": "Lähdekoodi",
"Staggered File Versioning": "Porrastettu tiedostoversiointi",
"Start Browser": "Käynnistä selain",
"Statistics": "Statistics",
"Statistics": "Tilastot",
"Stopped": "Pysäytetty",
"Support": "Tuki",
"Sync Protocol Listen Addresses": "Synkronointiprotokollan kuunteluosoite",
@@ -180,16 +181,16 @@
"The folder ID must be unique.": "Kansion ID:n tulee olla uniikki.",
"The folder path cannot be blank.": "Kansion polku ei voi olla tyhjä.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Seuraavat aikavälit ovat käytössä: ensimmäisen tunnin ajalta uusi versio säilytetään joka 30 sekunti, ensimmäisen päivän ajalta uusi versio säilytetään tunneittain ja ensimmäisen 30 päivän aikana uusi versio säilytetään päivittäin. Lopulta uusi versio säilytetään viikoittain, kunnes maksimi-ikä saavutetaan.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Seuraavia nimikkeitä ei voitu synkronoida.",
"The maximum age must be a number and cannot be blank.": "Maksimi-iän tulee olla numero, eikä se voi olla tyhjä.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimiaika versioiden säilytykseen (päivissä, aseta 0 säilyttääksesi versiot ikuisesti).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Vapaan levytilan vähimmäismäärä prosentteina tulee olla positiivinen luku (suljetulta) väliltä 0-100.",
"The number of days must be a number and cannot be blank.": "Päivien määrän tulee olla numero, eikä se voi olla tyhjä.",
"The number of days to keep files in the trash can. Zero means forever.": "Montako päivää tiedostoja säilytetään roskakorissa. Nolla (0) = ikuisesti.",
"The number of old versions to keep, per file.": "Säilytettävien vanhojen versioiden määrä tiedostoa kohden.",
"The number of versions must be a number and cannot be blank.": "Versioiden määrän rulee olla numero, eikä se voi olla tyhjä.",
"The path cannot be blank.": "Polku ei voi olla tyhjä.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Nopeusrajan tulee olla positiivinen luku tai nolla. (0: ei rajaa)",
"The rescan interval must be a non-negative number of seconds.": "Uudelleenskannauksen aikavälin tulee olla ei-negatiivinen numero sekunteja.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This is a major version upgrade.": "This is a major version upgrade.",
@@ -203,7 +204,7 @@
"Upgrade To {%version%}": "Päivitä versioon {{version}}",
"Upgrading": "Päivitetään",
"Upload Rate": "Lähetysmäärä",
"Uptime": "Uptime",
"Uptime": "Päälläoloaika",
"Use HTTPS for GUI": "Käytä HTTPS:ää GUI:n kanssa",
"Version": "Versio",
"Versions Path": "Versioiden polku",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lisättäessä uutta kansiota, muista että kansion ID:tä käytetään solmimaan kansiot yhteen laitteiden välillä. Ne ovat riippuvaisia kirjankoosta ja niiden tulee täsmätä kaikkien laitteiden välillä.",
"Yes": "Kyllä",
"You must keep at least one version.": "Sinun tulee säilyttää ainakin yksi versio.",
"days": "days",
"full documentation": "täysi dokumentaatio",
"items": "kohteet",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "L'appareil {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette appareil ?",
"Devices": "Appareil",
"Disconnected": "Déconnecté",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
"Downloaded": "Téléchargé",
@@ -212,6 +213,7 @@
"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.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que son ID est utilisé pour lier les répertoires à travers les appareils. Les ID sont sensibles à la casse et doivent être identiques à travers tous les nœuds.",
"Yes": "Oui",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."

View File

@@ -38,8 +38,9 @@
"Device Identification": "Identification de l'appareil",
"Device Name": "Nom du périphérique",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "L'appareil {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette appareil ?",
"Devices": "Appareil",
"Devices": "Appareils",
"Disconnected": "Déconnecté",
"Discovery": "Découverte",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
"Downloaded": "Téléchargé",
@@ -212,6 +213,7 @@
"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.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que son ID est utilisé pour lier les répertoires à travers les appareils. Les ID sont sensibles à la casse et doivent être identiques à travers tous les nœuds.",
"Yes": "Oui",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."

View File

@@ -3,15 +3,15 @@
"A new major version may not be compatible with previous versions.": "Az új főverzió nem kompatibilis az előző főverzióval.",
"API Key": "API kulcs",
"About": "Névjegy",
"Actions": "Tevékenységek",
"Actions": "Műveletek",
"Add": "Hozzáadás",
"Add Device": "Eszköz hozzáadása",
"Add Folder": "Mappa hozzáadása",
"Add new folder?": " ",
"Add new folder?": " Új mappa hozzáadás?",
"Address": "Cím",
"Addresses": "Címek",
"Advanced": "Speciális",
"Advanced Configuration": "Speciális beállítások",
"Advanced": "Haladó",
"Advanced Configuration": "Haladó beállítások",
"All Data": "Minden adat",
"Allow Anonymous Usage Reporting?": "Engedélyezed a névtelen felhasználási adatok küldését?",
"Alphabetic": "ABC rendben",
@@ -31,15 +31,16 @@
"Connection Error": "Kapcsolódási hiba",
"Copied from elsewhere": "Másolva máshonnan",
"Copied from original": "Másolva az eredetiről",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 az alábbi Közreműködők",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 az alábbi Közreműködők:",
"Delete": "Törlés",
"Deleted": "Törölve",
"Device ID": "Eszköz azonosító",
"Device Identification": "Eszköz azonosító",
"Device Name": "Eszköz neve",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne.",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne. Hozzáadod az új eszközt?",
"Devices": "Eszközök",
"Disconnected": "Kapcsolat bontva",
"Discovery": "Felfedezés",
"Documentation": "Dokumentáció",
"Download Rate": "Letöltési sebesség",
"Downloaded": "Letöltve",
@@ -56,7 +57,7 @@
"Failed Items": "Hibás elemek",
"File Pull Order": "Fájl küldési sorrend",
"File Versioning": "Fájl verziózás",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszereken használatakor.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszerek használatakor.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing áthelyezi vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing áthelyezi vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve, időbélyegzővel ellátva.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "A fájlok védve vannak a más eszközökön történt változásokkal szemben, de az ezen az eszközön történt változások érvényesek lesznek a többire.",
@@ -65,7 +66,7 @@
"Folder Master": "Központi mappa",
"Folder Path": "Mappa elérési útja",
"Folders": "Mappák",
"GUI": "Felület",
"GUI": "Grafikus felület",
"GUI Authentication Password": "Grafikus felület jelszava",
"GUI Authentication User": "Grafikus felület felhasználó neve ",
"GUI Listen Addresses": "Grafikus felület címe",
@@ -75,8 +76,8 @@
"Global State": "Globális állapot",
"Help": "Segítség",
"Home page": "Főoldal",
"Ignore": "Visszautasítás",
"Ignore Patterns": "Figyelmen kívül hagyás",
"Ignore": "Figyelmen kívül hagyás",
"Ignore Patterns": "Minták figyelmen kívül hagyása",
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
"Incoming Rate Limit (KiB/s)": "Bejövő sebesség korlát (KIB/mp)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Helytelen konfiguráció esetén károsodhat a mappák tartalma és működésképtelenné válhat a Syncthing.",
@@ -107,23 +108,23 @@
"Off": "Kikapcsolva",
"Oldest First": "Régebbi először",
"Options": "Opciók",
"Out of Sync": "Out of Sync",
"Out of Sync": "Nincs szinkronban",
"Out of Sync Items": "Nem szinkronizált elemek",
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
"Override Changes": "Változtatások felülbírálása",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "A mappa elérési útja az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Elérési út ahol a verziók tárolásra kerülnek (szabadon hagyva az alapértelmezett .stversions mappa lesz használva)",
"Pause": "Pause",
"Paused": "Paused",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Pause": "Szünet",
"Paused": "Szünetel",
"Please consult the release notes before performing a major upgrade.": "Nagyobb frissítés előtt ellenőrizni kell a kiadási megjegyzéseket.",
"Please wait": "Kérlek, várj",
"Preview": "Előnézet",
"Preview Usage Report": "Felhasználási adatok átnézése",
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
"RAM Utilization": "Memória használat",
"Random": "Véletlenszerű",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Közvetítve",
"Relays": "Közvetítések",
"Release Notes": "Kiadási megjegyzések",
"Remove": "Eltávolítás",
"Rescan": "Átnézés",
@@ -132,7 +133,7 @@
"Restart": "Újraindítás",
"Restart Needed": "Újraindítás szükséges",
"Restarting": "Újraindulás",
"Resume": "Resume",
"Resume": "Folytatás",
"Reused": "Újrafelhasználva",
"Save": "Mentés",
"Scanning": "Átnézés",
@@ -150,7 +151,7 @@
"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": "Leállítás",
"Shutdown Complete": "Leállítás kész",
"Simple File Versioning": "Egyszerű fájl verziókövetés",
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
"Smallest First": "Kisebb előbb",
@@ -167,7 +168,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 refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Úgy tűnik, hogy a Syncthing problémába ütközött a kérés feldolgozása során. Ha a probléma továbbra is fennáll, akkor frissíteni kell az oldalt, vagy újra kell indítani a Syncthinget.",
"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.",
@@ -189,9 +190,9 @@
"The number of old versions to keep, per file.": "A megtartott régi verziók száma, fájlonként.",
"The number of versions must be a number and cannot be blank.": "A megtartott verziók száma nem lehet üres",
"The path cannot be blank.": "Elérési út nem lehet üres.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Az arány limitnek pozitív számnak kell lennie (0: nincs limit)",
"The rescan interval must be a non-negative number of seconds.": "Az átnézési intervallum nullánál nagyobb másodperc érték kell legyen",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "A hiba javítása után automatikusan újra megpróbálja a szinkronizálást.",
"This is a major version upgrade.": "Ez egy főverzió frissítés.",
"Trash Can File Versioning": "Szemetes fájl verziózás",
"Unknown": "Ismeretlen",
@@ -204,7 +205,7 @@
"Upgrading": "Frissítés",
"Upload Rate": "Feltöltési sebesség",
"Uptime": "Üzemidő",
"Use HTTPS for GUI": "HTTPS használata a GUI-hoz",
"Use HTTPS for GUI": "HTTPS használata a felülethez",
"Version": "Verzió",
"Versions Path": "Verziók útvonala",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "A régi verziók automatikusan törlődnek, amennyiben öregebbek mint a maximum kor, vagy már több van belőlük mint az adott időszakban megtartható maximum.",
@@ -212,7 +213,8 @@
"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.": "Amikor új mappát adsz hozzá, tartsd észben, hogy a mappa azonosító arra való hogy összekösd a mappákat az eszközeiden. Az azonosító kisbetű-nagybetű érzékeny és pontosan egyeznie kell az eszközökön.",
"Yes": "Igen",
"You must keep at least one version.": "Legalább egy verziót meg kell tartanod",
"days": "nap",
"full documentation": "teljes dokumentáció",
"items": "elemek",
"items": "elem",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} meg szeretné osztani a \"{{folder}}\" nevű mappát."
}

View File

@@ -10,8 +10,8 @@
"Add new folder?": "Aggiungere una nuova cartella?",
"Address": "Indirizzo",
"Addresses": "Indirizzi",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Avanzato",
"Advanced Configuration": "Configurazione avanzata",
"All Data": "Tutti i Dati",
"Allow Anonymous Usage Reporting?": "Abilitare Statistiche Anonime di Utilizzo?",
"Alphabetic": "Alfabetico",
@@ -19,7 +19,7 @@
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
"Any devices configured on an introducer device will be added to this device as well.": "Qualsiasi dispositivo configurato in un introduttore verrà aggiunto anche a questo dispositivo.",
"Automatic upgrades": "Aggiornamenti automatici",
"Be careful!": "Be careful!",
"Be careful!": "Fai attenzione!",
"Bugs": "Bug",
"CPU Utilization": "Utilizzo CPU",
"Changelog": "Changelog",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Il dispositivo {{device}} ({{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
"Devices": "Dispositivi",
"Disconnected": "Disconnesso",
"Discovery": "Individuazione",
"Documentation": "Documentazione",
"Download Rate": "Velocità Download",
"Downloaded": "Scaricato",
@@ -49,23 +50,23 @@
"Edit Folder": "Modifica Cartella",
"Editing": "Modifica di",
"Enable UPnP": "Attiva UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Inserisci indirizzi separati da virgola (\"tcp://ip:porta\", \"tcp://host:porta\") oppure \"dynamic\" per effettuare il rilevamento automatico dell'indirizzo.",
"Enter ignore patterns, one per line.": "Inserisci gli schemi di esclusione, uno per riga.",
"Error": "Errore",
"External File Versioning": "Controllo Versione Esterno",
"Failed Items": "Failed Items",
"Failed Items": "Elementi errati",
"File Pull Order": "Ordine di prelievo dei file",
"File Versioning": "Controllo Versione dei File",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Il software evita i bit dei permessi dei file durante il controllo delle modifiche. Utilizzato nei filesystem FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "I file sono spostati nella certella .stversions quando vengono sostituiti o cancellati da Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "I file sostituiti o eliminati da Syncthing vengono datati e spostati in una cartella .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "I file sono protetti dalle modifiche effettuate negli altri dispositivi, ma le modifiche effettuate in questo dispositivo verranno inviate anche al resto del cluster.",
"Folder": "Folder",
"Folder": "Cartella",
"Folder ID": "ID Cartella",
"Folder Master": "Cartella Principale",
"Folder Path": "Percorso Cartella",
"Folders": "Cartelle",
"GUI": "GUI",
"GUI": "Interfaccia grafica utente",
"GUI Authentication Password": "Password di Autenticazione dell'Utente",
"GUI Authentication User": "Utente dell'Interfaccia Grafica",
"GUI Listen Addresses": "Indirizzi dell'Interfaccia Grafica",
@@ -74,12 +75,12 @@
"Global Discovery Server": "Server di Ricerca Globale",
"Global State": "Stato Globale",
"Help": "Aiuto",
"Home page": "Home page",
"Home page": "Pagina home",
"Ignore": "Ignora",
"Ignore Patterns": "Schemi Esclusione File",
"Ignore Permissions": "Ignora Permessi",
"Incoming Rate Limit (KiB/s)": "Limite Velocità in Ingresso (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configurazione incorretta potrebbe danneggiare il contenuto delle cartelle e rendere Syncthing inoperativo.",
"Introducer": "Introduttore",
"Inversion of the given condition (i.e. do not exclude)": "Inversione della condizione indicata (ad es. non escludere)",
"Keep Versions": "Versioni Mantenute",
@@ -93,7 +94,7 @@
"Major Upgrade": "Aggiornamento principale",
"Maximum Age": "Durata Massima",
"Metadata Only": "Solo i Metadati",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Minimo spazio libero su disco",
"Move to top of queue": "Posiziona in cima alla coda",
"Multi level wildcard (matches multiple directory levels)": "Metacarattere multi-livello (corrisponde alle cartelle e alle sotto-cartelle)",
"Never": "Mai",
@@ -106,15 +107,15 @@
"OK": "OK",
"Off": "Disattiva",
"Oldest First": "Prima il meno recente",
"Options": "Options",
"Out of Sync": "Out of Sync",
"Options": "Opzioni",
"Out of Sync": "Non sincronizzato",
"Out of Sync Items": "Elementi Non Sincronizzati",
"Outgoing Rate Limit (KiB/s)": "Limite Velocità in Uscita (KiB/s)",
"Override Changes": "Ignora Modifiche",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Percorso della cartella nel computer locale. Verrà creata se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Percorso di salvataggio delle versioni (lasciare vuoto per utilizzare la cartella predefinita .stversions in questa cartella).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Pausa",
"Paused": "In pausa",
"Please consult the release notes before performing a major upgrade.": "Si prega di consultare le note di rilascio prima di eseguire un aggiornamento principale.",
"Please wait": "Attendere prego",
"Preview": "Anteprima",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
"RAM Utilization": "Utilizzo RAM",
"Random": "Casuale",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Reindirizzato tramite",
"Relays": "Servers di reindirizzamento",
"Release Notes": "Note di rilascio",
"Remove": "Remove",
"Remove": "Rimuovi",
"Rescan": "Riscansiona",
"Rescan All": "Riscansiona Tutto",
"Rescan Interval": "Intervallo Scansione",
"Restart": "Riavvia",
"Restart Needed": "Riavvio Necessario",
"Restarting": "Riavvio",
"Resume": "Resume",
"Resume": "Riprendi",
"Reused": "Riutilizzato",
"Save": "Salva",
"Scanning": "Scansione in corso",
@@ -157,7 +158,7 @@
"Source Code": "Codice Sorgente",
"Staggered File Versioning": "Controllo Versione Cadenzato",
"Start Browser": "Avvia Browser",
"Statistics": "Statistics",
"Statistics": "Statistiche",
"Stopped": "Fermato",
"Support": "Supporto",
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
@@ -180,18 +181,18 @@
"The folder ID must be unique.": "L'ID della cartella dev'essere unico.",
"The folder path cannot be blank.": "Il percorso della cartella non può essere vuoto.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Vengono utilizzati i seguenti intervalli temporali: per la prima ora viene mantenuta una versione ogni 30 secondi, per il primo giorno viene mantenuta una versione ogni ora, per i primi 30 giorni viene mantenuta una versione al giorno, successivamente viene mantenuta una versione ogni settimana fino al periodo massimo impostato.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Non è stato possibile sincronizzare i seguenti elementi",
"The maximum age must be a number and cannot be blank.": "La durata massima dev'essere un numero e non può essere vuoto.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La durata massima di una versione (in giorni, imposta a 0 per mantenere le versioni per sempre).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Lo spazio libero minimo su disco deve essere un numero non negativo tra 0 e 100 (inclusi)",
"The number of days must be a number and cannot be blank.": "Il numero di giorni deve essere un numero e non può essere vuoto.",
"The number of days to keep files in the trash can. Zero means forever.": "Il numero di giorni per conservare i file nel cestino. Zero significa per sempre.",
"The number of old versions to keep, per file.": "Il numero di vecchie versioni da mantenere, per file.",
"The number of versions must be a number and cannot be blank.": "Il numero di versioni dev'essere un numero e non può essere vuoto.",
"The path cannot be blank.": "Il percorso non può essere vuoto.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Il limite di banda deve essere un numero non negativo (da 0 a infinito)",
"The rescan interval must be a non-negative number of seconds.": "L'intervallo di scansione deve essere un numero superiore a zero secondi.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "Verranno effettuati tentativi in automatico e verranno sincronizzati quando l'errore sarà risolto.",
"This is a major version upgrade.": "Questo è un aggiornamento di versione principale",
"Trash Can File Versioning": "Controllo Versione con Cestino",
"Unknown": "Sconosciuto",
@@ -212,6 +213,7 @@
"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.": "Quando aggiungi una nuova cartella, ricordati che gli ID vengono utilizzati per collegare le cartelle nei dispositivi. Distinguono maiuscole e minuscole e devono corrispondere esattamente su tutti i dispositivi.",
"Yes": "Sì",
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
"days": "giorni",
"full documentation": "documentazione completa",
"items": "elementi",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vuole condividere la cartella \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "デバイス {{device}} ({{address}}) が接続を求めています。新しいデバイスとして追加しますか?",
"Devices": "デバイス",
"Disconnected": "切断中",
"Discovery": "Discovery",
"Documentation": "マニュアル",
"Download Rate": "ダウンロード速度",
"Downloaded": "ダウンロード済",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "新しいフォルダーを追加する際、フォルダーIDはデバイス間でフォルダーの対応づけに使われることに注意してください。フォルダーIDは大文字と小文字が区別され、共有するすべてのデバイスの間で完全に一致しなくてはなりません。",
"Yes": "はい",
"You must keep at least one version.": "少なくとも一つのバージョンを保存してください。",
"days": "日",
"full documentation": "詳細なマニュアル",
"items": "項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。"

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
"Devices": "기기",
"Disconnected": "연결 끊김",
"Discovery": "Discovery",
"Documentation": "문서",
"Download Rate": "다운로드 속도",
"Downloaded": "다운로드됨",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
"Yes": "예",
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
"days": "days",
"full documentation": "전체 문서",
"items": "항목",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다."

View File

@@ -10,8 +10,8 @@
"Add new folder?": "Pridėti naują aplanką?",
"Address": "Adresas",
"Addresses": "Adresai",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Pažangus",
"Advanced Configuration": "Pažangus nustatymai",
"All Data": "Visiems duomenims",
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
"Alphabetic": "Abėcėlės tvarka",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Įrenginys {{device}} ({{address}}) nori prisijungti. Pridėti naują įrenginį?",
"Devices": "Įrenginiai",
"Disconnected": "Atsijungęs",
"Discovery": "Lokacija",
"Documentation": "Aprašymas",
"Download Rate": "Parsisiuntimo greitis",
"Downloaded": "Parsisiųstas",
@@ -53,19 +54,19 @@
"Enter ignore patterns, one per line.": "Suveskite nepaisomus šablonus, kiekvieną naujoje eilutėje.",
"Error": "Klaida",
"External File Versioning": "Išorinis versijų valdymas",
"Failed Items": "Failed Items",
"Failed Items": "Nepavykę siuntimai",
"File Pull Order": "Failų siuntimo tvarka",
"File Versioning": "Versijų valdymas",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Ieškant pakeitimų, į failų leidimų bitus yra nekreipiama dėmesio. Naudoti FAT failų sistemose.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Failai perkeliami į .stversions aplanką kai tampa pakeisti arba ištrinti.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Programai Syncthing pakeičiant ar ištrinant failus, jie yra perkeliami į datomis pažymėtas versijas, aplanke .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Failai apsaugoti nuo pakeitimų atliktų kituose įrenginiuose, bet pakeitimai šiame įrenginyje bus nusiųsti kitiems.",
"Folder": "Folder",
"Folder": "Aplankas",
"Folder ID": "Aplanko ID",
"Folder Master": "Aplanko vadovas",
"Folder Path": "Kelias iki apkanko",
"Folders": "Aplankai",
"GUI": "GUI",
"GUI": "Valdymo skydelis",
"GUI Authentication Password": "Valdymo skydelio slaptažodis",
"GUI Authentication User": "Valdymo skydelio vartotojo vardas",
"GUI Listen Addresses": "Valdymo skydelio adresas",
@@ -74,7 +75,7 @@
"Global Discovery Server": "Visuotinio matomumo serveris",
"Global State": "Visuotinė būsena",
"Help": "Pagalba",
"Home page": "Home page",
"Home page": "Pagrindinis puslapis",
"Ignore": "Ignoruoti",
"Ignore Patterns": "Nepaisyti šablonų",
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
@@ -113,8 +114,8 @@
"Override Changes": "Perrašyti pakeitimus",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Kelias iki aplanko šiame kompiuteryje. Bus sukurtas, jei neegzistuoja. Tildės simbolis (~) gali būti naudojamas kaip trumpinys",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kelias, kur bus saugomos versijos (palikite tuščią numatytam .stversions aplankui).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Sustabdyti",
"Paused": "Sustabdyta",
"Please consult the release notes before performing a major upgrade.": "Peržvelkite laidos informaciją prieš atlikdami stambų atnaujinimą.",
"Please wait": "Prašome palaukti",
"Preview": "Peržiūra",
@@ -122,8 +123,8 @@
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
"RAM Utilization": "Atminties naudojimas",
"Random": "Atsitiktinė",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Retransliuojama per",
"Relays": "Retransliatoriai",
"Release Notes": "Laidos Informacija",
"Remove": "Pašalinti",
"Rescan": "Nuskaityti iš naujo",
@@ -132,7 +133,7 @@
"Restart": "Perleisti",
"Restart Needed": "Reikalingas perleidimas",
"Restarting": "Persileidžia",
"Resume": "Resume",
"Resume": "Pratęsti",
"Reused": "Pakartotinas",
"Save": "Išsaugoti",
"Scanning": "Skenuojama",
@@ -180,7 +181,7 @@
"The folder ID must be unique.": "Aplanko ID turi būti unikalus.",
"The folder path cannot be blank.": "Kelias iki aplanko negali būti tuščias.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Šie pertraukų nustatymai naudojami: pirmą valandą versijos laikomos 30 sekundžių, pirmą dieną versijos laikomos valandą, pirmas 30 dienų versijos laikomos parą, kol nebus viršytas nustatytas maksimalus amžius.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Nepavyko parsiųsti šių failų",
"The maximum age must be a number and cannot be blank.": "Maksimalus amžius turi būti skaitmuo ir negali būti tuščias laukelis.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimalus laikas kurį bus saugojama versija (dienomis, nustatykite 0 norėdami saugoti amžinai).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
@@ -189,9 +190,9 @@
"The number of old versions to keep, per file.": "Kiek failo versijų saugoti.",
"The number of versions must be a number and cannot be blank.": "Versijų skaičius turi būti skaitmuo ir negali būti tuščias laukelis.",
"The path cannot be blank.": "Kelias negali būti tuščias.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Srauto maksimalus greitis privalo būti ne neigiamas skaičius (0: nėra apribojimo)",
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "Failus bus automatiškai badoma parsiųsti dar kartą kai išspręsite klaidas",
"This is a major version upgrade.": "Tai yra stambus atnaujinimas.",
"Trash Can File Versioning": "Šiukšliadėžės versijų valdymas",
"Unknown": "Nežinoma",
@@ -212,6 +213,7 @@
"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.": "Kai įvedate naują aplanką neužmirškite, kad jis bus naudojamas visuose įrenginiuose. Svarbu visur įvesti visiškai tokį pat aplanko vardą neužmirštant apie didžiąsias ir mažąsias raides.",
"Yes": "Taip",
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
"days": "dienos",
"full documentation": "pilna dokumentacija",
"items": "įrašai",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\""

View File

@@ -1,6 +1,6 @@
{
"A negative number of days doesn't make sense.": "Et negativt antall dager gir ikke mening.",
"A new major version may not be compatible with previous versions.": "En ny hovedversjon kan bli ikke-kompatibel med en eldre versjon.",
"A new major version may not be compatible with previous versions.": "En ny hovedversjon er ikke nødvendigvis kompatibel med eldre versjoner.",
"API Key": "API-nøkkel",
"About": "Om",
"Actions": "Handlinger",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enhet {{device}} ({{address}}) ønsker å koble seg til. Legg til ny enhet?",
"Devices": "Enheter",
"Disconnected": "Frakoblet",
"Discovery": "Oppdagelse",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Lastet ned",
@@ -56,8 +57,8 @@
"Failed Items": "Elementsynkronisering som har feilet",
"File Pull Order": "Filenes Henterekkefølge",
"File Versioning": "Versjonskontroll",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fil bit-rettigheter ignoreres når forandringer oppdages. Bruk FAT filsystem. ",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer som slettes eller erstattes av Syncthing flyttes til katalogen .stversions",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Informasjon om filrettigheter ignoreres når det letes etter endringer. Bruk FAT filsystem. ",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer som slettes eller erstattes av Syncthing flyttes til katalogen .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til en datostemplet versjon i .stversions-katalogen når den oppdateres eller slettes av Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskyttet mot endringer som er gjort på andre enheter, men endringer som er gjort på denne enheten blir sendt til resten av gruppen.",
"Folder": "Katalog",
@@ -79,7 +80,7 @@
"Ignore Patterns": "Utelatelsesmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkommende Hastighetsbegrensning (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Feilaktige innstillinger kan skade innholdet i dine delte kataloger og hindre Syncthing i å fungere. ",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Feilaktige innstillinger kan skade innholdet i dine delte kataloger og hindre Syncthing i å fungere.",
"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",
@@ -94,7 +95,7 @@
"Maximum Age": "Maksimal Levetid",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Nødvendig ledig diskplass",
"Move to top of queue": "Flytt til topp av kø",
"Move to top of queue": "Flytt fremst ien",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
"New Device": "Ny Enhet",
@@ -122,8 +123,8 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "RAM-utnyttelse",
"Random": "Tilfeldig",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Relé via",
"Relays": "Reléer",
"Release Notes": "Utgivelsesnotat",
"Remove": "Fjern",
"Rescan": "Skann på nytt",
@@ -193,7 +194,7 @@
"The rescan interval must be a non-negative number of seconds.": "Antall sekund i skanneintervallet kan ikke være negativt.",
"They are retried automatically and will be synced when the error is resolved.": "Disse hentes automatisk og vil synkroniseres når feilen er blitt utbedret.",
"This is a major version upgrade.": "Dette er en hovedoppgradering",
"Trash Can File Versioning": "Fil Versjonskontroll i papirkurven",
"Trash Can File Versioning": "Papirkurv Versjonskontroll",
"Unknown": "Ukjent",
"Unshared": "Ikke delt",
"Unused": "Ikke i bruk",
@@ -212,6 +213,7 @@
"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.": "Når en ny mappe blir lagt til, husk at Mappe-ID blir brukt til å binde sammen mapper mellom enheter. Det er forskjell på store og små bokstaver, så IDene må være identiske på alle enhetene.",
"Yes": "Ja",
"You must keep at least one version.": "Du må beholde minst én versjon",
"days": "dager",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Apparaat {{device}} ({{address}}) wil verbinding maken. Dit apparaat toevoegen?",
"Devices": "Apparaten",
"Disconnected": "Niet verbonden",
"Discovery": "Zoeken",
"Documentation": "Documentatie",
"Download Rate": "Downloadsnelheid",
"Downloaded": "Gedownload",
@@ -49,7 +50,7 @@
"Edit Folder": "Bewerk map",
"Editing": "Bezig met bewerken",
"Enable UPnP": "UPnP gebruiken",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen of \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen in of voer \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
"Enter ignore patterns, one per line.": "Voer negeerpatronen in, één per regel.",
"Error": "Fout",
"External File Versioning": "Extern versiebeheer voor bestanden",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Houd er bij het toevoegen van nieuwe mappen rekening mee dat het map-ID gebruikt wordt om mappen tussen apparaten te verbinden. Dit ID is hoofdlettergevoelig en moet identiek zijn op andere apparaten.",
"Yes": "Ja",
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
"days": "dagen",
"full documentation": "volledige documentatie",
"items": "objecten",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de map \"{{folder}}\" delen."

View File

@@ -1,6 +1,6 @@
{
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
"A negative number of days doesn't make sense.": "Eit negativt tal dagar har ikkje meining.",
"A new major version may not be compatible with previous versions.": "Ein ny hovudversjon er ikkje nødvendigvis kompatibel med eldre versjonar. ",
"API Key": "API-nøkkel",
"About": "Om",
"Actions": "Handlingar",
@@ -15,7 +15,7 @@
"All Data": "Alle data",
"Allow Anonymous Usage Reporting?": "Tillata anonymisert bruksrapportering?",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein ekstern kommando håndterer filutgåver. Den må sørge for at fila blir fjerna frå den synkroniserte mappa.",
"Anonymous Usage Reporting": "Anonymisert bruksrapportering",
"Any devices configured on an introducer device will be added to this device as well.": "Einingar konfigurert på ei introduksjonseining vil òg verta lagt til denne eininga.",
"Automatic upgrades": "Automatiske oppdateringar",
@@ -23,7 +23,7 @@
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnytting",
"Changelog": "Endringslogg",
"Clean out after": "Clean out after",
"Clean out after": "Tøm etter",
"Close": "Lukk",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, når brukt i starten av linja",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Eininga {{device}} ({{address}}) vil kopla seg til. Vil du leggja ho til?",
"Devices": "Einingar",
"Disconnected": "Fråkopla",
"Discovery": "Oppdaging",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsfart",
"Downloaded": "Lasta ned",
@@ -53,12 +54,12 @@
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelatast, eitt per linje.",
"Error": "Feilmelding",
"External File Versioning": "Ekstern filutgåvehandtering",
"Failed Items": "Failed Items",
"File Pull Order": "File Pull Order",
"Failed Items": "Feilande element",
"File Pull Order": "Henterekkefølge for filer",
"File Versioning": "Filutgåvekontroll",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Informasjon om filrettar vert ignorert når det blir leita etter endringar. Bruk på FAT filsystem. ",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer som Syncthing slettar eller skriv over vert flytta til katalogen .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer som Syncthing oppdaterar eller sletter vert flytta til ein datostempla versjon i .stversions-katalogen.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskytta mot endringar gjort på andre einingar, men endringar gjort på denne eininga vert sende til resten av klyngja.",
"Folder": "Mappe",
"Folder ID": "Mappe ID",
@@ -79,7 +80,7 @@
"Ignore Patterns": "Utelatingsmønster",
"Ignore Permissions": "Ignorer tilgangar",
"Incoming Rate Limit (KiB/s)": "Innkomande hastigheitsgrense (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Feil innstillingar kan skade innhaldet i dine delte katalogar og hindre Syncthing i å fungere.",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Det motsette av den gitte tilstanden (dvs. ekskluder ikkje)",
"Keep Versions": "Behald Versjonar",
@@ -89,11 +90,11 @@
"Later": "Seinare",
"Local Discovery": "Lokal oppdaging",
"Local State": "Lokal Tilstand",
"Local State (Total)": "Local State (Total)",
"Major Upgrade": "Major Upgrade",
"Local State (Total)": "Lokal tilstand (total)",
"Major Upgrade": "Hovudoppgradering",
"Maximum Age": "Maksimal Levetid",
"Metadata Only": "Berre metadata",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Naudsynt ledig diskplass",
"Move to top of queue": "Flytt øvst i køen",
"Multi level wildcard (matches multiple directory levels)": "Fleirnivå-jokerteikn (søkjer på fleire mappenivå)",
"Never": "Aldri",
@@ -115,15 +116,15 @@
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringa for lagra versjonar (la denne vera tom for å bruka standard .stversions-mappa i mappa).",
"Pause": "Stans",
"Paused": "Stansa",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Please consult the release notes before performing a major upgrade.": "Sjå \"release notes\" før ei hovudoppgradering vert utført.",
"Please wait": "Gjer vel og vent",
"Preview": "Førehandsvisning",
"Preview Usage Report": "Førehandsvis bruksrapporten",
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "Minnebruk",
"Random": "Tilfeldig",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Relé via",
"Relays": "Reléer",
"Release Notes": "Utgivingsnotat",
"Remove": "Fjern",
"Rescan": "Skann På Ny",
@@ -167,7 +168,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 refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ser ut til å ha støtt på eit problem under behandling av din førespurnad. Vær vennleg å oppfrisk nettlesaren eller start Syncthing på nytt om problemet vedvarer.",
"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.",
@@ -183,17 +184,17 @@
"The following items could not be synchronized.": "Fyljande filer kunne ikkje synkroniserast.",
"The maximum age must be a number and cannot be blank.": "Maksimal levetid må vera eit tal og kan ikkje vera tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Høgaste tidsrom å behalda ei utgåve (i dagar, set til 0 for å behalda versjonane for alltid).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Nødvendig ledig diskplass må vere eit tal mellom 0 og 100.",
"The number of days must be a number and cannot be blank.": "Tal på dagar må ver eit tal og kan ikkje vera tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
"The number of days to keep files in the trash can. Zero means forever.": "Antall dagar å behalda filer i søppelkorga. Null betyr for alltid.",
"The number of old versions to keep, per file.": "Tal på gamle versjonar ein skal behalda, per fil.",
"The number of versions must be a number and cannot be blank.": "Tal på versjonar må vera eit tal og kan ikkje vera tomt.",
"The path cannot be blank.": "Bana kan ikkje vera tom.",
"The rate limit must be a non-negative number (0: no limit)": "Hastigheitsgrensa må ver eit positivt tall (0: ingen grensa)",
"The rescan interval must be a non-negative number of seconds.": "Talet på sekund i skanneintervallet kan ikkje vera negativt.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This is a major version upgrade.": "This is a major version upgrade.",
"Trash Can File Versioning": "Trash Can File Versioning",
"They are retried automatically and will be synced when the error is resolved.": "Desse vil bli prøvd på nytt automatisk og vil bli synkronisert når feilen har blitt utbetra.",
"This is a major version upgrade.": "Dette er ei hovudoppgradering",
"Trash Can File Versioning": "Papirkorg filutgåvehandtering",
"Unknown": "Ukjent",
"Unshared": "Ikkje delt",
"Unused": "Ubrukt",
@@ -212,6 +213,7 @@
"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.": "Hugs at når ei ny mappe vert lagt til, vert mappe-ID-en brukt til å binda saman mappene mellom einingane. Det er skilnad på store og små bokstavar, så ID-ane må vera identiske på alle einingane.",
"Yes": "Ja",
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"days": "dagar",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønskjer å dela mappa \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Urządzenie {{device}} ({{address}}) chce się połączyć. Zezwolić?",
"Devices": "Urządzenia",
"Disconnected": "Rozłączony",
"Discovery": "Odnajdywanie",
"Documentation": "Dokumentacja",
"Download Rate": "Prędkość pobierania",
"Downloaded": "Pobrane",
@@ -49,7 +50,7 @@
"Edit Folder": "Edytuj folder",
"Editing": "Edytowanie",
"Enable UPnP": "Włącz UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Wpisz oddzielone przecinkiem adresy (\"tcp://ip:port\", \"tcp://host:port\") lub \"dynamic\" by przeprowadzić automatyczne odnalezienie adresu.",
"Enter ignore patterns, one per line.": "Wprowadz wzorce ignorowania, jeden w każdej linii.",
"Error": "Błąd",
"External File Versioning": "Zewnętrzne wersjonowanie pliku",
@@ -93,7 +94,7 @@
"Major Upgrade": "Ważna aktualizacja",
"Maximum Age": "Maksymalny wiek",
"Metadata Only": "Tylko metadane",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Minimum wolnego miejsca na dysku",
"Move to top of queue": "Przenieś na początek kolejki",
"Multi level wildcard (matches multiple directory levels)": "Wieloznaczność na poziomie katalogów i plików (uwzględnia nazwy folderów i plików)",
"Never": "Nigdy",
@@ -107,14 +108,14 @@
"Off": "Wyłącz",
"Oldest First": "Najstarsze na początku",
"Options": "Opcje",
"Out of Sync": "Utracono synchronizację",
"Out of Sync": "Niezsynchronizowane",
"Out of Sync Items": "Niezsynchronizowane pliki",
"Outgoing Rate Limit (KiB/s)": "Ograniczenie prędkości wysyłania (KiB/s)",
"Override Changes": "Nadpisz zmiany",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ścieżka gdzie będą przechowywane wersje (pozostaw puste dla domyślnego folderu .stversions)",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Zatrzymaj",
"Paused": "Zatrzymany",
"Please consult the release notes before performing a major upgrade.": "Zaleca się przeanalizowanie \"release notes\" przed przeprowadzeniem znaczącej aktualizacji.",
"Please wait": "Proszę czekać",
"Preview": "Podgląd",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"RAM Utilization": "Użycie pamięci RAM",
"Random": "Losowo",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Przekazane przez",
"Relays": "Przekaźniki",
"Release Notes": "Informacje o wydaniu",
"Remove": "Remove",
"Remove": "Usuń",
"Rescan": "Skanuj ponownie",
"Rescan All": "Skanuj wszystko ponownie",
"Rescan Interval": "Interwał skanowania",
"Restart": "Uruchom ponownie",
"Restart Needed": "Wymagane ponowne uruchomienie",
"Restarting": "Uruchamianie ponowne",
"Resume": "Resume",
"Resume": "Wznów",
"Reused": "Ponownie użyte",
"Save": "Zapisz",
"Scanning": "Skanowanie",
@@ -183,13 +184,13 @@
"The following items could not be synchronized.": "Następujące elementy nie mogły zostać zsynchronizowane.",
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być liczbą i nie może być pusty.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Procent minimalnej ilości wolnego miejsca na dysku musi być nieujemną liczbą od 0 do 100 (włącznie).",
"The number of days must be a number and cannot be blank.": "Ilość dni musi być dodatnia.",
"The number of days to keep files in the trash can. Zero means forever.": "Liczba dni przez które pliki trzymane będą w koszu. Zero oznacza brak ograniczeń.",
"The number of old versions to keep, per file.": "Liczba wersji pliku do zachowania.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być liczbą i nie może być pusta.",
"The path cannot be blank.": "Ścieżka nie może być pusta.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Ograniczenie prędkości powinno być nieujemną liczbą całkowitą (0: brak ograniczeń)",
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie, synchronizacja nastąpi po usunięciu usterki.",
"This is a major version upgrade.": "To jest ważna aktualizacja",
@@ -212,6 +213,7 @@
"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.": "Przy dodawaniu nowego folderu, pamiętaj, że ID użyte jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ciągu ma znaczenie musi zgadzać się na wszystkich urządzeniach.",
"Yes": "Tak",
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
"days": "dni",
"full documentation": "pełna dokumentacja",
"items": "pozycji",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\""

View File

@@ -37,9 +37,10 @@
"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?": "Dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Descoberta",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
@@ -49,7 +50,7 @@
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Enable UPnP": "Habilitar UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática de endereço.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática do endereço.",
"Enter ignore patterns, one per line.": "Insira os padrões de exclusão, um por linha.",
"Error": "Erro",
"External File Versioning": "Versionamento externo de arquivo",
@@ -57,9 +58,9 @@
"File Pull Order": "Ordem de retirada do arquivo",
"File Versioning": "Versionamento de arquivos",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Os bits de permissão de um arquivo são ignorados durante as verificações. Use em sistemas de arquivo FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são motivos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando são substituídos ou removidos pelo Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são movidos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions após serem substituídos ou removidos pelo Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto dos dispositivos.",
"Folder": "Pasta",
"Folder ID": "ID da pasta",
"Folder Master": "Pasta mestre",
@@ -108,7 +109,7 @@
"Oldest First": "Mais antigo primeiro",
"Options": "Opções",
"Out of Sync": "Fora de sincronia",
"Out of Sync Items": "Itens fora de sincronia",
"Out of Sync Items": "Fora de sincronia",
"Outgoing Rate Limit (KiB/s)": "Limite de velocidade de envio (KiB/s)",
"Override Changes": "Sobrescrever mudanças",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Caminho para a pasta na máquina local. Será criado caso não exista. O caractere til (~) pode ser usado como um atalho para",
@@ -140,14 +141,14 @@
"Select the folders to share with this device.": "Selecione as pastas a serem compartilhadas com este dispositivo.",
"Settings": "Configurações",
"Share": "Compartilhar",
"Share Folder": "Compartilhar Pasta",
"Share Folder": "Compartilhar pasta",
"Share Folders With Device": "Compartilhar pastas com o dispositivo",
"Share With Devices": "Compartilhar com os dispositivos",
"Share this folder?": "Compartilhar esta pasta?",
"Shared With": "Compartilhada com",
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curto para a pasta. Deve ser igual em todos os dispositivos.",
"Show ID": "Mostrar ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado no lugar 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 advertised to other devices as an optional default name.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome opcional e pré-definido.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será atualizado para o nome que o dispositivo divulga, caso seja deixado em branco.",
"Shutdown": "Desligar",
"Shutdown Complete": "Desligamento completado",
@@ -172,11 +173,11 @@
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi salva mas ainda não foi ativada. O Syncthing precisa ser reiniciado para a ativação da nova configuração.",
"The device ID cannot be blank.": "O ID de dispositivo não pode ficar vazio.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a ser inserido aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífens são opcionais (ignorados).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório de uso é criptografado e enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "O ID de dispositivo inserido não parece ser válido. Ele deve ter entre 52 e 56 caracteres e ser composto de letras e números, com espaços e hífens opcionais.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "O primeiro argumento da linha de comando é o caminho da pasta e o segundo é o caminho relativo à pasta.",
"The folder ID cannot be blank.": "O ID da pasta não pode ficar vazio.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos), composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos) e composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
"The folder ID must be unique.": "O ID da pasta deve ser único.",
"The folder path cannot be blank.": "O caminho da pasta não pode ficar vazio.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
@@ -184,7 +185,7 @@
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico. O campo não pode ficar vazio.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "O número máximo de dias em que uma versão é guardada. (Use 0 para manter para sempre).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "A porcentagem de espaço livre mínimo no disco deve ser um número positivo entre 0 e 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "O número de dias deve ser um número valido e não pode ficar em branco.",
"The number of days must be a number and cannot be blank.": "O número de dias deve ser um número válido e não pode ficar em branco.",
"The number of days to keep files in the trash can. Zero means forever.": "O número de dias em que são mantidos os arquivos da lixeira. Zero significa para sempre.",
"The number of old versions to keep, per file.": "O número de versões antigas a serem mantidas, por arquivo.",
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numérico. O campo não pode ficar vazio.",
@@ -197,7 +198,7 @@
"Unknown": "Desconhecida",
"Unshared": "Não compartilhada",
"Unused": "Não utilizado",
"Up to Date": "Sincronizada",
"Up to Date": "Em sincronia",
"Updated": "Atualizado",
"Upgrade": "Atualização",
"Upgrade To {%version%}": "Atualizar para {{version}}",
@@ -209,9 +210,10 @@
"Versions Path": "Caminho das versões",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "As versões são automaticamente apagadas se elas são mais antigas do que a idade máxima ou excederem o número de arquivos permitido em um intervalo.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando estiver adicionando um dispositivo, lembre-se de que este dispositivo deve ser adicionado do outro lado também.",
"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.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e devem ser iguais em todos os dispositivos.",
"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.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e deve ser o mesmo em todos os dispositivos.",
"Yes": "Sim",
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
"days": "dias",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer conectar-se. Adiciono este novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Detecção",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
@@ -49,7 +50,7 @@
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Enable UPnP": "Activar UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza endereços separados por vírgulas (\"tcp://ip:porto\", \"tcp://máquina:porto\") ou \"dynamic\" para descobrir automaticamente os endereços.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza endereços separados por vírgulas (\"tcp://ip:porto\", \"tcp://máquina:porto\") ou \"dynamic\" para detectar automaticamente os endereços.",
"Enter ignore patterns, one per line.": "Escreva os padrões de exclusão, um por linha.",
"Error": "Erro",
"External File Versioning": "Externa",
@@ -70,8 +71,8 @@
"GUI Authentication User": "Utilizador da autenticação na interface gráfica",
"GUI Listen Addresses": "Endereço de escuta da interface gráfica",
"Generate": "Gerar",
"Global Discovery": "Busca global",
"Global Discovery Server": "Servidor da busca global",
"Global Discovery": "Detecção global",
"Global Discovery Server": "Servidor de detecção global",
"Global State": "Estado global",
"Help": "Ajuda",
"Home page": "Página do projecto",
@@ -87,7 +88,7 @@
"Last File Received": "Último ficheiro recebido",
"Last seen": "Última vez que foi verificado",
"Later": "Mais tarde",
"Local Discovery": "Busca local",
"Local Discovery": "Detecção local",
"Local State": "Estado local",
"Local State (Total)": "Estado local (total)",
"Major Upgrade": "Actualização importante",
@@ -122,8 +123,8 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Utilização da RAM",
"Random": "Aleatória",
"Relayed via": "Transmitido via",
"Relays": "Transmissores",
"Relayed via": "Retransmitido via",
"Relays": "Retransmissores",
"Release Notes": "Notas de lançamento",
"Remove": "Remover",
"Rescan": "Verificar agora",
@@ -212,6 +213,7 @@
"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.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar as pastas entre dispositivos. É sensível às diferenças entre maiúsculas e minúsculas e tem que ter uma correspondência perfeita entre todos os dispositivos.",
"Yes": "Sim",
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
"days": "dias",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\"."

View File

@@ -11,7 +11,7 @@
"Address": "Adresă",
"Addresses": "Adrese",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced Configuration": "Configurari avansate",
"All Data": "Toate Datele",
"Allow Anonymous Usage Reporting?": "Permiteţi raportarea anonimă de folosire a aplicaţiei?",
"Alphabetic": "Alphabetic",
@@ -19,7 +19,7 @@
"Anonymous Usage Reporting": "Raport Anonim despre Folosirea Aplicației",
"Any devices configured on an introducer device will be added to this device as well.": "Toate dispozitivele configurate pe un dispozitiv iniţiator vor fi adăugate şi pe acest dispozitiv. ",
"Automatic upgrades": "Actualizare automată",
"Be careful!": "Be careful!",
"Be careful!": "Fii atent!",
"Bugs": "Bug-uri",
"CPU Utilization": "CPU ",
"Changelog": "Noutăți",
@@ -33,13 +33,14 @@
"Copied from original": "Copiat din original",
"Copyright © 2015 the following Contributors:": "Copyright ©2015 Următorii Contribuitori:",
"Delete": "Şterge",
"Deleted": "Deleted",
"Deleted": "Șters",
"Device ID": "ID Dispozitiv",
"Device Identification": "Identificare Dispozitiv",
"Device Name": "Nume Dispozitiv",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Dispozitivul{{dispoztiv}}({{adresă}})vrea sa se conecteze.Adaug un dispozitiv nou?",
"Devices": "Dispozitiv",
"Disconnected": "Deconectat",
"Discovery": "Discovery",
"Documentation": "Documentaţie",
"Download Rate": "Viteză de Descărcare",
"Downloaded": "Descărcat",
@@ -60,7 +61,7 @@
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Documentele sînt mutate într-un fișier .stversions conținînd versiuni datate atunci cînd sînt șterse sau înlocuite de Syncthing. ",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Fișierele sunt protejate de schimbările făcute pe alte dispozitive dar schimbările efectuate pe acest dispozitiv vor fi trimise catre restul grupului.",
"Folder": "Folder",
"Folder": "Mapă",
"Folder ID": "ID Mapă",
"Folder Master": "Master Măpi",
"Folder Path": "Locaţie Mapei",
@@ -106,14 +107,14 @@
"OK": "OK",
"Off": "Închis",
"Oldest First": "Oldest First",
"Options": "Options",
"Options": "Opțiuni",
"Out of Sync": "Out of Sync",
"Out of Sync Items": "Elemente Nesincronizate",
"Outgoing Rate Limit (KiB/s)": "Limită Viteză de Upload (KB/s)",
"Override Changes": "Suprascrie Schimbări",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Localizarea fișierului în acest computer. Dacă nu există, va fi creat. Tilda (~) înlocuiește ",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Locul unde vor fi stocate versiunile (a se lăsa neschimbat pentru fișierul .stversions din fișier). ",
"Pause": "Pause",
"Pause": "Pau",
"Paused": "Paused",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Please wait": "Aşteaptă",
@@ -157,7 +158,7 @@
"Source Code": "Cod Sursă",
"Staggered File Versioning": "Versiuni eşalonate ale documentelor",
"Start Browser": "Lansează Browser",
"Statistics": "Statistics",
"Statistics": "Statistici",
"Stopped": "Oprit",
"Support": "Suport Tehnic",
"Sync Protocol Listen Addresses": "Adresa protocolului de sincronizare",
@@ -185,7 +186,7 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Câte zile să se păstreze o versiune (setează 0 pentru nelimitat)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
"The number of days to keep files in the trash can. Zero means forever.": "Numărul de zile pentru a păstra fișierele in urnă.Zero înseamnă permanent.",
"The number of old versions to keep, per file.": "Numărul de versiuni vechi de salvat per fişier.",
"The number of versions must be a number and cannot be blank.": "Numărul de versiuni trebuie să fie un număr şi nu poate fi gol.",
"The path cannot be blank.": "Locația nu poate fi goală.",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cînd adăugaţi un fişier nou, nu uitaţi că ID-ul fişierului va rămîne acelaşi pe toate dispozitivele. Iar literele mari sînt diferite de literele mici. ",
"Yes": "Da",
"You must keep at least one version.": "Trebuie să păstrezi cel puţin o versiune.",
"days": "Zile",
"full documentation": "toată documentaţia",
"items": "obiecte",
"{%device%} wants to share folder \"{%folder%}\".": "{{Dispozitivul}} vrea să transmită mapa {{Mapa}}"

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) хочет подключиться. Добавить новое устройство?",
"Devices": "Устройства",
"Disconnected": "Нет соединения",
"Discovery": "Обнаружение",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
"Downloaded": "Загружено",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
"Yes": "Да",
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"days": "Дней",
"full documentation": "полная документация",
"items": "элементы",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой \"{{folder}}\"."

View File

@@ -1,5 +1,5 @@
{
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
"A negative number of days doesn't make sense.": "Negativt antal dagar är inte troligt.",
"A new major version may not be compatible with previous versions.": "En ny huvudversion kan eventuellt vara inkompatibel med tidigare versioner.",
"API Key": "API-nyckel",
"About": "Om",
@@ -10,8 +10,8 @@
"Add new folder?": "Lägg till katalog?",
"Address": "Adress",
"Addresses": "Adresser",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Avancerat",
"Advanced Configuration": "Avancerad konfiguration",
"All Data": "All data",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
"Alphabetic": "Alfabetisk",
@@ -19,11 +19,11 @@
"Anonymous Usage Reporting": "Anonym användarstatistik",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurerade på en introduktörsenhet kommer också att läggas till den här enheten.",
"Automatic upgrades": "Automatisk uppgradering",
"Be careful!": "Be careful!",
"Be careful!": "Var aktsam!",
"Bugs": "Buggar",
"CPU Utilization": "CPU-användning",
"Changelog": "Changelog",
"Clean out after": "Clean out after",
"Clean out after": "Rensa efteråt",
"Close": "Stäng",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
@@ -33,13 +33,14 @@
"Copied from original": "Oförändrat",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 följande medverkande:",
"Delete": "Radera",
"Deleted": "Deleted",
"Deleted": "Borttaget",
"Device ID": "Enhets-ID",
"Device Identification": "Enhetsidentifikation",
"Device Name": "Enhetsnamn",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enheten {{device}} ({{address}}) vill ansluta. Lägg till ny enhet?",
"Devices": "Enheter",
"Disconnected": "Ej ansluten",
"Discovery": "Uppslagning",
"Documentation": "Dokumentation",
"Download Rate": "Nedladdningshastighet",
"Downloaded": "Nerladdat",
@@ -49,18 +50,18 @@
"Edit Folder": "Redigera katalog",
"Editing": "Redigerar",
"Enable UPnP": "Använd UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade (\"tcp://ip:port\", \"tcp://host:port\")-adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
"Enter ignore patterns, one per line.": "Ange filmönster, ett per rad.",
"Error": "Fel",
"External File Versioning": "Extern versionshantering",
"Failed Items": "Failed Items",
"Failed Items": "Misslyckade filer",
"File Pull Order": "Hämtningsprioritering av filer",
"File Versioning": "Versionshantering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras vid sökning efter förändringar. Används på FAT-filsystem.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till katalogen .stversions om de ersätts eller raderas av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersatts eller raderats av Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
"Folder": "Folder",
"Folder": "Katalog",
"Folder ID": "Katalog-ID",
"Folder Master": "Huvudlagring",
"Folder Path": "Sökväg",
@@ -74,12 +75,12 @@
"Global Discovery Server": "Global uppslagningsserver",
"Global State": "Global status",
"Help": "Hjälp",
"Home page": "Home page",
"Home page": "Hemsida",
"Ignore": "Ignorera",
"Ignore Patterns": "Ignorerade filmönster",
"Ignore Permissions": "Ignorera filrättigheter",
"Incoming Rate Limit (KiB/s)": "Max nedladdningshastighet (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Inkorrekt konfiguration kan skada innehållet i katalogen and få Syncthing att sluta fungera.",
"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",
@@ -89,11 +90,11 @@
"Later": "Senare",
"Local Discovery": "Lokal uppslagning",
"Local State": "Lokal status",
"Local State (Total)": "Local State (Total)",
"Local State (Total)": "Lokal status (Total)",
"Major Upgrade": "Stor uppgradering",
"Maximum Age": "Högsta åldersgräns",
"Metadata Only": "Endast metadata",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Minimum ledigt diskutrymme",
"Move to top of queue": "Flytta till överst i kön",
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
"Never": "Aldrig",
@@ -106,15 +107,15 @@
"OK": "OK",
"Off": "Av",
"Oldest First": "Äldst först",
"Options": "Options",
"Out of Sync": "Out of Sync",
"Options": "Alternativ",
"Out of Sync": "Osynkad",
"Out of Sync Items": "Osynkade poster",
"Outgoing Rate Limit (KiB/s)": "Max uppladdningshastighet (KiB/s)",
"Override Changes": "Skriv över ändringar",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sökväg till katalogen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna tomt för att använda .stversions i den ordinarie katalogen).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Paus",
"Paused": "Pausad",
"Please consult the release notes before performing a major upgrade.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
"Please wait": "Var god vänta",
"Preview": "Förhandsgranska",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Snabb guide till filmönster som stöds",
"RAM Utilization": "Minnesanvändning",
"Random": "Slumpmässig",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Vidarbefordras via",
"Relays": "Vidarbefordringar",
"Release Notes": "versionsnyheter",
"Remove": "Remove",
"Remove": "Ta bort",
"Rescan": "Uppdatera",
"Rescan All": "Uppdatera alla",
"Rescan Interval": "Uppdateringsintervall",
"Restart": "Starta om",
"Restart Needed": "Omstart behövs",
"Restarting": "Startar om",
"Resume": "Resume",
"Resume": "Återuppta",
"Reused": "Återanvänt",
"Save": "Spara",
"Scanning": "Uppdaterar",
@@ -157,7 +158,7 @@
"Source Code": "Källkod",
"Staggered File Versioning": "Versionshantering i intervall",
"Start Browser": "Starta browser",
"Statistics": "Statistics",
"Statistics": "Statistik",
"Stopped": "Stoppad",
"Support": "Support",
"Sync Protocol Listen Addresses": "Address för inkommande anslutningar",
@@ -180,25 +181,25 @@
"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 following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Följande filer kunde inte synkroniseras.",
"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 minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Minimum ledigt diskutrymme i procent måste vara en icke negativ siffra mellan 0 och 100 (inklusive).",
"The number of days must be a number and cannot be blank.": "Antalet dagar måste vara en siffra och får inte vara tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "Antal dagar som filer ligger kvar i papperskorgen. Noll betyder för alltid.",
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas, per fil.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas tomt.",
"The path cannot be blank.": "Ange en sökväg",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Frekvensgränsen måste vara ett icke-negativt tal (0: ingen gräns)",
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "De omprövas automatiskt och kommer att synkroniseras när felet är löst.",
"This is a major version upgrade.": "Det här är en stor uppgradering.",
"Trash Can File Versioning": "Trash Can File Versioning",
"Trash Can File Versioning": "Versionshantering på filer i papperskorgen",
"Unknown": "Okänt",
"Unshared": "Inte delad",
"Unused": "Oanvänd",
"Up to Date": "Helt uppdaterad",
"Updated": "Updated",
"Updated": "Uppdaterad",
"Upgrade": "Uppgradering",
"Upgrade To {%version%}": "Uppgradera till {{version}}",
"Upgrading": "Uppgraderar",
@@ -212,6 +213,7 @@
"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.": "När du lägger till ny katalog, tänk på att katalog-ID:t knyter ihop katalogen mellan olika noder. De måste vara exakt desamma mellan noder och stora eller små bokstäver har betydelse.",
"Yes": "Ja",
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"days": "dagar",
"full documentation": "fullständig dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Пристрій {{device}} ({{address}}) намагається під’єднатися. Додати новий пристрій?",
"Devices": "Пристрої",
"Disconnected": "З’єднання відсутнє",
"Discovery": "Discovery",
"Documentation": "Документація",
"Download Rate": "Швидкість завантаження",
"Downloaded": "Завантажено",
@@ -49,7 +50,7 @@
"Edit Folder": "Редагувати директорію",
"Editing": "Редагування",
"Enable UPnP": "Увімкнути UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введіть розділені комою (\"tcp://ip:port\", \"tcp://host:port\") адреси або \"dynamic\" для автоматичного визначення адреси.",
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
"Error": "Помилка",
"External File Versioning": "Зовнішне керування версіями",
@@ -74,7 +75,7 @@
"Global Discovery Server": "Сервер для глобального виявлення",
"Global State": "Глобальний статус",
"Help": "Допомога",
"Home page": "Home page",
"Home page": "Домашня сторінка",
"Ignore": "Ігнорувати",
"Ignore Patterns": "Ігнорувати шаблони",
"Ignore Permissions": "Ігнорувати права доступу до файлів",
@@ -93,7 +94,7 @@
"Major Upgrade": "Мажорне оновлення",
"Maximum Age": "Максимальний вік",
"Metadata Only": "Тільки метадані",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Мінімальний вільний простір на диску",
"Move to top of queue": "Пересунути у початок черги",
"Multi level wildcard (matches multiple directory levels)": "Багаторівнева маска (пошук збігів в усіх піддиректоріях) ",
"Never": "Ніколи",
@@ -113,8 +114,8 @@
"Override Changes": "Перезаписати зміни",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Шлях до директорії на локальному комп’ютері. Буде створений, якщо такий не існує. Символ тильди (~) може бути використаний як ярлик для",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Пауза",
"Paused": "Призупинено",
"Please consult the release notes before performing a major upgrade.": "Будь ласка перегляньте примітки до випуску перед мажорним оновленням. ",
"Please wait": "Будь ласка, зачекайте",
"Preview": "Попередній перегляд",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
"RAM Utilization": "Використання RAM",
"Random": "Випадково",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Ретранслювати через",
"Relays": "Ретрансляція",
"Release Notes": "Примітки до випуску",
"Remove": "Remove",
"Remove": "Видалити",
"Rescan": "Пересканувати",
"Rescan All": "Пересканувати усе",
"Rescan Interval": "Інтервал для повторного сканування",
"Restart": "Перезапуск",
"Restart Needed": "Необхідний перезапуск",
"Restarting": "Відбувається перезапуск",
"Resume": "Resume",
"Resume": "Продовжити",
"Reused": "Використано вдруге",
"Save": "Зберегти",
"Scanning": "Сканування",
@@ -157,7 +158,7 @@
"Source Code": "Сирцевий код",
"Staggered File Versioning": "Поступове версіонування",
"Start Browser": "Запустити браузер",
"Statistics": "Statistics",
"Statistics": "Статистика",
"Stopped": "Зупинено",
"Support": "Підтримка",
"Sync Protocol Listen Addresses": "Адреса панелі управління",
@@ -183,13 +184,13 @@
"The following items could not be synchronized.": "Наступні пункти не можуть бути синхронізовані.",
"The maximum age must be a number and cannot be blank.": "Максимальний термін повинен бути числом та не може бути пустим.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальний термін, щоб зберігати версію (у днях, вствновіть в 0, щоби зберігати версії назавжди).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Відсоток мінімального вільного простору на диску має бути додатнім числом від 0 до 100 включно.",
"The number of days must be a number and cannot be blank.": "Кількість днів має бути числом і не може бути порожнім.",
"The number of days to keep files in the trash can. Zero means forever.": "Кількість днів зберігання файлів у кошику. Нуль означає назавжди.",
"The number of old versions to keep, per file.": "Кількість старих версій, яку необхідно зберігати для кожного файлу.",
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
"The path cannot be blank.": "Шлях не може бути порожнім.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Швидкість має бути додатнім числом.",
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
"They are retried automatically and will be synced when the error is resolved.": "Вони будуть автоматично повторно синхронізовані, коли помилку буде усунено. ",
"This is a major version upgrade.": "Це оновлення мажорної версії",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між вузлами. Назви є чутливими до регістра та повинні співпадати точно між усіма вузлами.",
"Yes": "Так",
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
"days": "днів",
"full documentation": "повна документація",
"items": "елементи",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\"."

View File

@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "设备:{{device}} 地址:({{address}}) 请求连接。是否添加新设备?",
"Devices": "设备",
"Disconnected": "连接已断开",
"Discovery": "Discovery",
"Documentation": "文档",
"Download Rate": "下载速度",
"Downloaded": "已下载",
@@ -212,6 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "若你添加了新文件夹,记住文件夹标识是用以在不同设备间建立联系的。在不同设备间拥有相同标识的文件夹将会被同步。且文件夹标识大小写敏感。",
"Yes": "是",
"You must keep at least one version.": "您必须保留至少一个版本。",
"days": "days",
"full documentation": "完整文档",
"items": "条目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将 “{{folder}}” 文件夹共享给您"

View File

@@ -1,6 +1,6 @@
{
"A negative number of days doesn't make sense.": "一個負的天數並不合理。",
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
"A new major version may not be compatible with previous versions.": "一個新的主要版本可能與以前的版本並不相容。",
"API Key": "API 金鑰",
"About": "關於",
"Actions": "操作",
@@ -10,8 +10,8 @@
"Add new folder?": "新增資料夾?",
"Address": "位址",
"Addresses": "位址",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "進階",
"Advanced Configuration": "進階設定",
"All Data": "全部資料",
"Allow Anonymous Usage Reporting?": "允許匿名的使用資訊回報?",
"Alphabetic": "字母順序",
@@ -19,18 +19,18 @@
"Anonymous Usage Reporting": "匿名的使用資訊回報",
"Any devices configured on an introducer device will be added to this device as well.": "任何在引入者裝置所設置的裝置將會一併新增至此裝置",
"Automatic upgrades": "自動升級",
"Be careful!": "Be careful!",
"Be careful!": "請小心!",
"Bugs": "程式錯誤",
"CPU Utilization": "CPU 使用",
"Changelog": "更新日誌",
"Clean out after": "Clean out after",
"Clean out after": "於之後清空",
"Close": "關閉",
"Command": "指令",
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
"Compression": "壓縮",
"Connection Error": "連線錯誤",
"Copied from elsewhere": "從別處複製",
"Copied from original": "Copied from original",
"Copied from original": "從原來複製",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 下列貢獻者:",
"Delete": "刪除",
"Deleted": "已刪除",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "裝置 {{device}} ({{address}}) 想要連線。要新增裝置嗎?",
"Devices": "裝置",
"Disconnected": "斷線",
"Discovery": "Discovery",
"Documentation": "說明文件",
"Download Rate": "下載速率",
"Downloaded": "已下載",
@@ -53,10 +54,10 @@
"Enter ignore patterns, one per line.": "輸入忽略樣式,每行一種。",
"Error": "錯誤",
"External File Versioning": "外部檔案版本控制",
"Failed Items": "Failed Items",
"Failed Items": "失敗的項目",
"File Pull Order": "提取檔案的順序",
"File Versioning": "檔案版本控制",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "當改變時,檔案權限位元 File permission bits 會被忽略。用於 FAT 檔案系統上。",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "其他裝置做的改變不會影響到此裝置的檔案,但在此裝置上的變化將被發送到叢集中的其他部分。",
@@ -79,7 +80,7 @@
"Ignore Patterns": "忽略樣式",
"Ignore Permissions": "忽略權限",
"Incoming Rate Limit (KiB/s)": "連入速率限制 (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "不正確的設定可能會損壞你的資料夾內容,並引致 Syncthing 的不正當運作。",
"Introducer": "引入者",
"Inversion of the given condition (i.e. do not exclude)": "反轉給定條件 (即:不要排除)",
"Keep Versions": "保留歷史版本數",
@@ -89,11 +90,11 @@
"Later": "稍後",
"Local Discovery": "本地探索",
"Local State": "本地狀態",
"Local State (Total)": "Local State (Total)",
"Local State (Total)": "本地狀態 (總結)",
"Major Upgrade": "重大更新",
"Maximum Age": "最長保留時間",
"Metadata Only": "僅中繼資料",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "最少閒置磁碟空間",
"Move to top of queue": "移到隊列頂端",
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
"Never": "從未",
@@ -113,8 +114,8 @@
"Override Changes": "置換改變",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "資料夾在本地電腦的路徑。若資料夾不存在則會建立。波浪符號 (~) 可用作下列資料夾的捷徑:",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "儲存歷史版本的路徑 (若為空,則預設使用資料夾中的 .stversions 資料夾)。",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "暫停",
"Paused": "暫停",
"Please consult the release notes before performing a major upgrade.": "執行重大升級前請先參閱版本資訊。",
"Please wait": "請稍後",
"Preview": "預覽",
@@ -122,7 +123,7 @@
"Quick guide to supported patterns": "可支援樣式的快速指南",
"RAM Utilization": "記憶體使用",
"Random": "隨機",
"Relayed via": "Relayed via",
"Relayed via": "中繼於",
"Relays": "中繼點",
"Release Notes": "版本資訊",
"Remove": "移除",
@@ -132,8 +133,8 @@
"Restart": "重新啟動",
"Restart Needed": "需要重新啟動",
"Restarting": "正在重新啟動",
"Resume": "Resume",
"Reused": "Reused",
"Resume": "繼續",
"Reused": "重用",
"Save": "儲存",
"Scanning": "正在掃描",
"Select the devices to share this folder with.": "選擇要共享這個資料夾的裝置。",
@@ -180,18 +181,18 @@
"The folder ID must be unique.": "資料夾識別碼必須為獨一無二的。",
"The folder path cannot be blank.": "資料夾路徑不能空白。",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "使用下列的間隔:在第一個小時內每 30 秒保留一個版本,在第一天內每小時保留一個版本,在第 30 天內每一天保留一個版本,在達到最長保留時間前每一星期保留一個版本。",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "以下項目不能被同步。",
"The maximum age must be a number and cannot be blank.": "最長保留時間必須為一個數字且不得為空。",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "一個版本被保留的最長時間 (單位為天,若設定為 0 則表示永遠保留)。",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "天數必須必須為一個數字且不得為空。",
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
"The number of days to keep files in the trash can. Zero means forever.": "檔案在 trash can 中保留的日子。零表示永遠地保留。",
"The number of old versions to keep, per file.": "每個檔案要保留的舊版本數量。",
"The number of versions must be a number and cannot be blank.": "每個檔案要保留的舊版本數量必須是數字且不能為空白。",
"The path cannot be blank.": "路徑不能空白。",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "限制速率必須為非負的數字 (0: 不設限制)",
"The rescan interval must be a non-negative number of seconds.": "重新掃描間隔必須為一個非負數的秒數。",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "解決間題後,將會自動重試和同步。",
"This is a major version upgrade.": "這是一個主要版本更新。",
"Trash Can File Versioning": "Trash Can File Versioning",
"Unknown": "未知",
@@ -208,10 +209,11 @@
"Version": "版本",
"Versions Path": "歷史版本路徑",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "當檔案歷史版本的存留時間大於設定的最大值,或是其數量在一段時間內超出允許值時,則會被刪除。",
"When adding a new device, keep in mind that this device must be added on the other side too.": "當新增一個裝置時,記住,這個裝置也必須被添加至另一邊。",
"When adding a new device, keep in mind that this device must be added on the other side too.": "當新增一個裝置時,務必記住,當前的這個裝置也同樣必須被添加至另一邊。",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "當新增一個資料夾時,請記住,資料夾識別碼是用來將裝置之間的資料夾綁定在一起的。它們有區分大小寫,且必須在所有裝置之間完全相同。",
"Yes": "是",
"You must keep at least one version.": "您必須保留至少一個版本。",
"days": "日",
"full documentation": "完整說明文件",
"items": "個項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\"。"

View File

@@ -70,7 +70,7 @@
</div>
</nav>
<div class="container">
<div class="container" id="content">
<!-- Panel: Restart Needed -->
@@ -171,7 +171,7 @@
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="fa fa-exclamation-circle"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.time | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
<p ng-repeat="err in errorList()"><small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.message)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="clearErrors()">
@@ -190,18 +190,25 @@
<!-- Folder list (top left) -->
<div class="col-md-6">
<h3 translate>Folders</h3>
<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>
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id)}}%"></div>
<h3 class="panel-title">
<span class="fa fa-folder 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="scanning">
<span class="hidden-xs" translate>Scanning</span>
<span class="hidden-xs" ng-if="scanPercentage(folder.id) != undefined">
({{scanPercentage(folder.id)}}%)
</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 class="hidden-xs" translate>Syncing</span>
@@ -232,7 +239,7 @@
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
</tr>
<tr ng-if="model[folder.id].needFiles > 0">
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync</span></th>
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right">
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
</td>
@@ -341,7 +348,7 @@
<!-- This device -->
<div class="col-md-6">
<div class="visible-xs"><h3><span translate>Devices</span></h3><hr></div>
<h3 translate>Devices</h3>
<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">
@@ -372,15 +379,28 @@
<th><span class="fa fa-fw fa-tachometer"></span>&nbsp;<span translate>CPU Utilization</span></th>
<td class="text-right">{{system.cpuPercent | alwaysNumber | natural:1}}%</td>
</tr>
<tr ng-if="system.extAnnounceOK != undefined && announceServersTotal > 0">
<th><span class="fa fa-fw fa-bullhorn"></span>&nbsp;<span translate>Global Discovery</span></th>
<tr ng-if="system.discoveryEnabled">
<th><span class="fa fa-fw fa-map-signs"></span>&nbsp;<span translate>Discovery</span></th>
<td class="text-right">
<span ng-if="announceServersFailed.length == 0" class="data text-success">
<span>OK</span>
<span ng-if="discoveryFailed.length == 0" class="data text-success">
<span>{{discoveryTotal}}/{{discoveryTotal}}</span>
</span>
<span ng-if="announceServersFailed.length != 0" class="data" ng-class="{'text-danger': announceServersFailed.length == announceServersTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-content="{{announceServersFailed.join('\n')}}">
{{announceServersTotal-announceServersFailed.length}}/{{announceServersTotal}}
<span ng-if="discoveryFailed.length != 0" class="data" ng-class="{'text-danger': discoveryFailed.length == discoveryTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-html="true" data-content="{{discoveryFailed.join('<br>\n')}}">
{{discoveryTotal-discoveryFailed.length}}/{{discoveryTotal}}
</span>
</span>
</td>
</tr>
<tr ng-if="system.relaysEnabled">
<th><span class="fa fa-fw fa-sitemap"></span>&nbsp;<span translate>Relays</span></th>
<td class="text-right">
<span ng-if="relaysFailed.length == 0" class="data text-success">
<span>{{relaysTotal}}/{{relaysTotal}}</span>
</span>
<span ng-if="relaysFailed.length != 0" class="data" ng-class="{'text-danger': relaysFailed.length == relaysTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-html="true" data-content="{{relaysFailed.join('<br>\n')}}">
{{relaysTotal-relaysFailed.length}}/{{relaysTotal}}
</span>
</span>
</td>
@@ -412,6 +432,7 @@
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.deviceID]._total | number:0}}%)
</span>
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs">&#9724;</span></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>
@@ -421,16 +442,20 @@
<div class="panel-body">
<table class="table table-condensed table-striped">
<tbody>
<tr ng-if="connections[deviceCfg.deviceID]">
<tr ng-if="connections[deviceCfg.deviceID].connected">
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Download Rate</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].inbps | binary}}B/s ({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID]">
<tr ng-if="connections[deviceCfg.deviceID].connected">
<th><span class="fa fa-fw fa-cloud-upload"></span>&nbsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-link"></span>&nbsp;<span translate>Address</span></th>
<th>
<span class="fa fa-fw fa-link"></span>
<span translate ng-if="connections[deviceCfg.deviceID].type.indexOf('direct') == 0" >Address</span>
<span translate ng-if="connections[deviceCfg.deviceID].type.indexOf('relay') == 0" >Relayed via</span>
</th>
<td class="text-right">{{deviceAddr(deviceCfg)}}</td>
</tr>
<tr ng-if="deviceCfg.compression != 'metadata'">
@@ -444,11 +469,11 @@
<th><span class="fa fa-fw fa-thumbs-o-up"></span>&nbsp;<span translate>Introducer</span></th>
<td translate class="text-right">Yes</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID]">
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
<th><span class="fa fa-fw fa-tag"></span>&nbsp;<span translate>Version</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
</tr>
<tr ng-if="!connections[deviceCfg.deviceID]">
<tr ng-if="!connections[deviceCfg.deviceID].connected">
<th><span class="fa fa-fw fa-eye"></span>&nbsp;<span translate>Last seen</span></th>
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}</td>
@@ -465,6 +490,12 @@
<button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
<span class="fa fa-pencil"></span>&nbsp;<span translate>Edit</span>
</button>
<button ng-if="!connections[deviceCfg.deviceID].paused" type="button" class="btn btn-sm btn-default" ng-click="pauseDevice(deviceCfg.deviceID)">
<span class="fa fa-pause"></span>&nbsp;<span translate>Pause</span>
</button>
<button ng-if="connections[deviceCfg.deviceID].paused" type="button" class="btn btn-sm btn-default" ng-click="resumeDevice(deviceCfg.deviceID)">
<span class="fa fa-play"></span>&nbsp;<span translate>Resume</span>
</button>
</span>
<div class="clearfix"></div>
</div>

View File

@@ -13,6 +13,7 @@
<div class="col-md-12">
<ul class="list-unstyled three-columns" id="contributor-list">
<li class="auto-generated">Aaron Bieber</li>
<li class="auto-generated">Adam Piggott</li>
<li class="auto-generated">Alexander Graf</li>
<li class="auto-generated">Andrew Dunham</li>
<li class="auto-generated">Antony Male</li>
@@ -46,6 +47,7 @@
<li class="auto-generated">Frank Isemann</li>
<li class="auto-generated">Gilli Sigurdsson</li>
<li class="auto-generated">Jacek Szafarkiewicz</li>
<li class="auto-generated">Jake Peterson</li>
<li class="auto-generated">Jakob Borg</li>
<li class="auto-generated">James Patterson</li>
<li class="auto-generated">Jaroslav Malec</li>
@@ -59,6 +61,8 @@
<li class="auto-generated">Marc Laporte</li>
<li class="auto-generated">Marc Pujol</li>
<li class="auto-generated">Marcin Dziadus</li>
<li class="auto-generated">Mateusz Naściszewski</li>
<li class="auto-generated">Matt Burke</li>
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Tilli</li>
<li class="auto-generated">Pascal Jungblut</li>
@@ -68,13 +72,16 @@
<li class="auto-generated">Piotr Bejda</li>
<li class="auto-generated">Ryan Sullivan</li>
<li class="auto-generated">Sergey Mishin</li>
<li class="auto-generated">Stefan Kuntz</li>
<li class="auto-generated">Stefan Tatschner</li>
<li class="auto-generated">Tim Abell</li>
<li class="auto-generated">Tobias Nygren</li>
<li class="auto-generated">Tomas Cerveny</li>
<li class="auto-generated">Tully Robinson</li>
<li class="auto-generated">Tyler Brazier</li>
<li class="auto-generated">Veeti Paananen</li>
<li class="auto-generated">Vil Brekin</li>
<li class="auto-generated">Yannic A.</li>
</ul>
</div>
</div>

View File

@@ -63,6 +63,8 @@ angular.module('syncthing.core')
DEVICE_DISCONNECTED: 'DeviceDisconnected', // Generated each time a connection to a device has been terminated
DEVICE_DISCOVERED: 'DeviceDiscovered', // Emitted when a new device is discovered using local discovery
DEVICE_REJECTED: 'DeviceRejected', // Emitted when there is a connection from a device we are not configured to talk to
DEVICE_PAUSED: 'DevicePaused', // Emitted when a device has been paused
DEVICE_RESUMED: 'DeviceResumed', // Emitted when a device has been resumed
DOWNLOAD_PROGRESS: 'DownloadProgress', // Emitted during file downloads for each folder for each file
FOLDER_COMPLETION: 'FolderCompletion', //Emitted when the local or remote contents for a folder changes
FOLDER_REJECTED: 'FolderRejected', // Emitted when a device sends index information for a folder we do not have, or have but do not share with the device in question
@@ -76,6 +78,7 @@ angular.module('syncthing.core')
STARTUP_COMPLETED: 'StartupCompleted', // Emitted exactly once, when initialization is complete and Syncthing is ready to start exchanging data with other devices
STATE_CHANGED: 'StateChanged', // Emitted when a folder changes state
FOLDER_ERRORS: 'FolderErrors', // Emitted when a folder has errors preventing a full sync
FOLDER_SCAN_PROGRESS: 'FolderScanProgress', // Emitted every ScanProgressIntervalS seconds, indicating how far into the scan it is at.
start: function() {
$http.get(urlbase + '/events?limit=1')

View File

@@ -48,6 +48,7 @@ angular.module('syncthing.core')
$scope.failedCurrentPage = 1;
$scope.failedCurrentFolder = undefined;
$scope.failedPageSize = 10;
$scope.scanProgress = {};
$scope.localStateTotal = {
bytes: 0,
@@ -163,6 +164,12 @@ angular.module('syncthing.core')
if (data.to === 'syncing') {
$scope.failed[data.folder] = [];
}
// If a folder has started scanning, then any scan progress is
// also obsolete.
if (data.to === 'scanning') {
delete $scope.scanProgress[data.folder];
}
}
});
@@ -171,7 +178,7 @@ angular.module('syncthing.core')
});
$scope.$on(Events.DEVICE_DISCONNECTED, function (event, arg) {
delete $scope.connections[arg.data.id];
$scope.connections[arg.data.id].connected = false;
refreshDeviceStats();
});
@@ -182,6 +189,7 @@ angular.module('syncthing.core')
outbps: 0,
inBytesTotal: 0,
outBytesTotal: 0,
type: arg.data.type,
address: arg.data.addr
};
$scope.completion[arg.data.id] = {
@@ -214,6 +222,14 @@ angular.module('syncthing.core')
$scope.deviceRejections[arg.data.device] = arg;
});
$scope.$on(Events.DEVICE_PAUSED, function (event, arg) {
$scope.connections[arg.data.device].paused = true;
});
$scope.$on(Events.DEVICE_RESUMED, function (event, arg) {
$scope.connections[arg.data.device].paused = false;
});
$scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
$scope.folderRejections[arg.data.folder + "-" + arg.data.device] = arg;
});
@@ -301,6 +317,15 @@ angular.module('syncthing.core')
$scope.failed[data.folder] = data.errors;
});
$scope.$on(Events.FOLDER_SCAN_PROGRESS, function (event, arg) {
var data = arg.data;
$scope.scanProgress[data.folder] = {
current: data.current,
total: data.total
};
console.log("FolderScanProgress", data);
});
$scope.emitHTTPError = function (data, status, headers, config) {
$scope.$emit('HTTPError', {data: data, status: status, headers: headers, config: config});
};
@@ -352,15 +377,26 @@ angular.module('syncthing.core')
$http.get(urlbase + '/system/status').success(function (data) {
$scope.myID = data.myID;
$scope.system = data;
$scope.announceServersTotal = data.extAnnounceOK ? Object.keys(data.extAnnounceOK).length : 0;
var failed = [];
for (var server in data.extAnnounceOK) {
if (!data.extAnnounceOK[server]) {
failed.push(server);
$scope.discoveryTotal = data.discoveryMethods;
var discoveryFailed = [];
for (var disco in data.discoveryErrors) {
if (data.discoveryErrors[disco]) {
discoveryFailed.push(disco + ": " + data.discoveryErrors[disco]);
}
}
$scope.announceServersFailed = failed;
$scope.discoveryFailed = discoveryFailed;
var relaysFailed = [];
var relaysTotal = 0;
for (var relay in data.relayClientStatus) {
if (!data.relayClientStatus[relay]) {
relaysFailed.push(relay);
}
relaysTotal++;
}
$scope.relaysFailed = relaysFailed;
$scope.relaysTotal = relaysTotal;
console.log("refreshSystem", data);
}).error($scope.emitHTTPError);
@@ -615,12 +651,28 @@ angular.module('syncthing.core')
return Math.floor(pct);
};
$scope.scanPercentage = function (folder) {
if (!$scope.scanProgress[folder]) {
return undefined;
}
var pct = 100 * $scope.scanProgress[folder].current / $scope.scanProgress[folder].total;
return Math.floor(pct);
}
$scope.deviceStatus = function (deviceCfg) {
if ($scope.deviceFolders(deviceCfg).length === 0) {
return 'unused';
}
if ($scope.connections[deviceCfg.deviceID]) {
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
return 'unknown';
}
if ($scope.connections[deviceCfg.deviceID].paused) {
return 'paused';
}
if ($scope.connections[deviceCfg.deviceID].connected) {
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
return 'insync';
} else {
@@ -638,7 +690,15 @@ angular.module('syncthing.core')
return 'warning';
}
if ($scope.connections[deviceCfg.deviceID]) {
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
return 'info';
}
if ($scope.connections[deviceCfg.deviceID].paused) {
return 'default';
}
if ($scope.connections[deviceCfg.deviceID].connected) {
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
return 'success';
} else {
@@ -652,7 +712,7 @@ angular.module('syncthing.core')
$scope.deviceAddr = function (deviceCfg) {
var conn = $scope.connections[deviceCfg.deviceID];
if (conn) {
if (conn && conn.connected) {
return conn.address;
}
return '?';
@@ -697,6 +757,14 @@ angular.module('syncthing.core')
return device.deviceID.substr(0, 6);
};
$scope.pauseDevice = function (device) {
$http.post(urlbase + "/system/pause?device=" + device);
};
$scope.resumeDevice = function (device) {
$http.post(urlbase + "/system/resume?device=" + device);
};
$scope.editSettings = function () {
// Make a working copy
$scope.tmpOptions = angular.copy($scope.config.options);
@@ -975,13 +1043,16 @@ angular.module('syncthing.core')
};
$scope.errorList = function () {
if (!$scope.errors) {
return [];
}
return $scope.errors.filter(function (e) {
return e.time > $scope.seenError;
return e.when > $scope.seenError;
});
};
$scope.clearErrors = function () {
$scope.seenError = $scope.errors[$scope.errors.length - 1].time;
$scope.seenError = $scope.errors[$scope.errors.length - 1].when;
$http.post(urlbase + '/system/error/clear');
};
@@ -1064,6 +1135,7 @@ angular.module('syncthing.core')
};
$scope.currentFolder.rescanIntervalS = 60;
$scope.currentFolder.minDiskFreePct = 1;
$scope.currentFolder.maxConflicts = -1;
$scope.currentFolder.order = "random";
$scope.currentFolder.fileVersioningSelector = "none";
$scope.currentFolder.trashcanClean = 0;
@@ -1085,6 +1157,7 @@ angular.module('syncthing.core')
selectedDevices: {},
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: -1,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,

View File

@@ -13,9 +13,9 @@
<label translate for="deviceID">Device ID</label>
<input ng-if="!editingExisting" name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required valid-deviceid list="discovery-list" />
<datalist id="discovery-list" ng-if="!editingExisting">
<option ng-repeat="(id,address) in discovery" value="{{ id }}" />
<option ng-repeat="(id, data) in discovery" value="{{id}}" />
</datalist>
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.deviceID}}</div>
<div ng-if="editingExisting" class="well well-sm text-monospace" select-on-click>{{currentDevice.deviceID}}</div>
<p class="help-block">
<span translate ng-if="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">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).</span>
<span translate ng-show="!editingExisting && (deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine)">When adding a new device, keep in mind that this device must be added on the other side too.</span>
@@ -32,7 +32,7 @@
<div class="form-group">
<label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input>
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p>
<p translate class="help-block">Enter comma separated ("tcp://ip:port", "tcp://host:port") addresses or "dynamic" to perform automatic discovery of the address.</p>
</div>
<div class="form-group">
<label translate>Compression</label>

View File

@@ -1,4 +1,4 @@
<modal id="idqr" status="info" icon="qrcode" title="{{'Device Identification' | translate}} - {{deviceName(thisDevice())}}" large="yes" close="yes">
<div class="well well-sm text-monospace text-center">{{myID}}</div>
<div class="well well-sm text-monospace text-center" select-on-click>{{myID}}</div>
<img ng-if="myID" class="center-block img-thumbnail" ng-src="qr/?text={{myID}}"/>
</modal>

View File

@@ -13,12 +13,11 @@
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder></input>
<p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
<span translate ng-if="folderEditor.folderID.$error.pattern && folderEditor.folderID.$dirty">The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
@@ -40,8 +39,8 @@
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.minDiskFreePct.$invalid && folderEditor.minDiskFreePct.$dirty}">
<label for="minDiskFreePct"><span translate>Minimum Free Disk Space</span> (0-100%)</label>
<input name="minDiskFreePct" id="minDiskFreePct" class="form-control" type="number" ng-model="currentFolder.minDiskFreePct" required min="0" max="100"></input>
<label for="minDiskFreePct"><span translate>Minimum Free Disk Space</span> (0.0 - 100.0%)</label>
<input name="minDiskFreePct" id="minDiskFreePct" class="form-control" type="number" ng-model="currentFolder.minDiskFreePct" required min="0.0" max="100.0"></input>
<p class="help-block">
<span translate ng-if="!folderEditor.minDiskFreePct.$valid && folderEditor.minDiskFreePct.$dirty">The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).</span>
</p>
@@ -98,7 +97,7 @@
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required min="0"></input>
<div class="input-group-addon">days</div>
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>

View File

@@ -108,7 +108,7 @@
<div class="form-group">
<label translate>API Key</label>
<div class="well well-sm text-monospace">{{tmpGUI.apiKey || "-"}}</div>
<div class="well well-sm text-monospace" select-on-click>{{tmpGUI.apiKey || "-"}}</div>
<button type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(tmpGUI)">
<span class="fa fa-repeat"></span>&nbsp;<span translate>Generate</span>
</button>

View File

@@ -1,5 +1,7 @@
<modal id="urPreview" status="success" icon="bar-chart" title="{{'Anonymous Usage Reporting' | translate}}" large="yes" close="yes" tabindex="-1">
<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="<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>
<form>
<textarea class="form-control" rows="20">{{reportData | json}}</textarea>
</form>
</modal>

View File

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,12 @@
package beacon
import "net"
import (
"net"
stdsync "sync"
"github.com/thejerf/suture"
)
type recv struct {
data []byte
@@ -14,34 +19,26 @@ type recv struct {
}
type Interface interface {
suture.Service
Send(data []byte)
Recv() ([]byte, net.Addr)
Error() error
}
type readerFrom interface {
ReadFrom([]byte) (int, net.Addr, error)
type errorHolder struct {
err error
mut stdsync.Mutex // uses stdlib sync as I want this to be trivially embeddable, and there is no risk of blocking
}
func genericReader(conn readerFrom, outbox chan<- recv) {
bs := make([]byte, 65536)
for {
n, addr, err := conn.ReadFrom(bs)
if err != nil {
l.Warnln("multicast read:", err)
return
}
if debug {
l.Debugf("recv %d bytes from %s", n, addr)
}
c := make([]byte, n)
copy(c, bs)
select {
case outbox <- recv{c, addr}:
default:
if debug {
l.Debugln("dropping message")
}
}
}
func (e *errorHolder) setError(err error) {
e.mut.Lock()
e.err = err
e.mut.Unlock()
}
func (e *errorHolder) Error() error {
e.mut.Lock()
err := e.err
e.mut.Unlock()
return err
}

View File

@@ -11,6 +11,7 @@ import (
"net"
"time"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
)
@@ -19,6 +20,8 @@ type Broadcast struct {
port int
inbox chan []byte
outbox chan recv
br *broadcastReader
bw *broadcastWriter
}
func NewBroadcast(port int) *Broadcast {
@@ -31,9 +34,7 @@ func NewBroadcast(port int) *Broadcast {
FailureBackoff: 60 * time.Second,
// Only log restarts in debug mode.
Log: func(line string) {
if debug {
l.Debugln(line)
}
l.Debugln(line)
},
}),
port: port,
@@ -41,14 +42,18 @@ func NewBroadcast(port int) *Broadcast {
outbox: make(chan recv, 16),
}
b.Add(&broadcastReader{
port: port,
outbox: b.outbox,
})
b.Add(&broadcastWriter{
port: port,
inbox: b.inbox,
})
b.br = &broadcastReader{
port: port,
outbox: b.outbox,
connMut: sync.NewMutex(),
}
b.Add(b.br)
b.bw = &broadcastWriter{
port: port,
inbox: b.inbox,
connMut: sync.NewMutex(),
}
b.Add(b.bw)
return b
}
@@ -62,38 +67,42 @@ func (b *Broadcast) Recv() ([]byte, net.Addr) {
return recv.data, recv.src
}
func (b *Broadcast) Error() error {
if err := b.br.Error(); err != nil {
return err
}
return b.bw.Error()
}
type broadcastWriter struct {
port int
inbox chan []byte
conn *net.UDPConn
failed bool // Have we already logged a failure reason?
port int
inbox chan []byte
conn *net.UDPConn
connMut sync.Mutex
errorHolder
}
func (w *broadcastWriter) Serve() {
if debug {
l.Debugln(w, "starting")
defer l.Debugln(w, "stopping")
}
l.Debugln(w, "starting")
defer l.Debugln(w, "stopping")
var err error
w.conn, err = net.ListenUDP("udp4", nil)
conn, err := net.ListenUDP("udp4", nil)
if err != nil {
if !w.failed {
l.Warnln("Local discovery over IPv4 unavailable:", err)
w.failed = true
}
l.Debugln(err)
w.setError(err)
return
}
defer w.conn.Close()
defer conn.Close()
w.failed = false
w.connMut.Lock()
w.conn = conn
w.connMut.Unlock()
for bs := range w.inbox {
addrs, err := net.InterfaceAddrs()
if err != nil {
if debug {
l.Debugln("Local discovery (broadcast writer):", err)
}
l.Debugln(err)
w.setError(err)
continue
}
@@ -110,41 +119,53 @@ func (w *broadcastWriter) Serve() {
dsts = append(dsts, net.IP{0xff, 0xff, 0xff, 0xff})
}
if debug {
l.Debugln("addresses:", dsts)
}
l.Debugln("addresses:", dsts)
success := 0
for _, ip := range dsts {
dst := &net.UDPAddr{IP: ip, Port: w.port}
w.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
_, err := w.conn.WriteTo(bs, dst)
conn.SetWriteDeadline(time.Now().Add(time.Second))
_, err := conn.WriteTo(bs, dst)
conn.SetWriteDeadline(time.Time{})
if err, ok := err.(net.Error); ok && err.Timeout() {
// Write timeouts should not happen. We treat it as a fatal
// error on the socket.
l.Infoln("Local discovery (broadcast writer):", err)
w.failed = true
l.Debugln(err)
w.setError(err)
return
} else if err, ok := err.(net.Error); ok && err.Temporary() {
// A transient error. Lets hope for better luck in the future.
if debug {
l.Debugln(err)
}
continue
} else if err != nil {
// Some other error that we don't expect. Bail and retry.
l.Infoln("Local discovery (broadcast writer):", err)
w.failed = true
return
} else if debug {
l.Debugf("sent %d bytes to %s", len(bs), dst)
}
if err, ok := err.(net.Error); ok && err.Temporary() {
// A transient error. Lets hope for better luck in the future.
l.Debugln(err)
continue
}
if err != nil {
// Some other error that we don't expect. Bail and retry.
l.Debugln(err)
w.setError(err)
return
}
l.Debugf("sent %d bytes to %s", len(bs), dst)
success++
}
if success > 0 {
w.setError(nil)
}
}
}
func (w *broadcastWriter) Stop() {
w.conn.Close()
w.connMut.Lock()
if w.conn != nil {
w.conn.Close()
}
w.connMut.Unlock()
}
func (w *broadcastWriter) String() string {
@@ -152,61 +173,59 @@ func (w *broadcastWriter) String() string {
}
type broadcastReader struct {
port int
outbox chan recv
conn *net.UDPConn
failed bool
port int
outbox chan recv
conn *net.UDPConn
connMut sync.Mutex
errorHolder
}
func (r *broadcastReader) Serve() {
if debug {
l.Debugln(r, "starting")
defer l.Debugln(r, "stopping")
}
l.Debugln(r, "starting")
defer l.Debugln(r, "stopping")
var err error
r.conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
if err != nil {
if !r.failed {
l.Warnln("Local discovery over IPv4 unavailable:", err)
r.failed = true
}
l.Debugln(err)
r.setError(err)
return
}
defer r.conn.Close()
defer conn.Close()
r.connMut.Lock()
r.conn = conn
r.connMut.Unlock()
bs := make([]byte, 65536)
for {
n, addr, err := r.conn.ReadFrom(bs)
n, addr, err := conn.ReadFrom(bs)
if err != nil {
if !r.failed {
l.Infoln("Local discovery (broadcast reader):", err)
r.failed = true
}
l.Debugln(err)
r.setError(err)
return
}
r.failed = false
r.setError(nil)
if debug {
l.Debugf("recv %d bytes from %s", n, addr)
}
l.Debugf("recv %d bytes from %s", n, addr)
c := make([]byte, n)
copy(c, bs)
select {
case r.outbox <- recv{c, addr}:
default:
if debug {
l.Debugln("dropping message")
}
l.Debugln("dropping message")
}
}
}
func (r *broadcastReader) Stop() {
r.conn.Close()
r.connMut.Lock()
if r.conn != nil {
r.conn.Close()
}
r.connMut.Unlock()
}
func (r *broadcastReader) String() string {

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