Compare commits

..

223 Commits

Author SHA1 Message Date
Jakob Borg
13679284ac cmd/syncthing: Rename --conf back to --config
This was inadvertently changed in the flag migration.
2021-04-06 10:42:29 +02:00
André Colomb
7931af1078 lib/db: Fix comparison of pending folder timestamps (fixes #7532) (#7535) 2021-04-06 10:37:11 +02:00
Jakob Borg
fb4fdaf4c0 Merge pull request from GHSA-x462-89pf-6r5h 2021-04-06 08:03:22 +02:00
Simon Frei
0d7a77ba85 lib/model: Reset conn when enc token is missing (fixes #7198) (#7518) 2021-03-23 10:38:40 +01:00
Simon Frei
924b96856f lib: Handle adding enc folders on an existing conn (fixes #7509) (#7510) 2021-03-22 21:50:19 +01:00
Audrius Butkevicius
f7929229c8 cmd/strelaysrv: Increase default strelaysrv network buffer size (fixes #7514) (#7517)
Increases throughput at the cost of more memory per connection
2021-03-22 21:47:51 +01:00
Simon Frei
6b25eb2e79 lib/ur: Prevent panic when blocksResult is nil (ref #7495) (#7515) 2021-03-22 15:07:41 +01:00
Simon Frei
bc08a951f1 lib/model: Encrypted fileinfo trailer needs to be in wire format (#7505) 2021-03-21 10:34:08 +01:00
Lars Lehtonen
a87c5515bd lib/model: Error check in test (#7508) 2021-03-21 10:32:17 +01:00
Jakob Borg
ebcd22b02b lib/osutil: Fix raising max FDs on macOS (#7507)
There was a logic mistake, so the limit in question wasn't used. On my
macOS this doesn't seem to matter, the hard limit returned is 2^63-1 and
setting the soft limit to that works. However I'm assuming that's not
the case for older macOSes since it was so nicely documented, so we
should still have this working. (10240 FDs should be enough for
anybody.)
2021-03-20 16:32:36 +01:00
Audrius Butkevicius
4b02b7e6f1 lib/connections: Silence "connected to myself" messages. (#7500) 2021-03-17 23:53:20 +01:00
Jakob Borg
fdd823d2cb lib/osutil: Remove unused code 2021-03-17 23:18:07 +01:00
Jakob Borg
8ef504f745 all: Simplify some method calls (#7499)
strings.Replace(a, b, c, -1) -> strings.ReplaceAll(a, b, c)

(Go 1.12) and who knows what was up with that dialQueue.Sort() thing.
2021-03-17 23:12:26 +01:00
Jakob Borg
960e850a78 cmd/uraggregate: Ignore errors harder (#7498)
We were already ignoring them, it just didn't look like it.
2021-03-17 22:55:43 +01:00
Jakob Borg
ea701a4e9e cmd/syncthing: Error handling on upgrade (#7497)
dat innocuous little colon
2021-03-17 22:52:43 +01:00
deepsource-autofix[bot]
6c573a5762 Remove unnecessary guard around delete (#7496)
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2021-03-17 22:23:36 +01:00
Jakob Borg
3ac858b150 all: Remove miscellaneous vestigial code (#7495) 2021-03-17 22:23:12 +01:00
Jakob Borg
f4372710bf all: Remove crypto/md5 (#7493)
This is a mostly pointless change to make security scanners and static
analysis tools happy, as they all hate seeing md5. None of our md5 uses
were security relevant, but still. Only visible effect of this change is
that our temp file names for very long file names become slightly longer
than they were previously...
2021-03-17 22:22:49 +01:00
Jakob Borg
f39477bbd5 lib/api: Missing error handling in API delete-device (#7494) 2021-03-17 22:08:44 +01:00
Jakob Borg
6e5514419d lib/db: Fix some omitted error checks, unused variable (#7489) 2021-03-17 21:41:07 +01:00
tomasz1986
f014b7b919 gui: Improve Receive Encrypted folder type requirements text (#7473)
The current text gives an impression that we are currently using a
Receive Encrypted folder, even if we are not. Thus, make the current
text displayed only when the folder is in fact Receive Encrypted, and
add a new string to be displayed when using different folder types.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-03-17 21:36:11 +01:00
Jakob Borg
81484699db lib/model: Actually break puller loop on context cancel (#7492)
Current break does nothing (breaks the select).
2021-03-17 21:34:52 +01:00
Jakob Borg
6d93d9c488 deepsource: Test patterns 2021-03-17 21:12:51 +01:00
deepsource-autofix[bot]
0930bccf88 cmd/ursrv, lib/scanner: Remove unnecessary slicing of slices (#7491)
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2021-03-17 21:04:36 +01:00
Jakob Borg
e321bd3941 lib/*/auto: Add noassets files (#7490)
This adds a couple of dummy asset files protected by the "noassets"
build tag. The purpose is that it should be possible for, for example,
CI tools and static analysis things to compile and analyze the source
tree without our custom asset generation step. Also makes `go test -tags
noassets ./...` work without building assets first.
2021-03-17 21:03:35 +01:00
Simon Frei
4b02937862 lib/model: Add missing lock on availability func (#7487) 2021-03-17 20:46:13 +01:00
Jakob Borg
21e6849f2d Add DeepSource for static analysis 2021-03-17 20:05:20 +01:00
Simon Frei
39c2d1bc1a cmd/syncthing: Check os.Args length (fixes #7486) 2021-03-17 15:51:14 +01:00
Simon Frei
cd21b8dfa5 cmd/syncthing/cli: Remove db reset and adjust others (#7484) 2021-03-17 09:06:37 +01:00
Simon Frei
40fbdc87ce cmd/syncthing: Refactor cli subcommand to allow flags (#7454) 2021-03-17 09:04:50 +01:00
Jakob Borg
1814f4693d gui, man, authors: Update docs, translations, and contributors 2021-03-17 07:45:40 +01:00
Simon Frei
3f2b584c4e lib/model: Don't use ignore patterns for recv-enc folders (fixes #7469) (#7472) 2021-03-16 15:04:11 +01:00
Jakob Borg
e0dd737822 gui: Fix validation and help texts in versioning editor (fixes #7481) (#7482) 2021-03-16 09:30:07 +01:00
Simon Frei
d2d4fcc1df lib/protocol: Improve messages when an error occurs receiving (ref #7466) (#7470) 2021-03-15 19:14:09 +01:00
Simon Frei
273ee09925 lib/db, lib/model: Allow needing invalid files (fixes #7474) (#7476) 2021-03-15 07:58:01 +01:00
tomasz1986
bb886868d2 gui: Use grey background for disabled options in form-control (#7468)
Disabled options are currently barely distinguishable from enabled
ones. This changes their background to grey, following the Bootstrap
defaults already used for disabled <select>.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-03-14 08:14:19 +01:00
Simon Frei
f80ee472c2 lib/protocol: Set invalid flag on encrypted file infos (fixes #7466) (#7467) 2021-03-13 16:57:36 +01:00
tomasz1986
a12ede3bbe gui: Fix missing trashcanClean in simple versioning (#7465) 2021-03-13 10:14:26 +01:00
Simon Frei
97a8777d03 lib/fs: Check both old and new path when renaming (fixes #7426) (#7463) 2021-03-12 21:15:50 +01:00
Simon Frei
8a4c00d82e lib/model: Send failure report on CC encryption check error (#7460) 2021-03-12 12:21:54 +01:00
Simon Frei
31f859e909 lib/model: Return correct error in puller-iteration (ref #7424) (#7461) 2021-03-12 12:21:28 +01:00
Jakob Borg
4d979a1ce9 all: Truncate some timestamps (fixes #7457) (#7459)
This truncates times meant for API consumption to second precision,
where fractions won't typically matter or add any value. Exception to
this is timestamps on logs and events, and of course I'm not touching
things like file metadata.

I'm not 100% certain this is an exhaustive change, but it's the things I
found by grepping and following the breadcrumbs from lib/api...

I also considered general-but-ugly solutions, like having the API
serializer itself do reflection magic or even regexps on returned
objects, but decided against it because aurgh...
2021-03-12 10:35:10 +01:00
Simon Frei
4465cdf8bc lib/api: Fix body of renamed config/restart-required endpoint (ref #7402) (#7453) 2021-03-11 15:54:05 +01:00
Simon Frei
3938b61c3f lib/fs: Expose fs option on interface (fixes #7385, ref #7381) (#7389) 2021-03-11 15:23:56 +01:00
Jakob Borg
cdef503db6 all: Make config.Wrapper an actual suture.Service (fixes #7451) (#7452) 2021-03-11 14:51:00 +01:00
Jakob Borg
df08984a58 lib/api: Sanitize names used in certificates (fixes #7434) (#7435) 2021-03-11 13:15:03 +01:00
Simon Frei
cf838c71f7 gui: Check if versioning object exists (fixes #7449) (#7450) 2021-03-11 11:21:35 +01:00
Simon Frei
9a001051d6 cmd/ursrv, lib/ur: Collect and present encryption usage (#7448) 2021-03-10 22:26:56 +00:00
Jakob Borg
5548a8eb7a gui, man, authors: Update docs, translations, and contributors 2021-03-10 07:45:45 +01:00
Simon Frei
727df34aa1 cmd/syncthing: Correct name of HiddenConsole flag (fixes #7446) (#7447) 2021-03-10 07:31:31 +01:00
Simon Frei
9587a523b3 build: Update notify (#7444) 2021-03-08 13:36:03 +01:00
Jakob Borg
22e44642a0 next-gen-gui: Add button to restore default theme (#7433)
This adds a button in the top right that changes the config back to the
default theme.

Code wise, it takes the header that was previously a part of the
dashboard component and moves it to the app component, and then adds the
button there. Possibly the header should be a component of it's own, but
that's more of refactor that can happen separately I think.

The config change uses the new config API to just patch the relevant
setting.

I'm not doing an automatic reload because 1) I don't want to figure out
how to do it correctly and 2) this doesn't work reliably anyway, as
for example the current gen GUI does a reload and ends up with
connection refused as the API service is still reloading...
2021-03-08 12:54:08 +01:00
greatroar
c00520281b lib/protocol: Optimize FileKey (#7440) 2021-03-07 18:44:21 +01:00
Simon Frei
587c89d979 cmd/syncthing, etc: Consistent failure restart backoff (#7439) 2021-03-07 15:25:55 +01:00
Simon Frei
310fba4c12 lib: Return error from db.FileSet.Snapshot (fixes #7419, ref #5907) (#7424) 2021-03-07 13:43:22 +01:00
bt90
c1d06d9501 build: Package sysctl configuration to raise UDP buffer size on Linux (#7417)
* Provide a sysctl config to raise max UDP buffer size

* Add sysctl config to deb

* Check if `deb-systemd-invoke` is available

Co-authored-by: otbutz <tbutz@optitool.de>
2021-03-05 08:08:29 +01:00
Simon Frei
4735575e8d cmd/syncthing: Set flag defaults through kong vars (#7431) 2021-03-05 08:06:54 +01:00
Simon Frei
767e1c6f58 lib/connections: Expose SetReadBuffer on conn passed to quic (ref #7417) (#7432) 2021-03-05 08:06:37 +01:00
Simon Frei
83fcb49894 gui: Apply #7430 to untrusted 2021-03-04 21:09:18 +01:00
André Colomb
5fd88481b6 gui: Fix hidden Ignore Patterns tab for folder editing (fixes #7429) (#7430) 2021-03-04 21:08:15 +01:00
Simon Frei
69c63e94bf scripts: Remove unused import (ref #7375) 2021-03-03 09:00:43 +01:00
Simon Frei
3d91f7c975 lib: Use counterfeiter to mock interfaces in tests (#7375) 2021-03-03 08:53:50 +01:00
Jakob Borg
7945430e64 gui, man, authors: Update docs, translations, and contributors 2021-03-03 07:45:37 +01:00
bt90
e2120c4728 docker: Expose 22000/udp (#7421)
Co-authored-by: otbutz <tbutz@optitool.de>
2021-03-02 20:42:25 +01:00
greatroar
56b5352f64 all: Use crypt/rand through its buffered version, but not in benchmarks (#7420) 2021-03-02 19:17:20 +01:00
Jakob Borg
55d5e03639 lib/db: Remove Badger experiment (#7413) 2021-03-01 09:16:08 +01:00
tomasz1986
cca17f5306 gui: Allow to resize command in External Versioning (#7410) 2021-02-27 09:41:16 +01:00
greatroar
ffcaffa32f lib/protocol: Optimize encrypted filename handling + make it more strict (#7408) 2021-02-27 08:57:12 +01:00
Simon Frei
0ffd80f380 lib/protocol: Alwasy return buffers to the pool (#7409) 2021-02-27 08:55:51 +01:00
wouter bolsterlee
25151b14e7 lib/api: Treat *.localhost as valid localhost addresses (#7412) (ref #4815)
This loosens the ‘is this localhost?’ check to include *.localhost host
names.

This allows for clearer (hence better) names to be used in browsers,
e.g. when accessing a remote syncthing instance ‘foo’ using a ssh port
forward, one can use foo.localhost to remind oneself which one is which.
💡 Without these changes, Syncthing shows a ‘Host check error’ when
pointing a browser at http://foo.localhost/, and with these changes, the
interface loads as usual.

The .localhost top level domain is a reserved top-level domain (RFC 2606):

> The ".localhost" TLD has traditionally been statically defined in
> host DNS implementations as having an A record pointing to the
> loop back IP address and is reserved for such use.  Any other use
> would conflict with widely deployed code which assumes this use.
> – https://tools.ietf.org/html/rfc2606

As Wikipedia puts it:

> This allows the use of these names for either documentation purposes
or in local testing scenarios. – https://en.wikipedia.org/wiki/.localhost

On Linux systems, systemd-resolved resolves *.localhost, on purpose:
https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html

See also #4815, #4816.
2021-02-27 08:52:49 +01:00
Simon Frei
428c5c02ce Merge branch 'release' 2021-02-26 13:47:03 +01:00
Simon Frei
fff8805ff6 all: Fix versioning path handling (#7407) 2021-02-26 12:04:05 +01:00
Simon Frei
a69afc9eeb gui: Folder versioning editing cleanup (#7384) 2021-02-25 18:24:18 +01:00
Simon Frei
0bf9645f2f lib/api: Rename config insync endpoint to restart-required (#7402) 2021-02-25 09:29:44 +01:00
Steven Eckhoff
6bfad8fce8 build: Make stupgrades build target conditional (fixes #7199) (#7404) 2021-02-25 09:29:14 +01:00
Jakob Borg
6ebab5db07 gui, man, authors: Update docs, translations, and contributors 2021-02-24 07:45:23 +01:00
bt90
34aa89a7d6 Allow QUIC traffic (#7400) 2021-02-23 20:39:03 +00:00
Jakob Borg
44e4f754dd Merge branch 'release'
* release:
  all: Fix Microsoft documentation links in code comments (#7387)
  gui: Handle info labels that are longer than available space (fixes #944) (#7386)
  gui: Show "Last seen" at the top when device is disconnected (ref #7166) (#7373)
  gui: Remove cleanupIntervalS leftovers from External Versioning (ref #7360) (#7378)
  gui: Allow setting custom path for all versioning except external (#7377)
  lib/fs: Consider options in case-fs caching (fixes #7371) (#7381)
  lib/scanner: Pass on errors while hashing (#7380)
  build: Update pfilter (#7376)
  gui: Hide the Rescan All button when no folders exist (#7367)
  lib/connections: Allow QUIC with Go 1.16 (#7372)
  gui: Hide non-functional Versions button when using External Versioning (#7365)
  gui, man, authors: Update docs, translations, and contributors
  gui: Fix setting external versioning command (fixes #7361) (#7362)
  lib/versioner: Improve error messages (fixes #7354) (#7357)
  gui: Disable Cleanup Interval with External File Versioning as unsupported (#7360)
2021-02-23 07:37:07 +01:00
tomasz1986
1ed0116147 all: Fix Microsoft documentation links in code comments (#7387) 2021-02-20 14:56:45 +01:00
tomasz1986
c5663689a3 gui: Handle info labels that are longer than available space (fixes #944) (#7386)
Apply to table headers the same code as already used for table data.
This way, the headers will be either pushed to the next line, or cut
with an ellipsis if the single word is too long.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-02-20 10:28:08 +00:00
tomasz1986
9caaaa49b6 gui: Show "Last seen" at the top when device is disconnected (ref #7166) (#7373)
Move the "Last seen" field to the very top in the device information.
This way, if a device has disconnected unexpectly, we can quickly check
the time when it was last available. Right now, due to the very long
address field, it is usually necessary to scroll down in order to view
the "Last seen" field.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-02-20 10:26:34 +00:00
otbutz
c18dc6a629 cmd/strelaysrv: Load map tiles over HTTPS (#7382) 2021-02-19 22:07:05 +01:00
tomasz1986
b1fbd87680 gui: Remove cleanupIntervalS leftovers from External Versioning (ref #7360) (#7378) 2021-02-19 17:49:56 +01:00
tomasz1986
be630691b1 gui: Allow setting custom path for all versioning except external (#7377) 2021-02-19 17:48:46 +01:00
Simon Frei
aa1c274231 lib/fs: Consider options in case-fs caching (fixes #7371) (#7381) 2021-02-19 11:06:25 +01:00
Simon Frei
78c2844e3f lib/scanner: Pass on errors while hashing (#7380) 2021-02-19 08:51:39 +01:00
Simon Frei
57a7f4391f build: Update pfilter (#7376) 2021-02-18 15:09:46 +00:00
Simon Frei
0970aed596 cmd/stcrashreceiver: Add tag for report type (crash/failure) (#7374) 2021-02-18 13:16:32 +00:00
tomasz1986
342ade501a gui: Hide the Rescan All button when no folders exist (#7367)
If there are no folders present, show only the "Add Folder" button, and
hide the "Rescan All" button. Only show the latter when at least one
folder exists.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-02-17 18:02:55 +01:00
Jakob Borg
327604719a lib/connections: Allow QUIC with Go 1.16 (#7372) 2021-02-17 11:09:16 +01:00
tomasz1986
e8ef586cef gui: Hide non-functional Versions button when using External Versioning (#7365)
The button does nothing when the External Versioning is being used, so
it should not be displayed at all to avoid confusing the users.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-02-17 08:29:25 +01:00
Jakob Borg
c014fc5ec5 gui, man, authors: Update docs, translations, and contributors 2021-02-17 07:45:43 +01:00
Audrius Butkevicius
fb078068b4 cmd/syncthing: Add cli as a subcommand (fixes #6566, fixes #4719) (#7364)
* cmd/syncthing: Add cli as a subcommand (fixes #6566, fixes #4719)

* Hijack help

* Add comment

* Revert go.mod/go.sum
2021-02-15 18:50:53 +01:00
Jakob Borg
b2c9e7b07b build: Add build process for newgui (#7351) 2021-02-15 14:52:28 +01:00
Simon Frei
d117b4b570 gui: Fix setting external versioning command (fixes #7361) (#7362) 2021-02-14 09:54:49 +01:00
Simon Frei
80fc238bec all: Automatic/disabled folder-config when receive-encrypted (#7327) 2021-02-12 22:51:29 +01:00
Simon Frei
7e4e2f3720 lib/versioner: Improve error messages (fixes #7354) (#7357) 2021-02-12 20:30:51 +01:00
tomasz1986
5e9c7bc022 gui: Disable Cleanup Interval with External File Versioning as unsupported (#7360)
As for right now, the External File Versioning does not support Cleanup
Interval. Therefore, the option should no be available at all when using
it.

Ref: https://forum.syncthing.net/t/16346

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-02-12 17:32:25 +00:00
Jakob Borg
55afa625fc cmd/syncthing: Add decrypt subcommand (#7332)
This adds the `syncthing decrypt` subcommand that is used to
(offline-)decrypt or just verify the contents of an encrypted folder.
2021-02-12 08:38:43 +01:00
Simon Frei
f2e9b40ad1 etc: Adjust all the startup scripts to new cmd (ref #7330) (#7353) 2021-02-11 12:18:47 +01:00
Simon Frei
6697b8fde3 cmd/syncthing: Fix hyphenated flag names (ref #7330) (#7352) 2021-02-11 11:37:48 +01:00
Jakob Borg
4f20c900d0 cmd/syncthing: Refactor command line parsing (#7330) 2021-02-10 20:35:37 +01:00
Jakob Borg
0471daf771 newgui: Merge separate repo into syncthing/syncthing
Co-authored-by: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Co-authored-by: Simon Frei <freisim93@gmail.com>
2021-02-10 14:04:28 +01:00
Jakob Borg
5788ad2529 Reassign copyright to the Syncthing Authors 2021-02-10 13:58:51 +01:00
Jakob Borg
472affb6a3 Move all into newgui/ 2021-02-10 13:58:08 +01:00
Jakob Borg
777a30e870 Add usage notes and screenshot 2020-04-17 09:34:31 +02:00
Jakob Borg
aea66ff25a v1.0.0 2020-04-17 09:11:01 +02:00
Jesse Lucas
9dd319f4da status-list: update styling, adding selected and hover states 2020-04-14 21:00:48 -04:00
Jesse Lucas
8b4a1f52d0 update folder devices array for the "shared with" section in the folder list #CTR-2 2020-04-14 21:00:48 -04:00
Jesse Lucas
7a9f317ee1 enable expandable rows for devices list 2020-04-14 21:00:48 -04:00
Jesse Lucas
48efea99e5 add trim pipe 2020-04-14 21:00:48 -04:00
Jesse Lucas
fbef856a97 Remove unused columns 2020-04-14 21:00:48 -04:00
Jesse Lucas
3b7c760ffd start of expandable rows for folder list 2020-04-14 21:00:48 -04:00
Jesse Lucas
ae776ff8df Rework device and folder service to have public obseravble
to accomaccommodatemodate many subscribers without re-requesting data
from the API
2020-04-14 21:00:48 -04:00
Jesse Lucas
24a637e9e6 Change device/folder getAll to getEach 2020-04-14 21:00:48 -04:00
Jesse Lucas
f53475a204 use replay subject in place of timer 2020-04-14 21:00:48 -04:00
Jakob Borg
3fbc51cd38 Skip animations on donut charts 2020-04-14 16:29:52 +02:00
Jesse Lucas
5f1aba9a37 adjusting styles 2020-04-09 13:01:06 -04:00
Jesse Lucas
95f7a26bce style dialog component and close button 2020-04-09 13:01:06 -04:00
Jesse Lucas
6e1828ff63 support dark mode and add custom card component 2020-04-09 13:01:06 -04:00
Jesse Lucas
89c53c508b consolidate retry logic in error interceptor
remove retry operator from services
2020-04-09 13:01:06 -04:00
Jesse Lucas
9918cb4ffc Add MatDialog and use messageService to display errors 2020-04-09 13:01:06 -04:00
Jesse Lucas
7b61f800c3 add missing semicolon 2020-04-09 13:01:06 -04:00
Jesse Lucas
33d47063e8 remove unused data binding 2020-04-09 13:01:06 -04:00
Jesse Lucas
e67b91977d remove console logs 2020-04-09 13:01:06 -04:00
Jesse Lucas
ac603e9228 start of message service 2020-04-09 13:01:06 -04:00
Jesse Lucas
632b6f8fbd start of dialog component 2020-04-09 13:01:06 -04:00
Jesse Lucas
b247ef2632 start of error interceptor 2020-04-09 13:01:06 -04:00
Jesse Lucas
5a6d79a66e Recalculate completion for devices and update total properties 2020-04-06 14:30:44 -04:00
Jesse Lucas
1e33cc9720 update progress service with device/folders loaded and add animation 2020-04-05 11:50:05 -04:00
Jesse Lucas
ee465c0890 update progress service with get percentValue and table tests 2020-04-05 11:50:05 -04:00
Jesse Lucas
236816cb93 Add imports and providers to enable basic testing 2020-04-05 11:50:05 -04:00
Jesse Lucas
3ee9bc097f Update tech-ui-blue style to have contrast 2020-04-05 11:50:05 -04:00
Jesse Lucas
f205ce14c1 start of progress indicator and animation 2020-04-05 11:50:05 -04:00
Jesse Lucas
8199e0a7a9 update angular dependencies 2020-04-05 11:50:05 -04:00
Jakob Borg
f18fc40436 Harmonize license with Syncthing proper 2020-04-03 22:28:36 +02:00
Jesse Lucas
f509c65509 increase chart item padding 2020-04-03 10:25:19 -04:00
Jesse Lucas
bf062db83f create chart item state to toggle filter selection 2020-04-03 09:20:27 -05:00
Jesse Lucas
9907523321 switch filter to Subject from BehaviorSubject 2020-04-03 09:20:27 -05:00
Jesse Lucas
915faabe25 Filter lists when donut chart items are clicked 2020-04-03 09:20:27 -05:00
Jesse Lucas
a9b6801b22 Store last filtered value between toggling of lists 2020-04-03 09:20:27 -05:00
Jesse Lucas
d76f1fe356 Style chart item 2020-04-03 09:20:27 -05:00
Jesse Lucas
3bdceb1d6b add filter service and enable chart states to filter lists 2020-04-03 09:20:27 -05:00
Jesse Lucas
cce8b60515 adjust filter style and add filter to device list 2020-03-31 20:40:36 -04:00
Jesse Lucas
06eacb87d8 Add additional columns, styling and data to lists 2020-03-31 20:40:36 -04:00
Jesse Lucas
865f1f2ea6 reduce mock data delay for testing 2020-03-31 20:40:36 -04:00
Jesse Lucas
f59a26cb80 relative path apiURL 2020-03-31 11:41:59 -04:00
Jesse Lucas
d384ac52a1 use relative path for base-href and meta.js 2020-03-31 11:41:59 -04:00
Jesse Lucas
42fa03aa4a start of list filtering 2020-03-31 11:41:59 -04:00
Jesse Lucas
dae1f990a5 Combine types 2020-03-31 11:41:59 -04:00
Jakob Borg
33398b0b6b Also mention STGUIASSETS 2020-03-31 16:14:09 +02:00
Jakob Borg
192e843989 README/LICENSE 2020-03-31 15:55:39 +02:00
Jesse Lucas
8851f4571c Add header and logo 2020-03-30 14:48:53 -04:00
Jesse Lucas
7fed8334b9 Use a smaller font when device or folder counts are above 4 digits 2020-03-30 14:48:53 -04:00
Jesse Lucas
6af2657d7e Combine folder and device chart component into chart component 2020-03-30 14:48:53 -04:00
Jesse Lucas
933a57af7a Add title to donut chart and media queries 2020-03-30 11:54:54 -04:00
Jesse Lucas
e5b85ff1a0 update angular cli, devkit and node 2020-03-30 11:54:24 -04:00
Jesse Lucas
c382aa04e4 right justify chart items 2020-03-29 17:45:08 -04:00
Jesse Lucas
86141c1eef enable chart canvas responsiveness 2020-03-29 16:51:12 -04:00
Jesse Lucas
d67c9b66d3 determine chart color based on state type 2020-03-29 16:51:12 -04:00
Jesse Lucas
6158b20a8c Style donut chart and add total count 2020-03-29 16:51:12 -04:00
Jesse Lucas
485a234263 Use flexbox for dashboard layout 2020-03-29 16:51:12 -04:00
Jesse Lucas
7226a87c6d Create tech ui card css to replace material cards 2020-03-29 16:51:12 -04:00
Jesse Lucas
7aca2bcbc0 add custom chartjs tooltip 2020-03-29 16:51:12 -04:00
Jesse Lucas
3840d57f8d temporarily handle current device in device list 2020-03-29 16:51:12 -04:00
Jesse Lucas
b7f3425f36 add system status service and mock data 2020-03-29 16:51:12 -04:00
Jesse Lucas
9fe0c30299 update mock names 2020-03-29 16:51:12 -04:00
Jesse Lucas
9d126760fa Synchronously get the status of each device 2020-03-29 16:51:12 -04:00
Jesse Lucas
09673ba0c6 create db completion service and mock data 2020-03-29 16:51:12 -04:00
Jesse Lucas
bc7d71ee3d Create system connections service, mocks, and work on updating devices chart 2020-03-29 16:51:12 -04:00
Jesse Lucas
78a31449aa create Device namespace and getStateType function 2020-03-29 16:51:12 -04:00
Jesse Lucas
0361d303f2 organize services into services folder 2020-03-29 16:51:12 -04:00
Jesse Lucas
2fe94736e6 update device chart to use donut chart updateData 2020-03-29 16:51:12 -04:00
Jesse Lucas
9589f25e57 refactor to use caching interceptor 2020-03-29 16:51:12 -04:00
Jesse Lucas
15c2556e06 fix conditional logic 2020-03-29 16:51:12 -04:00
Jesse Lucas
f342a2a54b refactor to use array of objects to map state data to chart update 2020-03-29 16:51:12 -04:00
Jesse Lucas
598dc991e8 complete observers 2020-03-29 16:51:12 -04:00
Jesse Lucas
7d59dc6c18 add updateData and removeAllData methods to donut chart 2020-03-29 16:51:12 -04:00
Jesse Lucas
05f01a5d94 start of caching, csrf interceptor, and device service 2020-03-29 16:51:12 -04:00
Jesse Lucas
03c6fb2f82 Rename folders 2020-03-29 16:51:12 -04:00
Jesse Lucas
7a657bf3c7 Create states map and set chart-items based on map 2020-03-29 16:51:12 -04:00
Jesse Lucas
02d14c7e9b Add chart item component, StateType and getStateType to determine state from Folder 2020-03-29 16:51:12 -04:00
Jesse Lucas
32c849e18c Refactor Folder interface to use Folder namespace 2020-03-29 16:51:12 -04:00
Jesse Lucas
c27fe5694d Update mock data 2020-03-29 16:51:12 -04:00
Jesse Lucas
b0ebf56283 Add new component chart-item 2020-03-29 16:51:12 -04:00
Jesse Lucas
fb33a8ad6b Remove unused tags in index.html 2020-03-29 16:51:12 -04:00
Jesse Lucas
9207562545 refactor status toggle to list toggle 2020-03-29 16:51:12 -04:00
Jesse Lucas
230eb20a34 update font styles 2020-03-29 16:51:12 -04:00
Jesse Lucas
e0d9542bfe update status list template and add flex box 2020-03-29 16:51:12 -04:00
Jesse Lucas
063951cffa Synchronously get the status of each folder 2020-03-29 16:51:12 -04:00
Jesse Lucas
dae0872379 Remove Folder object from array in development only 2020-03-29 16:51:12 -04:00
Jesse Lucas
123f4a6d9d Update mock data 2020-03-29 16:51:12 -04:00
Jesse Lucas
65679892b0 Don't encapsulate data for in memory services for development 2020-03-29 16:51:12 -04:00
Jesse Lucas
c4979cf6d6 update readme to disable syncthing upgrade 2020-03-29 16:51:12 -04:00
Jesse Lucas
09d498bb80 Refactor charts to use @ViewChild instead of @Input 2020-03-29 16:51:12 -04:00
Jesse Lucas
90ed7aa121 add apiRetry to api-utils 2020-03-29 16:51:12 -04:00
Jesse Lucas
206c0d0933 Work on db status service 2020-03-29 16:51:12 -04:00
Jesse Lucas
81c539c516 new folder service 2020-03-29 16:51:12 -04:00
Jesse Lucas
75b3b31d11 create folder status interface 2020-03-29 16:51:12 -04:00
Jesse Lucas
90c39de0cc add system config service retry 2020-03-29 16:51:12 -04:00
Jesse Lucas
241864b43c create mock data 2020-03-29 16:51:12 -04:00
Jesse Lucas
b2d839d4e7 fix race condition with Observables in list components 2020-03-29 16:51:12 -04:00
Jesse Lucas
8bdf409d07 start of db status service 2020-03-29 16:51:12 -04:00
Jesse Lucas
08da982de5 pass and add data to donut chart 2020-03-29 16:51:12 -04:00
Jesse Lucas
f250425ef1 clean up mock data and update device interface 2020-03-29 16:51:12 -04:00
Jesse Lucas
aebcc495f9 create style.ts to set elevation for all dashboard components 2020-03-29 16:51:12 -04:00
Jesse Lucas
eaffbc0f92 refactor chart and list component structure 2020-03-29 16:51:12 -04:00
Jesse Lucas
37f2f2cdf6 add delay to in memory service and update check interval 2020-03-29 16:51:12 -04:00
Jesse Lucas
82f5706350 start of chart component 2020-03-29 16:51:12 -04:00
Jesse Lucas
ad2902b3ee start of dashboard component with flexbox 2020-03-15 17:19:57 -04:00
Jesse Lucas
ce2c6c01da update mock config data and service 2020-03-15 17:19:57 -04:00
Jesse Lucas
fb4c7d288c update system config url and http options 2020-03-15 12:47:24 -04:00
Jesse Lucas
838b2a6a34 add mock in-memory data service 2020-03-15 12:47:24 -04:00
Jesse Lucas
fd0de9eb6c update readme to indicate build and syncthing tech-ui branch 2020-03-14 22:38:41 -04:00
Jesse Lucas
6b89991ba8 create cookie service to find CSRF header data 2020-03-14 20:11:05 -04:00
Jesse Lucas
4727570870 create api-utils to hold convenience convenience data structures 2020-03-14 20:11:05 -04:00
Jesse Lucas
ebf0541385 start of CSRF 2020-03-14 20:11:05 -04:00
Jesse Lucas
af3d380b6a update SystemConfigService to load config and set folders and devices
update getFolders and getDevices to send respective array as soon as it has data
2020-03-14 20:11:05 -04:00
Jesse Lucas
cf3de8cf35 toggle status lists 2020-03-11 21:24:02 -04:00
Jesse Lucas
61b4de581d create folder and device list components
refactor status-list to contain device and folder list
2020-03-11 21:24:02 -04:00
Jesse Lucas
031fd72dfd start of toggle to switch between devices and folders data table 2020-03-11 21:24:02 -04:00
Jesse Lucas
1d249a877e start of StatusList component 2020-03-10 22:51:50 -04:00
Jesse Lucas
3eb6045dee initial commit 2020-03-10 10:57:02 -04:00
322 changed files with 54617 additions and 3421 deletions

12
.deepsource.toml Normal file
View File

@@ -0,0 +1,12 @@
version = 1
exclude_patterns = ["*.pb.go"]
test_patterns = ["*_test.go"]
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_paths = ["github.com/syncthing/syncthing"]
build_tags = ["noassets"]

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ deb
*.bz2
/repos
/proto/scripts/protoc-gen-gosyncthing
/gui/next-gen-gui

View File

@@ -56,6 +56,7 @@ Boris Rybalkin <ribalkin@gmail.com>
Brandon Philips (philips) <brandon@ifup.org>
Brendan Long (brendanlong) <self@brendanlong.com>
Brian R. Becker (brbecker) <brbecker@gmail.com>
bt90 <btom1990@googlemail.com>
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
@@ -235,6 +236,7 @@ Simon Mwepu <simonmwepu@gmail.com>
Sly_tom_cat <slytomcat@mail.ru>
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
Thomas Hipp <thomashipp@gmail.com>
@@ -256,6 +258,7 @@ Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
Vladimir Rusinov <vrusinov@google.com>
wangguoliang <liangcszzu@163.com>
William A. Kennington III (wkennington) <william@wkennington.com>
wouter bolsterlee <wouter@bolsterl.ee>
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
xarx00 <xarx00@users.noreply.github.com>
Xavier O. (damajor) <damajor@gmail.com>

View File

@@ -11,7 +11,7 @@ RUN rm -f syncthing && go run build.go -no-upgrade build syncthing
FROM alpine
EXPOSE 8384 22000 21027/udp
EXPOSE 8384 22000/tcp 22000/udp 21027/udp
VOLUME ["/var/syncthing"]

View File

@@ -1,7 +1,7 @@
FROM alpine
ARG TARGETARCH
EXPOSE 8384 22000 21027/udp
EXPOSE 8384 22000/tcp 22000/udp 21027/udp
VOLUME ["/var/syncthing"]

View File

@@ -13,7 +13,7 @@ altered with the ``PUID`` and ``PGID`` environment variables.
```
$ docker pull syncthing/syncthing
$ docker run -p 8384:8384 -p 22000:22000 \
$ docker run -p 8384:8384 -p 22000:22000/tcp -p 22000:22000/udp \
-v /wherever/st-sync:/var/syncthing \
syncthing/syncthing:latest
```

110
build.go
View File

@@ -34,23 +34,24 @@ import (
)
var (
goarch string
goos string
noupgrade bool
version string
goCmd string
race bool
debug = os.Getenv("BUILDDEBUG") != ""
extraTags string
installSuffix string
pkgdir string
cc string
run string
benchRun string
debugBinary bool
coverage bool
timeout = "120s"
numVersions = 5
goarch string
goos string
noupgrade bool
version string
goCmd string
race bool
debug = os.Getenv("BUILDDEBUG") != ""
extraTags string
installSuffix string
pkgdir string
cc string
run string
benchRun string
debugBinary bool
coverage bool
timeout = "120s"
numVersions = 5
withNextGenGUI = os.Getenv("BUILD_NEXT_GEN_GUI") != ""
)
type target struct {
@@ -114,6 +115,7 @@ var targets = map[string]target{
{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
{src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
{src: "etc/linux-sysctl/30-syncthing.conf", dst: "deb/usr/lib/sysctl.d/30-syncthing.conf", perm: 0644},
{src: "etc/firewall-ufw/syncthing", dst: "deb/etc/ufw/applications.d/syncthing", perm: 0644},
{src: "etc/linux-desktop/syncthing-start.desktop", dst: "deb/usr/share/applications/syncthing-start.desktop", perm: 0644},
{src: "etc/linux-desktop/syncthing-ui.desktop", dst: "deb/usr/share/applications/syncthing-ui.desktop", perm: 0644},
@@ -216,7 +218,7 @@ var dependencyRepos = []dependencyRepo{
{path: "xdr", repo: "https://github.com/calmh/xdr.git", commit: "08e072f9cb16"},
}
func init() {
func initTargets() {
all := targets["all"]
pkgs, _ := filepath.Glob("cmd/*")
for _, pkg := range pkgs {
@@ -225,6 +227,9 @@ func init() {
// ignore dotfiles
continue
}
if noupgrade && pkg == "stupgrades" {
continue
}
all.buildPkgs = append(all.buildPkgs, fmt.Sprintf("github.com/syncthing/syncthing/cmd/%s", pkg))
}
targets["all"] = all
@@ -256,6 +261,8 @@ func main() {
}()
}
initTargets()
// Invoking build.go with no parameters at all builds everything (incrementally),
// which is what you want for maximum error checking during development.
if flag.NArg() == 0 {
@@ -310,6 +317,9 @@ func runCommand(cmd string, target target) {
case "proto":
proto()
case "testmocks":
testmocks()
case "translate":
translate()
@@ -372,6 +382,7 @@ func parseFlags() {
flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command")
flag.StringVar(&run, "run", "", "Specify which tests to run")
flag.StringVar(&benchRun, "bench", "", "Specify which benchmarks to run")
flag.BoolVar(&withNextGenGUI, "with-next-gen-gui", withNextGenGUI, "Also build 'newgui'")
flag.Parse()
}
@@ -438,6 +449,10 @@ func benchArgs() []string {
}
func install(target target, tags []string) {
if (target.name == "syncthing" || target.name == "") && !withNextGenGUI {
log.Println("Notice: Next generation GUI will not be built; see --with-next-gen-gui.")
}
lazyRebuildAssets()
tags = append(target.tags, tags...)
@@ -467,6 +482,10 @@ func install(target target, tags []string) {
}
func build(target target, tags []string) {
if (target.name == "syncthing" || target.name == "") && !withNextGenGUI {
log.Println("Notice: Next generation GUI will not be built; see --with-next-gen-gui.")
}
lazyRebuildAssets()
tags = append(target.tags, tags...)
@@ -764,11 +783,46 @@ func rebuildAssets() {
}
func lazyRebuildAssets() {
if shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") || shouldRebuildAssets("cmd/strelaypoolsrv/auto/gui.files.go", "cmd/strelaypoolsrv/gui") {
shouldRebuild := shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") ||
shouldRebuildAssets("cmd/strelaypoolsrv/auto/gui.files.go", "cmd/strelaypoolsrv/gui")
if withNextGenGUI {
shouldRebuild = buildNextGenGUI() || shouldRebuild
}
if shouldRebuild {
rebuildAssets()
}
}
func buildNextGenGUI() bool {
// Check if we need to run the npm process, and if so also set the flag
// to rebuild Go assets afterwards. The index.html is regenerated every
// time by the build process. This assumes the new GUI ends up in
// next-gen-gui/dist/next-gen-gui.
if !shouldRebuildAssets("gui/next-gen-gui/index.html", "next-gen-gui") {
// The GUI is up to date.
return false
}
runPrintInDir("next-gen-gui", "npm", "install")
runPrintInDir("next-gen-gui", "npm", "run", "build", "--", "--prod", "--subresource-integrity")
rmr("gui/tech-ui")
for _, src := range listFiles("next-gen-gui/dist") {
rel, _ := filepath.Rel("next-gen-gui/dist", src)
dst := filepath.Join("gui", rel)
if err := copyFile(src, dst, 0644); err != nil {
fmt.Println("copy:", err)
os.Exit(1)
}
}
return true
}
func shouldRebuildAssets(target, srcdir string) bool {
info, err := os.Stat(target)
if err != nil {
@@ -812,10 +866,26 @@ func proto() {
}
runPrintInDir(path, "git", "checkout", dep.commit)
}
runPrint(goCmd, "generate", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/stdiscosrv")
runPrint(goCmd, "generate", "github.com/syncthing/syncthing/cmd/stdiscosrv")
runPrint(goCmd, "generate", "proto/generate.go")
}
func testmocks() {
runPrint(goCmd, "get", "golang.org/x/tools/cmd/goimports")
runPrint(goCmd, "get", "github.com/maxbrunsfeld/counterfeiter/v6")
args := []string{
"generate",
"github.com/syncthing/syncthing/lib/config",
"github.com/syncthing/syncthing/lib/connections",
"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/protocol",
}
runPrint(goCmd, args...)
}
func translate() {
os.Chdir("gui/default/assets/lang")
runPipe("lang-en-new.json", goCmd, "run", "../../../../script/translate.go", "lang-en.json", "../../../")

View File

@@ -1,192 +0,0 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
import (
"bufio"
"crypto/tls"
"encoding/json"
"flag"
"log"
"os"
"reflect"
"github.com/AudriusButkevicius/recli"
"github.com/flynn-archive/go-shlex"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/urfave/cli"
)
func main() {
// This is somewhat a hack around a chicken and egg problem.
// We need to set the home directory and potentially other flags to know where the syncthing instance is running
// in order to get it's config ... which we then use to construct the actual CLI ... at which point it's too late
// to add flags there...
homeBaseDir := locations.GetBaseDir(locations.ConfigBaseDir)
guiCfg := config.GUIConfiguration{}
flags := flag.NewFlagSet("", flag.ContinueOnError)
flags.StringVar(&guiCfg.RawAddress, "gui-address", guiCfg.RawAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
flags.StringVar(&guiCfg.APIKey, "gui-apikey", guiCfg.APIKey, "Override GUI API key")
flags.StringVar(&homeBaseDir, "home", homeBaseDir, "Set configuration directory")
// Implement the same flags at the lower CLI, with the same default values (pre-parse), but do nothing with them.
// This is so that we could reuse os.Args
fakeFlags := []cli.Flag{
cli.StringFlag{
Name: "gui-address",
Value: guiCfg.RawAddress,
Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
},
cli.StringFlag{
Name: "gui-apikey",
Value: guiCfg.APIKey,
Usage: "Override GUI API key",
},
cli.StringFlag{
Name: "home",
Value: homeBaseDir,
Usage: "Set configuration directory",
},
}
// Do not print usage of these flags, and ignore errors as this can't understand plenty of things
flags.Usage = func() {}
_ = flags.Parse(os.Args[1:])
// Now if the API key and address is not provided (we are not connecting to a remote instance),
// try to rip it out of the config.
if guiCfg.RawAddress == "" && guiCfg.APIKey == "" {
// Update the base directory
err := locations.SetBaseDir(locations.ConfigBaseDir, homeBaseDir)
if err != nil {
log.Fatal(errors.Wrap(err, "setting home"))
}
// Load the certs and get the ID
cert, err := tls.LoadX509KeyPair(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
)
if err != nil {
log.Fatal(errors.Wrap(err, "reading device ID"))
}
myID := protocol.NewDeviceID(cert.Certificate[0])
// Load the config
cfg, _, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
if err != nil {
log.Fatalln(errors.Wrap(err, "loading config"))
}
guiCfg = cfg.GUI()
} else if guiCfg.Address() == "" || guiCfg.APIKey == "" {
log.Fatalln("Both -gui-address and -gui-apikey should be specified")
}
if guiCfg.Address() == "" {
log.Fatalln("Could not find GUI Address")
}
if guiCfg.APIKey == "" {
log.Fatalln("Could not find GUI API key")
}
client := getClient(guiCfg)
cfg, err := getConfig(client)
original := cfg.Copy()
if err != nil {
log.Fatalln(errors.Wrap(err, "getting config"))
}
// Copy the config and set the default flags
recliCfg := recli.DefaultConfig
recliCfg.IDTag.Name = "xml"
recliCfg.SkipTag.Name = "json"
commands, err := recli.New(recliCfg).Construct(&cfg)
if err != nil {
log.Fatalln(errors.Wrap(err, "config reflect"))
}
// Construct the actual CLI
app := cli.NewApp()
app.Name = "stcli"
app.HelpName = app.Name
app.Author = "The Syncthing Authors"
app.Usage = "Syncthing command line interface"
app.Version = build.Version
app.Flags = fakeFlags
app.Metadata = map[string]interface{}{
"client": client,
}
app.Commands = []cli.Command{
{
Name: "config",
HideHelp: true,
Usage: "Configuration modification command group",
Subcommands: commands,
},
showCommand,
operationCommand,
errorsCommand,
}
tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
if !tty {
// Not a TTY, consume from stdin
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input, err := shlex.Split(scanner.Text())
if err != nil {
log.Fatalln(errors.Wrap(err, "parsing input"))
}
if len(input) == 0 {
continue
}
err = app.Run(append(os.Args, input...))
if err != nil {
log.Fatalln(err)
}
}
err = scanner.Err()
if err != nil {
log.Fatalln(err)
}
} else {
err = app.Run(os.Args)
if err != nil {
log.Fatalln(err)
}
}
if !reflect.DeepEqual(cfg, original) {
body, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
log.Fatalln(err)
}
resp, err := client.Post("system/config", string(body))
if err != nil {
log.Fatalln(err)
}
if resp.StatusCode != 200 {
body, err := responseToBArray(resp)
if err != nil {
log.Fatalln(err)
}
log.Fatalln(string(body))
}
}
}

View File

@@ -7,7 +7,6 @@
package main
import (
"crypto/md5"
"errors"
"flag"
"fmt"
@@ -15,6 +14,8 @@ import (
"log"
"os"
"path/filepath"
"github.com/syncthing/syncthing/lib/sha256"
)
func main() {
@@ -74,7 +75,7 @@ type fileInfo struct {
name string
mode os.FileMode
mod int64
hash [16]byte
hash [sha256.Size]byte
}
func (f fileInfo) String() string {
@@ -106,11 +107,7 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
if err != nil {
return err
}
h := md5.New()
h.Write([]byte(tgt))
hash := h.Sum(nil)
copy(f.hash[:], hash)
f.hash = sha256.Sum256([]byte(tgt))
} else if info.IsDir() {
f = fileInfo{
name: rn,
@@ -123,7 +120,7 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
mode: info.Mode(),
mod: info.ModTime().Unix(),
}
sum, err := md5file(path)
sum, err := sha256file(path)
if err != nil {
return err
}
@@ -150,14 +147,14 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
return errc
}
func md5file(fname string) (hash [16]byte, err error) {
func sha256file(fname string) (hash [sha256.Size]byte, err error) {
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
h := md5.New()
h := sha256.New()
io.Copy(h, f)
hb := h.Sum(nil)
copy(hash[:], hb)

View File

@@ -84,7 +84,7 @@ func handleFailureFn(dsn string) func(w http.ResponseWriter, req *http.Request)
return
}
for _, r := range reports {
pkt := packet(version)
pkt := packet(version, "failure")
pkt.Message = r.Description
pkt.Extra = raven.Extra{
"count": r.Count,

View File

@@ -122,7 +122,7 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
}
}
pkt := packet(version)
pkt := packet(version, "crash")
pkt.Message = string(subjectLine)
pkt.Extra = raven.Extra{
"url": reportServer + path,
@@ -229,7 +229,7 @@ func parseVersion(line string) (version, error) {
return v, nil
}
func packet(version version) *raven.Packet {
func packet(version version, reportType string) *raven.Packet {
pkt := &raven.Packet{
Platform: "go",
Release: version.tag,
@@ -242,6 +242,7 @@ func packet(version version) *raven.Packet {
raven.Tag{Key: "goos", Value: version.goos},
raven.Tag{Key: "goarch", Value: version.goarch},
raven.Tag{Key: "builder", Value: version.builder},
raven.Tag{Key: "report_type", Value: reportType},
},
}
if version.commit != "" {

View File

@@ -30,13 +30,7 @@ func main() {
path = filepath.Join(defaultConfigDir(), "index-v0.14.0.db")
}
var ldb backend.Backend
var err error
if looksLikeBadger(path) {
ldb, err = backend.OpenBadger(path)
} else {
ldb, err = backend.OpenLevelDBRO(path)
}
ldb, err := backend.OpenLevelDBRO(path)
if err != nil {
log.Fatal(err)
}
@@ -56,8 +50,3 @@ func main() {
fmt.Println("Unknown mode")
}
}
func looksLikeBadger(path string) bool {
_, err := os.Stat(filepath.Join(path, "KEYREGISTRY"))
return err == nil
}

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//+build noassets
package auto
import "github.com/syncthing/syncthing/lib/assets"
func Assets() map[string]assets.Asset {
return nil
}

View File

@@ -237,7 +237,7 @@
uptimeSeconds: 0,
};
$scope.map = L.map('map').setView([40.90296, 1.90925], 2);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
attribution: 'Leaflet',
maxZoom: 17

View File

@@ -10,7 +10,6 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/syncthing/syncthing/lib/protocol"
"io"
"io/ioutil"
"log"
@@ -23,6 +22,8 @@ import (
"strings"
"time"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/golang/groupcache/lru"
"github.com/oschwald/geoip2-golang"
"github.com/prometheus/client_golang/prometheus"
@@ -475,7 +476,7 @@ func handleRelayTest(request request) {
updateMetrics(request.relay.uri.Host, *stats, location)
}
request.relay.Stats = stats
request.relay.StatsRetrieved = time.Now()
request.relay.StatsRetrieved = time.Now().Truncate(time.Second)
request.relay.Location = location
timer, ok := evictionTimers[request.relay.uri.Host]

View File

@@ -99,7 +99,7 @@ func main() {
flag.IntVar(&natRenewal, "nat-renewal", 30, "NAT renewal frequency in minutes")
flag.IntVar(&natTimeout, "nat-timeout", 10, "NAT discovery timeout in seconds")
flag.BoolVar(&pprofEnabled, "pprof", false, "Enable the built in profiling on the status server")
flag.IntVar(&networkBufferSize, "network-buffer", 2048, "Network buffer size (two of these per proxied connection)")
flag.IntVar(&networkBufferSize, "network-buffer", 65536, "Network buffer size (two of these per proxied connection)")
showVersion := flag.Bool("version", false, "Show version")
flag.Parse()
@@ -186,6 +186,7 @@ func main() {
}
wrapper := config.Wrap("config", config.New(id), id, events.NoopLogger)
go wrapper.Serve(context.TODO())
wrapper.Modify(func(cfg *config.Configuration) {
cfg.Options.NATLeaseM = natLease
cfg.Options.NATRenewalM = natRenewal
@@ -232,6 +233,7 @@ func main() {
uri, err := url.Parse(fmt.Sprintf("relay://%s/?id=%s&pingInterval=%s&networkTimeout=%s&sessionLimitBps=%d&globalLimitBps=%d&statusAddr=%s&providedBy=%s", mapping.Address(), id, pingInterval, networkTimeout, sessionLimitBps, globalLimitBps, statusAddr, providedBy))
if err != nil {
log.Fatalln("Failed to construct URI", err)
return
}
log.Println("URI:", uri.String())

View File

@@ -37,7 +37,7 @@ type result struct {
func main() {
flag.Parse()
prefix := strings.ToUpper(strings.Replace(flag.Arg(0), "-", "", -1))
prefix := strings.ToUpper(strings.ReplaceAll(flag.Arg(0), "-", ""))
if len(prefix) > 7 {
prefix = prefix[:7] + "-" + prefix[7:]
}

View File

@@ -7,31 +7,15 @@
package main
import (
"bytes"
"crypto/md5"
"flag"
"fmt"
"io"
"os"
"time"
"github.com/syncthing/syncthing/lib/sha256"
)
func getmd5(filePath string) ([]byte, error) {
var result []byte
file, err := os.Open(filePath)
if err != nil {
return result, err
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return result, err
}
return hash.Sum(result), nil
}
func main() {
period := flag.Duration("period", 200*time.Millisecond, "Sleep period between checks")
flag.Parse()
@@ -46,7 +30,7 @@ func main() {
exists := true
size := int64(0)
mtime := time.Time{}
hash := []byte{}
var hash [sha256.Size]byte
for {
time.Sleep(*period)
@@ -72,7 +56,7 @@ func main() {
if !exists {
size = 0
mtime = time.Time{}
hash = []byte{}
hash = [sha256.Size]byte{}
continue
}
@@ -83,12 +67,12 @@ func main() {
newSize := fi.Size()
newMtime := fi.ModTime()
newHash, err := getmd5(file)
newHash, err := sha256file(file)
if err != nil {
fmt.Println("getmd5:", err)
fmt.Println("sha256file:", err)
}
if newSize != size || newMtime != mtime || !bytes.Equal(newHash, hash) {
if newSize != size || newMtime != mtime || newHash != hash {
fmt.Println(file, "Size:", newSize, "Mtime:", newMtime, "Hash:", fmt.Sprintf("%x", newHash))
hash = newHash
size = newSize
@@ -96,3 +80,18 @@ func main() {
}
}
}
func sha256file(fname string) (hash [sha256.Size]byte, err error) {
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
h := sha256.New()
io.Copy(h, f)
hb := h.Sum(nil)
copy(hash[:], hb)
return
}

View File

@@ -15,19 +15,17 @@ import (
"time"
)
func init() {
if innerProcess && os.Getenv("STBLOCKPROFILE") != "" {
profiler := pprof.Lookup("block")
if profiler == nil {
panic("Couldn't find block profiler")
}
l.Debugln("Starting block profiling")
go func() {
err := saveBlockingProfiles(profiler) // Only returns on error
l.Warnln("Block profiler failed:", err)
panic("Block profiler failed")
}()
func startBlockProfiler() {
profiler := pprof.Lookup("block")
if profiler == nil {
panic("Couldn't find block profiler")
}
l.Debugln("Starting block profiling")
go func() {
err := saveBlockingProfiles(profiler) // Only returns on error
l.Warnln("Block profiler failed:", err)
panic("Block profiler failed")
}()
}
func saveBlockingProfiles(profiler *pprof.Profile) error {

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
package cli
import (
"bytes"

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
package cli
import (
"errors"

236
cmd/syncthing/cli/main.go Normal file
View File

@@ -0,0 +1,236 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package cli
import (
"bufio"
"crypto/tls"
"encoding/json"
"io/ioutil"
"os"
"reflect"
"strings"
"github.com/AudriusButkevicius/recli"
"github.com/alecthomas/kong"
"github.com/flynn-archive/go-shlex"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/urfave/cli"
)
type preCli struct {
GUIAddress string `name:"gui-address"`
GUIAPIKey string `name:"gui-apikey"`
HomeDir string `name:"home"`
ConfDir string `name:"conf"`
}
func Run() error {
// This is somewhat a hack around a chicken and egg problem. We need to set
// the home directory and potentially other flags to know where the
// syncthing instance is running in order to get it's config ... which we
// then use to construct the actual CLI ... at which point it's too late to
// add flags there...
c := preCli{}
parseFlags(&c)
// Not set as default above because the strings can be really long.
var err error
homeSet := c.HomeDir != ""
confSet := c.ConfDir != ""
switch {
case homeSet && confSet:
err = errors.New("-home must not be used together with -conf")
case homeSet:
err = locations.SetBaseDir(locations.ConfigBaseDir, c.HomeDir)
case confSet:
err = locations.SetBaseDir(locations.ConfigBaseDir, c.ConfDir)
}
if err != nil {
return errors.Wrap(err, "Command line options:")
}
guiCfg := config.GUIConfiguration{
RawAddress: c.GUIAddress,
APIKey: c.GUIAPIKey,
}
// Now if the API key and address is not provided (we are not connecting to a remote instance),
// try to rip it out of the config.
if guiCfg.RawAddress == "" && guiCfg.APIKey == "" {
// Load the certs and get the ID
cert, err := tls.LoadX509KeyPair(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
)
if err != nil {
return errors.Wrap(err, "reading device ID")
}
myID := protocol.NewDeviceID(cert.Certificate[0])
// Load the config
cfg, _, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
if err != nil {
return errors.Wrap(err, "loading config")
}
guiCfg = cfg.GUI()
} else if guiCfg.Address() == "" || guiCfg.APIKey == "" {
return errors.New("Both --gui-address and --gui-apikey should be specified")
}
if guiCfg.Address() == "" {
return errors.New("Could not find GUI Address")
}
if guiCfg.APIKey == "" {
return errors.New("Could not find GUI API key")
}
client := getClient(guiCfg)
cfg, err := getConfig(client)
original := cfg.Copy()
if err != nil {
return errors.Wrap(err, "getting config")
}
// Copy the config and set the default flags
recliCfg := recli.DefaultConfig
recliCfg.IDTag.Name = "xml"
recliCfg.SkipTag.Name = "json"
commands, err := recli.New(recliCfg).Construct(&cfg)
if err != nil {
return errors.Wrap(err, "config reflect")
}
// Implement the same flags at the upper CLI, but do nothing with them.
// This is so that the usage text is the same
fakeFlags := []cli.Flag{
cli.StringFlag{
Name: "gui-address",
Value: "URL",
Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
},
cli.StringFlag{
Name: "gui-apikey",
Value: "API-KEY",
Usage: "Override GUI API key",
},
cli.StringFlag{
Name: "home",
Value: "PATH",
Usage: "Set configuration and data directory",
},
cli.StringFlag{
Name: "conf",
Value: "PATH",
Usage: "Set configuration directory (config and keys)",
},
}
// Construct the actual CLI
app := cli.NewApp()
app.Author = "The Syncthing Authors"
app.Metadata = map[string]interface{}{
"client": client,
}
app.Commands = []cli.Command{{
Name: "cli",
Usage: "Syncthing command line interface",
Flags: fakeFlags,
Subcommands: []cli.Command{
{
Name: "config",
HideHelp: true,
Usage: "Configuration modification command group",
Subcommands: commands,
},
showCommand,
operationCommand,
errorsCommand,
},
}}
tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
if !tty {
// Not a TTY, consume from stdin
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input, err := shlex.Split(scanner.Text())
if err != nil {
return errors.Wrap(err, "parsing input")
}
if len(input) == 0 {
continue
}
err = app.Run(append(os.Args, input...))
if err != nil {
return err
}
}
err = scanner.Err()
if err != nil {
return err
}
} else {
err = app.Run(os.Args)
if err != nil {
return err
}
}
if !reflect.DeepEqual(cfg, original) {
body, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
resp, err := client.Post("system/config", string(body))
if err != nil {
return err
}
if resp.StatusCode != 200 {
body, err := responseToBArray(resp)
if err != nil {
return err
}
return errors.New(string(body))
}
}
return nil
}
func parseFlags(c *preCli) error {
// kong only needs to parse the global arguments after "cli" and before the
// subcommand (if any).
if len(os.Args) <= 2 {
return nil
}
args := os.Args[2:]
for i := 0; i < len(args); i++ {
if !strings.HasPrefix(args[i], "--") {
args = args[:i]
break
}
if !strings.Contains(args[i], "=") {
i++
}
}
// We don't want kong to print anything nor os.Exit (e.g. on -h)
parser, err := kong.New(c, kong.Writers(ioutil.Discard, ioutil.Discard), kong.Exit(func(int) {}))
if err != nil {
return err
}
_, err = parser.Parse(args)
return err
}

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
package cli
import (
"fmt"
@@ -27,11 +27,6 @@ var operationCommand = cli.Command{
Usage: "Shutdown syncthing",
Action: expects(0, emptyPost("system/shutdown")),
},
{
Name: "reset",
Usage: "Reset syncthing deleting all folders and devices",
Action: expects(0, emptyPost("system/reset")),
},
{
Name: "upgrade",
Usage: "Upgrade syncthing (if a newer version is available)",
@@ -39,7 +34,7 @@ var operationCommand = cli.Command{
},
{
Name: "folder-override",
Usage: "Override changes on folder (remote for sendonly, local for receiveonly)",
Usage: "Override changes on folder (remote for sendonly, local for receiveonly). WARNING: Destructive - deletes/changes your data.",
ArgsUsage: "[folder id]",
Action: expects(1, foldersOverride),
},

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
package cli
import (
"github.com/urfave/cli"
@@ -23,7 +23,7 @@ var showCommand = cli.Command{
{
Name: "config-status",
Usage: "Show configuration status, whether or not a restart is required for changes to take effect",
Action: expects(0, dumpOutput("system/config/insync")),
Action: expects(0, dumpOutput("config/restart-required")),
},
{
Name: "system",

View File

@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
package cli
import (
"encoding/json"

View File

@@ -0,0 +1,275 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// Package decrypt implements the `syncthing decrypt` subcommand.
package decrypt
import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"path/filepath"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
)
type CLI struct {
Path string `arg:"" required:"1" help:"Path to encrypted folder"`
To string `xor:"mode" placeholder:"PATH" help:"Destination directory, when decrypting"`
VerifyOnly bool `xor:"mode" help:"Don't write decrypted files to disk (but verify plaintext hashes)"`
Password string `help:"Folder password for decryption / verification" env:"FOLDER_PASSWORD"`
FolderID string `help:"Folder ID of the encrypted folder, if it cannot be determined automatically"`
Continue bool `help:"Continue processing next file in case of error, instead of aborting"`
Verbose bool `help:"Show verbose progress information"`
TokenPath string `placeholder:"PATH" help:"Path to the token file within the folder (used to determine folder ID)"`
folderKey *[32]byte
}
type storedEncryptionToken struct {
FolderID string
Token []byte
}
func (c *CLI) Run() error {
log.SetFlags(0)
if c.To == "" && !c.VerifyOnly {
return fmt.Errorf("must set --to or --verify")
}
if c.TokenPath == "" {
// This is a bit long to show as default in --help
c.TokenPath = filepath.Join(config.DefaultMarkerName, config.EncryptionTokenName)
}
if c.FolderID == "" {
// We should try to figure out the folder ID
folderID, err := c.getFolderID()
if err != nil {
log.Println("No --folder-id given and couldn't read folder token")
return fmt.Errorf("getting folder ID: %w", err)
}
c.FolderID = folderID
if c.Verbose {
log.Println("Found folder ID:", c.FolderID)
}
}
c.folderKey = protocol.KeyFromPassword(c.FolderID, c.Password)
return c.walk()
}
// walk finds and processes every file in the encrypted folder
func (c *CLI) walk() error {
srcFs := fs.NewFilesystem(fs.FilesystemTypeBasic, c.Path)
var dstFs fs.Filesystem
if c.To != "" {
dstFs = fs.NewFilesystem(fs.FilesystemTypeBasic, c.To)
}
return srcFs.Walk(".", func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsRegular() {
return nil
}
if fs.IsInternal(path) {
return nil
}
return c.withContinue(c.process(srcFs, dstFs, path))
})
}
// If --continue was set we just mention the error and return nil to
// continue processing.
func (c *CLI) withContinue(err error) error {
if err == nil {
return nil
}
if c.Continue {
log.Println("Warning:", err)
return nil
}
return err
}
// getFolderID returns the folder ID found in the encrypted token, or an
// error.
func (c *CLI) getFolderID() (string, error) {
tokenPath := filepath.Join(c.Path, c.TokenPath)
bs, err := ioutil.ReadFile(tokenPath)
if err != nil {
return "", fmt.Errorf("reading folder token: %w", err)
}
var tok storedEncryptionToken
if err := json.Unmarshal(bs, &tok); err != nil {
return "", fmt.Errorf("parsing folder token: %w", err)
}
return tok.FolderID, nil
}
// process handles the file named path in srcFs, decrypting it into dstFs
// unless dstFs is nil.
func (c *CLI) process(srcFs fs.Filesystem, dstFs fs.Filesystem, path string) error {
if c.Verbose {
log.Printf("Processing %q", path)
}
encFd, err := srcFs.Open(path)
if err != nil {
return err
}
defer encFd.Close()
encFi, err := c.loadEncryptedFileInfo(encFd)
if err != nil {
return fmt.Errorf("%s: loading metadata trailer: %w", path, err)
}
// Workaround for a bug in <= v1.15.0-rc.5 where we stored names
// in native format, while protocol expects wire format (slashes).
encFi.Name = osutil.NormalizedFilename(encFi.Name)
plainFi, err := protocol.DecryptFileInfo(*encFi, c.folderKey)
if err != nil {
return fmt.Errorf("%s: decrypting metadata: %w", path, err)
}
if c.Verbose {
log.Printf("Plaintext filename is %q", plainFi.Name)
}
var plainFd fs.File
if dstFs != nil {
if err := dstFs.MkdirAll(filepath.Dir(plainFi.Name), 0700); err != nil {
return fmt.Errorf("%s: %w", plainFi.Name, err)
}
plainFd, err = dstFs.Create(plainFi.Name)
if err != nil {
return fmt.Errorf("%s: %w", plainFi.Name, err)
}
defer plainFd.Close() // also closed explicitly in the return
}
if err := c.decryptFile(encFi, &plainFi, encFd, plainFd); err != nil {
// Decrypting the file failed, leaving it in an inconsistent state.
// Delete it. Even --continue currently doesn't mean "leave broken
// stuff in place", it just means "try the next file instead of
// aborting".
if plainFd != nil {
_ = dstFs.Remove(plainFd.Name())
}
return fmt.Errorf("%s: %s: %w", path, plainFi.Name, err)
} else if c.Verbose {
log.Printf("Data verified for %q", plainFi.Name)
}
if plainFd != nil {
return plainFd.Close()
}
return nil
}
// decryptFile reads, decrypts and verifies all the blocks in src, writing
// it to dst if dst is non-nil. (If dst is nil it just becomes a
// read-and-verify operation.)
func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo, src io.ReaderAt, dst io.WriterAt) error {
// The encrypted and plaintext files must consist of an equal number of blocks
if len(encFi.Blocks) != len(plainFi.Blocks) {
return fmt.Errorf("block count mismatch: encrypted %d != plaintext %d", len(encFi.Blocks), len(plainFi.Blocks))
}
fileKey := protocol.FileKey(plainFi.Name, c.folderKey)
for i, encBlock := range encFi.Blocks {
// Read the encrypted block
buf := make([]byte, encBlock.Size)
if _, err := src.ReadAt(buf, encBlock.Offset); err != nil {
return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
}
// Decrypt it
dec, err := protocol.DecryptBytes(buf, fileKey)
if err != nil {
return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
}
// Verify the block size against the expected plaintext
plainBlock := plainFi.Blocks[i]
if i == len(plainFi.Blocks)-1 && len(dec) > plainBlock.Size {
// The last block might be padded, which is fine (we skip the padding)
dec = dec[:plainBlock.Size]
} else if len(dec) != plainBlock.Size {
return fmt.Errorf("plaintext block %d size mismatch, actual %d != expected %d", i, len(dec), plainBlock.Size)
}
// Verify the hash against the plaintext block info
if !scanner.Validate(dec, plainBlock.Hash, 0) {
// The block decrypted correctly but fails the hash check. This
// is odd and unexpected, but it it's still a valid block from
// the source. The file might have changed while we pulled it?
err := fmt.Errorf("plaintext block %d (%d bytes) failed validation after decryption", i, plainBlock.Size)
if c.Continue {
log.Printf("Warning: %s: %s: %v", encFi.Name, plainFi.Name, err)
} else {
return err
}
}
// Write it to the destination, unless we're just verifying.
if dst != nil {
if _, err := dst.WriteAt(dec, plainBlock.Offset); err != nil {
return err
}
}
}
return nil
}
// loadEncryptedFileInfo loads the encrypted FileInfo trailer from a file on
// disk.
func (c *CLI) loadEncryptedFileInfo(fd fs.File) (*protocol.FileInfo, error) {
// Seek to the size of the trailer block
if _, err := fd.Seek(-4, io.SeekEnd); err != nil {
return nil, err
}
var bs [4]byte
if _, err := io.ReadFull(fd, bs[:]); err != nil {
return nil, err
}
size := int64(binary.BigEndian.Uint32(bs[:]))
// Seek to the start of the trailer
if _, err := fd.Seek(-(4 + size), io.SeekEnd); err != nil {
return nil, err
}
trailer := make([]byte, size)
if _, err := io.ReadFull(fd, trailer); err != nil {
return nil, err
}
var encFi protocol.FileInfo
if err := encFi.Unmarshal(trailer); err != nil {
return nil, err
}
return &encFi, nil
}

View File

@@ -11,24 +11,17 @@ import (
"os"
"runtime"
"runtime/pprof"
"strconv"
"syscall"
"time"
)
func init() {
if innerProcess && os.Getenv("STHEAPPROFILE") != "" {
rate := 1
if i, err := strconv.Atoi(os.Getenv("STHEAPPROFILE")); err == nil {
rate = i
}
l.Debugln("Starting heap profiling")
go func() {
err := saveHeapProfiles(rate) // Only returns on error
l.Warnln("Heap profiler failed:", err)
panic("Heap profiler failed")
}()
}
func startHeapProfiler() {
l.Debugln("Starting heap profiling")
go func() {
err := saveHeapProfiles(1) // Only returns on error
l.Warnln("Heap profiler failed:", err)
panic("Heap profiler failed")
}()
}
func saveHeapProfiles(rate int) error {

View File

@@ -10,7 +10,6 @@ import (
"bytes"
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"io/ioutil"
@@ -22,16 +21,24 @@ import (
"os/signal"
"path"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"strings"
"syscall"
"time"
"github.com/alecthomas/kong"
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/cmd/syncthing/cli"
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
@@ -45,7 +52,6 @@ import (
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/pkg/errors"
"github.com/thejerf/suture/v4"
)
const (
@@ -55,9 +61,8 @@ const (
)
const (
usage = "syncthing [options]"
extraUsage = `
The -logflags value is a sum of the following:
The --logflags value is a sum of the following:
1 Date
2 Time
@@ -65,7 +70,7 @@ The -logflags value is a sum of the following:
8 Long filename
16 Short filename
I.e. to prefix each log line with date and time, set -logflags=3 (1 + 2 from
I.e. to prefix each log line with date and time, set --logflags=3 (1 + 2 from
above). The value 0 is used to disable all of the above. The default is to
show time only (2).
@@ -80,31 +85,11 @@ Development Settings
--------------------
The following environment variables modify Syncthing's behavior in ways that
are mostly useful for developers. Use with care.
STNODEFAULTFOLDER Don't create a default folder when starting for the first
time. This variable will be ignored anytime after the first
run.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in
assets.
are mostly useful for developers. Use with care. See also the --debug-* options
above.
STTRACE A comma separated string of facilities to trace. The valid
facility strings listed below.
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start
the profiler with HTTP access.
STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
heap usage increases.
STBLOCKPROFILE Write block profiles to block-$pid-$timestamp.pprof every 20
seconds.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
facility strings are listed below.
STDEADLOCKTIMEOUT Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
@@ -112,24 +97,11 @@ are mostly useful for developers. Use with care.
STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
STNORESTART Equivalent to the -no-restart argument.
STNOUPGRADE Disable automatic upgrades.
STHASHING Select the SHA256 hashing package to use. Possible values
are "standard" for the Go standard library implementation,
"minio" for the github.com/minio/sha256-simd implementation,
and blank (the default) for auto detection.
STRECHECKDBEVERY Set to a time interval to override the default database
check interval of 30 days (720h). The interval understands
"h", "m" and "s" abbreviations for hours minutes and seconds.
Valid values are like "720h", "30s", etc.
STGCINDIRECTEVERY Set to a time interval to override the default database
indirection GC interval of 13 hours. Same format as the
STRECHECKDBEVERY variable.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.
@@ -143,75 +115,88 @@ Debugging Facilities
The following are valid values for the STTRACE variable:
%s`
%s
`
)
var (
// Environment options
innerProcess = os.Getenv("STMONITORED") != ""
noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != ""
upgradeCheckInterval = 5 * time.Minute
upgradeRetryInterval = time.Hour
upgradeCheckKey = "lastUpgradeCheck"
upgradeTimeKey = "lastUpgradeTime"
upgradeVersionKey = "lastUpgradeVersion"
errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance")
errTooEarlyUpgradeCheck = fmt.Errorf("last upgrade check happened less than %v ago, skipping", upgradeCheckInterval)
errTooEarlyUpgrade = fmt.Errorf("last upgrade happened less than %v ago, skipping", upgradeRetryInterval)
)
type RuntimeOptions struct {
syncthing.Options
homeDir string
confDir string
dataDir string
resetDatabase bool
showVersion bool
showPaths bool
showDeviceId bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
browserOnly bool
hideConsole bool
logFile string
logMaxSize int
logMaxFiles int
auditEnabled bool
auditFile string
paused bool
unpaused bool
guiAddress string
guiAPIKey string
generateDir string
noRestart bool
cpuProfile bool
stRestarting bool
logFlags int
showHelp bool
allowNewerConfig bool
// The entrypoint struct is the main entry point for the command line parser. The
// commands and options here are top level commands to syncthing.
// Cli is just a placeholder for the help text (see main).
var entrypoint struct {
Serve serveOptions `cmd:"" help:"Run Syncthing"`
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
}
func defaultRuntimeOptions() RuntimeOptions {
options := RuntimeOptions{
Options: syncthing.Options{
AssetDir: os.Getenv("STGUIASSETS"),
NoUpgrade: os.Getenv("STNOUPGRADE") != "",
ProfilerURL: os.Getenv("STPROFILER"),
},
noRestart: os.Getenv("STNORESTART") != "",
cpuProfile: os.Getenv("STCPUPROFILE") != "",
stRestarting: os.Getenv("STRESTART") != "",
logFlags: log.Ltime,
logMaxSize: 10 << 20, // 10 MiB
logMaxFiles: 3, // plus the current one
}
// serveOptions are the options for the `syncthing serve` command.
type serveOptions struct {
buildServeOptions
AllowNewerConfig bool `help:"Allow loading newer than current config version"`
Audit bool `help:"Write events to audit file"`
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
BrowserOnly bool `help:"Open GUI in browser"`
ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
DataDir string `name:"data" placeholder:"PATH" help:"Set data directory (database and logs)"`
DeviceID bool `help:"Show the device ID"`
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"`
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"`
LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"`
LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"`
NoBrowser bool `help:"Do not start browser"`
NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"`
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"`
Paths bool `help:"Show configuration paths"`
Paused bool `help:"Start with all devices and folders paused"`
Unpaused bool `help:"Start with all devices and folders unpaused"`
Upgrade bool `help:"Perform upgrade"`
UpgradeCheck bool `help:"Check for available upgrade"`
UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"`
Verbose bool `help:"Print verbose log output"`
Version bool `help:"Show version"`
// Debug options below
DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"`
DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"`
DebugDeadlockTimeout int `placeholder:"SECONDS" env:"STDEADLOCKTIMEOUT" help:"Used for debugging internal deadlocks"`
DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"`
DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"`
DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"`
DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"CPUPROFILE"`
DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"`
DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"`
DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"`
DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"`
// Internal options, not shown to users
InternalRestarting bool `env:"STRESTART" hidden:"1"`
InternalInnerProcess bool `env:"STMONITORED" hidden:"1"`
}
func defaultVars() kong.Vars {
vars := kong.Vars{}
vars["logFlags"] = strconv.Itoa(log.Ltime)
vars["logMaxSize"] = strconv.Itoa(10 << 20) // 10 MiB
vars["logMaxFiles"] = "3" // plus the current one
if os.Getenv("STTRACE") != "" {
options.logFlags = logger.DebugFlags
vars["logFlags"] = strconv.Itoa(logger.DebugFlags)
}
// On non-Windows, we explicitly default to "-" which means stdout. On
@@ -219,107 +204,108 @@ func defaultRuntimeOptions() RuntimeOptions {
// default path, unless the user has manually specified "-" or
// something else.
if runtime.GOOS == "windows" {
options.logFile = "default"
vars["logFile"] = "default"
} else {
options.logFile = "-"
vars["logFile"] = "-"
}
return options
}
func parseCommandLineOptions() RuntimeOptions {
options := defaultRuntimeOptions()
flag.StringVar(&options.generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&options.guiAddress, "gui-address", options.guiAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
flag.StringVar(&options.guiAPIKey, "gui-apikey", options.guiAPIKey, "Override GUI API key")
flag.StringVar(&options.homeDir, "home", "", "Set configuration and data directory")
flag.StringVar(&options.confDir, "config", "", "Set configuration directory (config and keys)")
flag.StringVar(&options.dataDir, "data", "", "Set data directory (database and logs)")
flag.IntVar(&options.logFlags, "logflags", options.logFlags, "Select information in log line prefix (see below)")
flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash")
flag.BoolVar(&options.resetDatabase, "reset-database", false, "Reset the database, forcing a full rescan and resync")
flag.BoolVar(&options.ResetDeltaIdxs, "reset-deltas", false, "Reset delta index IDs, forcing a full index exchange")
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
flag.BoolVar(&options.showVersion, "version", false, "Show version")
flag.BoolVar(&options.showHelp, "help", false, "Show this help")
flag.BoolVar(&options.showPaths, "paths", false, "Show configuration paths")
flag.BoolVar(&options.showDeviceId, "device-id", false, "Show the device ID")
flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
flag.BoolVar(&options.Verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (see below).")
flag.IntVar(&options.logMaxSize, "log-max-size", options.logMaxSize, "Maximum size of any file (zero to disable log rotation).")
flag.IntVar(&options.logMaxFiles, "log-max-old-files", options.logMaxFiles, "Number of old files to keep (zero to keep only current).")
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
flag.BoolVar(&options.allowNewerConfig, "allow-newer-config", false, "Allow loading newer than current config version")
if runtime.GOOS == "windows" {
// Allow user to hide the console window
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
}
longUsage := fmt.Sprintf(extraUsage, debugFacilities())
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
if len(flag.Args()) > 0 {
flag.Usage()
os.Exit(2)
}
return options
}
func setLocation(enum locations.BaseDirEnum, loc string) error {
if !filepath.IsAbs(loc) {
var err error
loc, err = filepath.Abs(loc)
if err != nil {
return err
}
}
return locations.SetBaseDir(enum, loc)
return vars
}
func main() {
options := parseCommandLineOptions()
l.SetFlags(options.logFlags)
if options.guiAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", options.guiAddress)
}
if options.guiAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", options.guiAPIKey)
// The "cli" subcommand uses a different command line parser, and e.g. help
// gets mangled when integrating it as a subcommand -> detect it here at the
// beginning.
if len(os.Args) > 1 && os.Args[1] == "cli" {
if err := cli.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
return
}
if options.hideConsole {
// First some massaging of the raw command line to fit the new model.
// Basically this means adding the default command at the front, and
// converting -options to --options.
args := os.Args[1:]
switch {
case len(args) == 0:
// Empty command line is equivalent to just calling serve
args = []string{"serve"}
case args[0] == "-help":
// For consistency, we consider this equivalent with --help even
// though kong would otherwise consider it a bad flag.
args[0] = "--help"
case args[0] == "-h", args[0] == "--help":
// Top level request for help, let it pass as-is to be handled by
// kong to list commands.
case strings.HasPrefix(args[0], "-"):
// There are flags not preceded by a command, so we tack on the
// "serve" command and convert the old style arguments (single dash)
// to new style (double dash).
args = append([]string{"serve"}, convertLegacyArgs(args)...)
}
// Create a parser with an overridden help function to print our extra
// help info.
parser, err := kong.New(&entrypoint, kong.Help(helpHandler), defaultVars())
if err != nil {
log.Fatal(err)
}
ctx, err := parser.Parse(args)
parser.FatalIfErrorf(err)
err = ctx.Run()
parser.FatalIfErrorf(err)
}
func helpHandler(options kong.HelpOptions, ctx *kong.Context) error {
if err := kong.DefaultHelpPrinter(options, ctx); err != nil {
return err
}
if ctx.Command() == "serve" {
// Help was requested for `syncthing serve`, so we add our extra
// usage info afte the normal options output.
fmt.Printf(extraUsage, debugFacilities())
}
return nil
}
// serveOptions.Run() is the entrypoint for `syncthing serve`
func (options serveOptions) Run() error {
l.SetFlags(options.LogFlags)
if options.GUIAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", options.GUIAddress)
}
if options.GUIAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", options.GUIAPIKey)
}
if options.HideConsole {
osutil.HideConsole()
}
// Not set as default above because the strings can be really long.
var err error
homeSet := options.homeDir != ""
confSet := options.confDir != ""
dataSet := options.dataDir != ""
homeSet := options.HomeDir != ""
confSet := options.ConfDir != ""
dataSet := options.DataDir != ""
switch {
case dataSet != confSet:
err = errors.New("either both or none of -conf and -data must be given, use -home to set both at once")
case homeSet && dataSet:
err = errors.New("-home must not be used together with -conf and -data")
case homeSet:
if err = setLocation(locations.ConfigBaseDir, options.homeDir); err == nil {
err = setLocation(locations.DataBaseDir, options.homeDir)
if err = locations.SetBaseDir(locations.ConfigBaseDir, options.HomeDir); err == nil {
err = locations.SetBaseDir(locations.DataBaseDir, options.HomeDir)
}
case dataSet:
if err = setLocation(locations.ConfigBaseDir, options.confDir); err == nil {
err = setLocation(locations.DataBaseDir, options.dataDir)
if err = locations.SetBaseDir(locations.ConfigBaseDir, options.ConfDir); err == nil {
err = locations.SetBaseDir(locations.DataBaseDir, options.DataDir)
}
}
if err != nil {
@@ -327,34 +313,29 @@ func main() {
os.Exit(svcutil.ExitError.AsInt())
}
if options.logFile == "default" || options.logFile == "" {
if options.LogFile == "default" || options.LogFile == "" {
// We must set this *after* expandLocations above.
// Handling an empty value is for backwards compatibility (<1.4.1).
options.logFile = locations.Get(locations.LogFile)
options.LogFile = locations.Get(locations.LogFile)
}
if options.AssetDir == "" {
if options.DebugGUIAssetsDir == "" {
// The asset dir is blank if STGUIASSETS wasn't set, in which case we
// should look for extra assets in the default place.
options.AssetDir = locations.Get(locations.GUIAssets)
options.DebugGUIAssetsDir = locations.Get(locations.GUIAssets)
}
if options.showVersion {
if options.Version {
fmt.Println(build.LongVersion)
return
return nil
}
if options.showHelp {
flag.Usage()
return
}
if options.showPaths {
if options.Paths {
showPaths(options)
return
return nil
}
if options.showDeviceId {
if options.DeviceID {
cert, err := tls.LoadX509KeyPair(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
@@ -365,23 +346,23 @@ func main() {
}
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
return
return nil
}
if options.browserOnly {
if options.BrowserOnly {
if err := openGUI(protocol.EmptyDeviceID); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(svcutil.ExitError.AsInt())
}
return
return nil
}
if options.generateDir != "" {
if err := generate(options.generateDir); err != nil {
if options.GenerateDir != "" {
if err := generate(options.GenerateDir, options.NoDefaultFolder); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(svcutil.ExitError.AsInt())
}
return
return nil
}
// Ensure that our home directory exists.
@@ -390,29 +371,30 @@ func main() {
os.Exit(svcutil.ExitError.AsInt())
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if options.UpgradeTo != "" {
err := upgrade.ToURL(options.UpgradeTo)
if err != nil {
l.Warnln("Error while Upgrading:", err)
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Upgraded from", options.upgradeTo)
return
l.Infoln("Upgraded from", options.UpgradeTo)
return nil
}
if options.doUpgradeCheck {
if options.UpgradeCheck {
if _, err := checkUpgrade(); err != nil {
l.Warnln("Checking for upgrade:", err)
os.Exit(exitCodeForUpgrade(err))
}
return
return nil
}
if options.doUpgrade {
if options.Upgrade {
release, err := checkUpgrade()
if err == nil {
// Use leveldb database locks to protect against concurrent upgrades
ldb, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
var ldb backend.Backend
ldb, err = syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
if err != nil {
err = upgradeViaRest()
} else {
@@ -428,24 +410,25 @@ func main() {
os.Exit(svcutil.ExitUpgrade.AsInt())
}
if options.resetDatabase {
if options.DebugResetDatabase {
if err := resetDB(); err != nil {
l.Warnln("Resetting database:", err)
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Successfully reset database - it will be rebuilt after next start.")
return
return nil
}
if innerProcess {
if options.InternalInnerProcess {
syncthingMain(options)
} else {
monitorMain(options)
}
return nil
}
func openGUI(myID protocol.DeviceID) error {
cfg, err := loadOrDefaultConfig(myID, events.NoopLogger)
cfg, err := loadOrDefaultConfig(myID, events.NoopLogger, true)
if err != nil {
return err
}
@@ -459,7 +442,7 @@ func openGUI(myID protocol.DeviceID) error {
return nil
}
func generate(generateDir string) error {
func generate(generateDir string, noDefaultFolder bool) error {
dir, err := fs.ExpandTilde(generateDir)
if err != nil {
return err
@@ -530,7 +513,7 @@ func (e *errNoUpgrade) Error() string {
}
func checkUpgrade() (upgrade.Release, error) {
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger, true)
if err != nil {
return upgrade.Release{}, err
}
@@ -549,7 +532,7 @@ func checkUpgrade() (upgrade.Release, error) {
}
func upgradeViaRest() error {
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger, true)
u, err := url.Parse(cfg.GUI().URL())
if err != nil {
return err
@@ -584,7 +567,17 @@ func upgradeViaRest() error {
return err
}
func syncthingMain(runtimeOptions RuntimeOptions) {
func syncthingMain(options serveOptions) {
if options.DebugProfileBlock {
startBlockProfiler()
}
if options.DebugProfileHeap {
startHeapProfiler()
}
if options.DebugPerfStats {
startPerfStats()
}
// Set a log prefix similar to the ID we will have later on, or early log
// lines look ugly.
l.SetPrefix("[start] ")
@@ -615,20 +608,18 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
evLogger := events.NewLogger()
earlyService.Add(evLogger)
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(svcutil.ExitError.AsInt())
}
if cfgService, ok := cfgWrapper.(suture.Service); ok {
earlyService.Add(cfgService)
}
earlyService.Add(cfgWrapper)
// Candidate builds should auto upgrade. Make sure the option is set,
// unless we are in a build where it's disabled or the STNOUPGRADE
// environment variable is set.
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
if build.IsCandidate && !upgrade.DisabledByCompilation && !options.NoUpgrade {
cfgWrapper.Modify(func(cfg *config.Configuration) {
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
if cfg.Options.AutoUpgradeIntervalH == 0 || cfg.Options.AutoUpgradeIntervalH > 24 {
@@ -652,7 +643,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
// upgrade immedately. The auto-upgrade routine can only be started
// later after App is initialised.
autoUpgradePossible := autoUpgradePossible(runtimeOptions)
autoUpgradePossible := autoUpgradePossible(options)
if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() {
// try to do upgrade directly and log the error if relevant.
release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
@@ -671,15 +662,24 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
if runtimeOptions.unpaused {
if options.Unpaused {
setPauseState(cfgWrapper, false)
} else if runtimeOptions.paused {
} else if options.Paused {
setPauseState(cfgWrapper, true)
}
appOpts := runtimeOptions.Options
if runtimeOptions.auditEnabled {
appOpts.AuditWriter = auditWriter(runtimeOptions.auditFile)
appOpts := syncthing.Options{
AssetDir: options.DebugGUIAssetsDir,
DeadlockTimeoutS: options.DebugDeadlockTimeout,
NoUpgrade: options.NoUpgrade,
ProfilerAddr: options.DebugProfilerListen,
ResetDeltaIdxs: options.DebugResetDeltaIdxs,
Verbose: options.Verbose,
DBRecheckInterval: options.DebugDBRecheckInterval,
DBIndirectGCInterval: options.DebugDBIndirectGCInterval,
}
if options.Audit {
appOpts.AuditWriter = auditWriter(options.AuditFile)
}
if t := os.Getenv("STDEADLOCKTIMEOUT"); t != "" {
secs, _ := strconv.Atoi(t)
@@ -708,7 +708,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
runtime.GOMAXPROCS(runtime.NumCPU())
}
if runtimeOptions.cpuProfile {
if options.DebugProfileCPU {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Warnln("Creating profile:", err)
@@ -728,7 +728,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
cleanConfigDirectory()
if cfgWrapper.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
if cfgWrapper.Options().StartBrowser && !options.NoBrowser && !options.InternalRestarting {
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in its own routine.
go func() { _ = openURL(cfgWrapper.GUI().URL()) }()
@@ -740,7 +740,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
l.Warnln("Syncthing stopped with error:", app.Error())
}
if runtimeOptions.cpuProfile {
if options.DebugProfileCPU {
pprof.StopCPUProfile()
}
@@ -768,7 +768,7 @@ func setupSignalHandling(app *syncthing.App) {
}()
}
func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger) (config.Wrapper, error) {
func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder bool) (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile)
cfg, _, err := config.Load(cfgFile, myID, evLogger)
@@ -858,11 +858,11 @@ func standbyMonitor(app *syncthing.App, cfg config.Wrapper) {
}
}
func autoUpgradePossible(runtimeOptions RuntimeOptions) bool {
func autoUpgradePossible(options serveOptions) bool {
if upgrade.DisabledByCompilation {
return false
}
if runtimeOptions.NoUpgrade {
if options.NoUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
return false
}
@@ -989,13 +989,13 @@ func cleanConfigDirectory() {
}
}
func showPaths(options RuntimeOptions) {
func showPaths(options serveOptions) {
fmt.Printf("Configuration file:\n\t%s\n\n", locations.Get(locations.ConfigFile))
fmt.Printf("Database directory:\n\t%s\n\n", locations.Get(locations.Database))
fmt.Printf("Device private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.KeyFile), locations.Get(locations.CertFile))
fmt.Printf("HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.HTTPSKeyFile), locations.Get(locations.HTTPSCertFile))
fmt.Printf("Log file:\n\t%s\n\n", options.logFile)
fmt.Printf("GUI override directory:\n\t%s\n\n", options.AssetDir)
fmt.Printf("Log file:\n\t%s\n\n", options.LogFile)
fmt.Printf("GUI override directory:\n\t%s\n\n", options.DebugGUIAssetsDir)
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
}
@@ -1020,3 +1020,21 @@ func exitCodeForUpgrade(err error) int {
}
return svcutil.ExitError.AsInt()
}
// convertLegacyArgs returns the slice of arguments with single dash long
// flags converted to double dash long flags.
func convertLegacyArgs(args []string) []string {
// Legacy args begin with a single dash, followed by two or more characters.
legacyExp := regexp.MustCompile(`^-\w{2,}`)
res := make([]string, len(args))
for i, arg := range args {
if legacyExp.MatchString(arg) {
res[i] = "-" + arg
} else {
res[i] = arg
}
}
return res
}

View File

@@ -36,20 +36,21 @@ var (
)
const (
countRestarts = 4
loopThreshold = 60 * time.Second
restartCounts = 4
restartPause = 1 * time.Second
restartLoopThreshold = 60 * time.Second
logFileAutoCloseDelay = 5 * time.Second
logFileMaxOpenTime = time.Minute
panicUploadMaxWait = 30 * time.Second
panicUploadNoticeWait = 10 * time.Second
)
func monitorMain(runtimeOptions RuntimeOptions) {
func monitorMain(options serveOptions) {
l.SetPrefix("[monitor] ")
var dst io.Writer = os.Stdout
logFile := runtimeOptions.logFile
logFile := options.LogFile
if logFile != "-" {
if expanded, err := fs.ExpandTilde(logFile); err == nil {
logFile = expanded
@@ -59,8 +60,8 @@ func monitorMain(runtimeOptions RuntimeOptions) {
open := func(name string) (io.WriteCloser, error) {
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime)
}
if runtimeOptions.logMaxSize > 0 {
fileDst, err = newRotatedFile(logFile, open, int64(runtimeOptions.logMaxSize), runtimeOptions.logMaxFiles)
if options.LogMaxSize > 0 {
fileDst, err = newRotatedFile(logFile, open, int64(options.LogMaxSize), options.LogMaxFiles)
} else {
fileDst, err = open(logFile)
}
@@ -84,7 +85,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
}
args := os.Args
var restarts [countRestarts]time.Time
var restarts [restartCounts]time.Time
stopSign := make(chan os.Signal, 1)
signal.Notify(stopSign, os.Interrupt, sigTerm)
@@ -97,8 +98,8 @@ func monitorMain(runtimeOptions RuntimeOptions) {
for {
maybeReportPanics()
if t := time.Since(restarts[0]); t < loopThreshold {
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
if t := time.Since(restarts[0]); t < restartLoopThreshold {
l.Warnf("%d restarts in %v; not retrying further", restartCounts, t)
os.Exit(svcutil.ExitError.AsInt())
}
@@ -174,7 +175,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if exiterr, ok := err.(*exec.ExitError); ok {
exitCode := exiterr.ExitCode()
if stopped || runtimeOptions.noRestart {
if stopped || options.NoRestart {
os.Exit(exitCode)
}
if exitCode == svcutil.ExitUpgrade.AsInt() {
@@ -188,12 +189,12 @@ func monitorMain(runtimeOptions RuntimeOptions) {
}
}
if runtimeOptions.noRestart {
if options.NoRestart {
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Syncthing exited:", err)
time.Sleep(1 * time.Second)
time.Sleep(restartPause)
if first {
// Let the next child process know that this is not the first time
@@ -563,7 +564,7 @@ func childEnv() []string {
// panicUploadMaxWait uploading panics...
func maybeReportPanics() {
// Try to get a config to see if/where panics should be reported.
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger, true)
if err != nil {
l.Warnln("Couldn't load config; not reporting crash")
return

View File

@@ -0,0 +1,13 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build !windows
package main
type buildServeOptions struct {
HideConsole bool `hidden:""`
}

View File

@@ -0,0 +1,11 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
type buildServeOptions struct {
HideConsole bool `name:"no-console" help:"Hide console window"`
}

View File

@@ -18,10 +18,8 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
)
func init() {
if innerProcess && os.Getenv("STPERFSTATS") != "" {
go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()))
}
func startPerfStats() {
go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()))
}
func savePerfStats(file string) {

View File

@@ -0,0 +1,12 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build solaris windows
package main
func startPerfStats() {
}

View File

@@ -1,57 +0,0 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
import (
"bytes"
"flag"
"fmt"
"io"
"text/tabwriter"
)
func optionTable(w io.Writer, rows [][]string) {
tw := tabwriter.NewWriter(w, 2, 4, 2, ' ', 0)
for _, row := range rows {
for i, cell := range row {
if i > 0 {
tw.Write([]byte("\t"))
}
tw.Write([]byte(cell))
}
tw.Write([]byte("\n"))
}
tw.Flush()
}
func usageFor(fs *flag.FlagSet, usage string, extra string) func() {
return func() {
var b bytes.Buffer
b.WriteString("Usage:\n " + usage + "\n")
var options [][]string
fs.VisitAll(func(f *flag.Flag) {
var opt = " -" + f.Name
if f.DefValue != "false" {
opt += "=" + fmt.Sprintf(`"%s"`, f.DefValue)
}
options = append(options, []string{opt, f.Usage})
})
if len(options) > 0 {
b.WriteString("\nOptions:\n")
optionTable(&b, options)
}
fmt.Println(b.String())
if len(extra) > 0 {
fmt.Println(extra)
}
}
}

View File

@@ -135,30 +135,30 @@ func setupDB(db *sql.DB) error {
row := db.QueryRow(`SELECT 'UniqueDayVersionIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE UNIQUE INDEX UniqueDayVersionIndex ON VersionSummary (Day, Version)`)
_, _ = db.Exec(`CREATE UNIQUE INDEX UniqueDayVersionIndex ON VersionSummary (Day, Version)`)
}
row = db.QueryRow(`SELECT 'VersionDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
_, _ = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
}
row = db.QueryRow(`SELECT 'MovementDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX MovementDayIndex ON UserMovement (Day)`)
_, _ = db.Exec(`CREATE INDEX MovementDayIndex ON UserMovement (Day)`)
}
row = db.QueryRow(`SELECT 'PerformanceDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
_, _ = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
}
row = db.QueryRow(`SELECT 'BlockStatsDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX BlockStatsDayIndex ON BlockStats (Day)`)
_, _ = db.Exec(`CREATE INDEX BlockStatsDayIndex ON BlockStats (Day)`)
}
return err
return nil
}
func maxIndexedDay(db *sql.DB, table string) time.Time {

View File

@@ -89,7 +89,7 @@ var funcs = map[string]interface{}{
parts = append(parts, part)
}
if len(input) > 0 {
parts = append(parts, input[:])
parts = append(parts, input)
}
return parts[whichPart-1]
},
@@ -725,8 +725,8 @@ func getReport(db *sql.DB) map[string]interface{} {
if rep.NATType != "" {
natType := rep.NATType
natType = strings.Replace(natType, "unknown", "Unknown", -1)
natType = strings.Replace(natType, "Symetric", "Symmetric", -1)
natType = strings.ReplaceAll(natType, "unknown", "Unknown")
natType = strings.ReplaceAll(natType, "Symetric", "Symmetric")
add(featureGroups["Various"]["v3"], "NAT Type", natType, 1)
}
@@ -745,6 +745,7 @@ func getReport(db *sql.DB) map[string]interface{} {
inc(features["Folder"]["v3"], "Weak hash, custom threshold", rep.FolderUsesV3.CustomWeakHashThreshold)
inc(features["Folder"]["v3"], "Filesystem watcher", rep.FolderUsesV3.FsWatcherEnabled)
inc(features["Folder"]["v3"], "Case sensitive FS", rep.FolderUsesV3.CaseSensitiveFS)
inc(features["Folder"]["v3"], "Mode, receive encrypted", rep.FolderUsesV3.ReceiveEncrypted)
add(featureGroups["Folder"]["v3"], "Conflicts", "Disabled", rep.FolderUsesV3.ConflictsDisabled)
add(featureGroups["Folder"]["v3"], "Conflicts", "Unlimited", rep.FolderUsesV3.ConflictsUnlimited)

View File

@@ -1,7 +1,7 @@
[syncthing]
title=Syncthing
description=Syncthing file synchronisation
ports=22000/tcp|21027/udp
ports=22000|21027/udp
[syncthing-gui]
title=Syncthing-GUI

View File

@@ -38,13 +38,14 @@ syncthing_group=${syncthing_group:-$syncthing_user}
command=/usr/local/bin/syncthing
pidfile=/var/run/syncthing.pid
syncthing_flags="${syncthing_home:+-home=${syncthing_home}} ${syncthing_log_file:+-logfile=${syncthing_log_file}}"
syncthing_cmd=serve
syncthing_flags="${syncthing_home:+--home=${syncthing_home}} ${syncthing_log_file:+--logfile=${syncthing_log_file}}"
syncthing_start() {
echo "Starting syncthing"
touch ${pidfile} && chown ${syncthing_user} ${pidfile}
touch ${syncthing_log_file} && chown ${syncthing_user} ${syncthing_log_file}
/usr/sbin/daemon -cf -p ${pidfile} -u ${syncthing_user} ${command} ${syncthing_flags}
/usr/sbin/daemon -cf -p ${pidfile} -u ${syncthing_user} ${command} ${syncthing_cmd} ${syncthing_flags}
}
syncthing_cleanup() {

View File

@@ -2,7 +2,7 @@
Name=Start Syncthing
GenericName=File synchronization
Comment=Starts the main syncthing process in the background.
Exec=/usr/bin/syncthing -no-browser -logfile=default
Exec=/usr/bin/syncthing serve --no-browser --logfile=default
Icon=syncthing
Terminal=false
Type=Application

View File

@@ -5,5 +5,4 @@ export HOME="/home/$USERNAME"
export SYNCTHING="$HOME/bin/syncthing"
exec 2>&1
exec chpst -u "$USERNAME" "$SYNCTHING" -logflags 0
exec chpst -u "$USERNAME" "$SYNCTHING" serve --logflags 0

View File

@@ -0,0 +1,3 @@
# Increase maximum receive socket buffer size to 2MiB for QUIC connections
# see https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
net.core.rmem_max = 2097152

View File

@@ -0,0 +1,21 @@
sysctl configuration to raise UDP buffer size
===================
Installation
-----------
**Please note:** When you installed syncthing using the official deb package, you can skip the copying.
Copy the file `30-syncthing.conf` to `/etc/sysctl.d/` (root permissions required).
In a terminal run
```
sudo sysctl -q --system
```
to apply the sysctl changes.
Verification
----------
You can verify that the new limit is active using
```
sysctl net.core.rmem_max
```

View File

@@ -5,9 +5,11 @@ After=network.target
[Service]
User=%i
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
Restart=on-failure
RestartSec=5
RestartSec=1
StartLimitIntervalSec=60
StartLimitBurst=4
SuccessExitStatus=3 4
RestartForceExitStatus=3 4

View File

@@ -3,9 +3,11 @@ Description=Syncthing - Open Source Continuous File Synchronization
Documentation=man:syncthing(1)
[Service]
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
Restart=on-failure
RestartSec=5
RestartSec=1
StartLimitIntervalSec=60
StartLimitBurst=4
SuccessExitStatus=3 4
RestartForceExitStatus=3 4

12
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/syncthing/syncthing
require (
github.com/AudriusButkevicius/pfilter v0.0.0-20210218141631-7468b85d810a
github.com/AudriusButkevicius/recli v0.0.5
github.com/alecthomas/kong v0.2.12
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e
github.com/calmh/xdr v1.1.0
github.com/ccding/go-stun v0.1.2
@@ -10,7 +11,6 @@ require (
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5
github.com/d4l3k/messagediff v1.2.1
github.com/dchest/siphash v1.2.2
github.com/dgraph-io/badger/v2 v2.0.3
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
github.com/getsentry/raven-go v0.2.0
github.com/go-ldap/ldap/v3 v3.2.4
@@ -28,8 +28,11 @@ require (
github.com/lucas-clemente/quic-go v0.19.3
github.com/maruel/panicparse v1.5.1
github.com/mattn/go-isatty v0.0.12
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 // indirect
github.com/minio/sha256-simd v0.1.1
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/gomega v1.10.3 // indirect
github.com/oschwald/geoip2-golang v1.4.0
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1
@@ -37,17 +40,18 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/sasha-s/go-deadlock v0.2.0
github.com/shirou/gopsutil/v3 v3.20.11
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc
github.com/syncthing/notify v0.0.0-20210308121556-f45149b04939
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7
github.com/thejerf/suture/v4 v4.0.0
github.com/urfave/cli v1.22.4
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
golang.org/x/text v0.3.4
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/protobuf v1.23.0
golang.org/x/tools v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
)
go 1.14

69
go.sum
View File

@@ -14,17 +14,15 @@ github.com/AudriusButkevicius/recli v0.0.5/go.mod h1:Q2E26yc6RvWWEz/TJ/goUp6yXvi
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/kong v0.2.12 h1:X3kkCOXGUNzLmiu+nQtoxWqj4U2a39MpSJR3QdQXOwI=
github.com/alecthomas/kong v0.2.12/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -34,7 +32,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
@@ -59,8 +56,6 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
@@ -71,14 +66,10 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
@@ -89,15 +80,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I=
github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/dgraph-io/badger/v2 v2.0.3 h1:inzdf6VF/NZ+tJ8RwwYMjJMvsOALTHYdozn0qSl6XJI=
github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM=
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU=
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
@@ -214,7 +198,6 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
@@ -248,7 +231,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -262,7 +244,6 @@ github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QS
github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
@@ -280,6 +261,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 h1:8E6DrFvII6QR4eJ3PkFvV+lc03P+2qwqTPLm1ax7694=
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0/go.mod h1:fcEyUyXZXoV4Abw8DX0t7wyL8mCDxXyU4iAFZfT3IHw=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -289,7 +272,6 @@ github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 h1:cUVxyR+U
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75/go.mod h1:pBbZyGwC5i16IBkjVKoy/sznA8jPD/K9iedwe1ESE6w=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -310,6 +292,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@@ -322,8 +306,9 @@ github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
@@ -341,7 +326,6 @@ github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
@@ -398,6 +382,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.20.11 h1:NeVf1K0cgxsWz+N3671ojRptdgzvp7BXL3KV21R0JnA=
@@ -435,17 +420,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@@ -456,15 +432,14 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc h1:b6b5XVKqpwxC6keIYThA5/XhFue4zNWRwv8FfqlKoxA=
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
github.com/syncthing/notify v0.0.0-20210308121556-f45149b04939 h1:InjitJPCBfhc1/DP0Z8OglJq5qvQD+J0o64TFyenf68=
github.com/syncthing/notify v0.0.0-20210308121556-f45149b04939/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 h1:udtnv1cokhJYqnUfCMCppJ71bFN9VKfG1BQ6UsYZnx8=
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/thejerf/suture/v4 v4.0.0 h1:GX3X+1Qaewtj9flL2wgoTBfLA5NcmrCY39TJRpPbUrI=
github.com/thejerf/suture/v4 v4.0.0/go.mod h1:g0e8vwskm9tI0jRjxrnA6lSr0q6OfPdWJVX7G5bVWRs=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
@@ -474,7 +449,7 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -493,7 +468,6 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -513,6 +487,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -536,6 +512,9 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -549,6 +528,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -559,13 +539,11 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -584,8 +562,8 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -613,7 +591,11 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -657,8 +639,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

View File

@@ -437,3 +437,9 @@ ul.three-columns li, ul.two-columns li {
.form-horizontal {
margin-bottom: 10px;
}
/* Use the same style as Bootstrap uses for disabled <select>. */
.form-control option[disabled] {
background-color: #eeeeee;
opacity: 1;
}

View File

@@ -100,9 +100,9 @@
"Downloaded": "Изтеглен",
"Downloading": "Изтегляне",
"Edit": "Промени",
"Edit Device": "Edit Device",
"Edit Device": "Промяна на устройството",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Промяна на папката",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Променяне на {{path}}.",
"Enable Crash Reporting": "Enable Crash Reporting",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "дни",
"directories": "directories",
"directories": "директории",
"files": "файла",
"full documentation": "пълна документация",
"items": "елемента",

View File

@@ -100,9 +100,9 @@
"Downloaded": "Descarregat",
"Downloading": "Descarregant",
"Edit": "Editar",
"Edit Device": "Edit Device",
"Edit Device": "Editar Dispositiu",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Editar Carpeta",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Editant {{path}}.",
"Enable Crash Reporting": "Enable Crash Reporting",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "dies",
"directories": "directories",
"directories": "directoris",
"files": "arxius",
"full documentation": "Documentació completa",
"items": "Elements",

View File

@@ -61,11 +61,11 @@
"Currently Shared With Devices": "i øjeblikket delt med enheder",
"Danger!": "Fare!",
"Debugging Facilities": "Faciliteter til fejlretning",
"Default Configuration": "Default Configuration",
"Default Device": "Default Device",
"Default Folder": "Default Folder",
"Default Configuration": "Standard opsætning",
"Default Device": "Standard enhed",
"Default Folder": "Standard mappe",
"Default Folder Path": "Standardmappesti",
"Defaults": "Defaults",
"Defaults": "Standarder",
"Delete Unexpected Items": "Slet ikke forventede elementer ",
"Deleted": "Slettet",
"Deselect All": "Fravælg alle",
@@ -101,9 +101,9 @@
"Downloading": "Downloader",
"Edit": "Redigér",
"Edit Device": "Redigere enhed",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Device Defaults": "Rediger enhed standarder",
"Edit Folder": "Redigere mappe",
"Edit Folder Defaults": "Edit Folder Defaults",
"Edit Folder Defaults": "Rediger mappe standarder",
"Editing {%path%}.": "Redigerer {{path}}.",
"Enable Crash Reporting": "Aktivere nedbrud rapportering",
"Enable NAT traversal": "Aktivér NAT-traversering",
@@ -374,7 +374,7 @@
"Uptime": "Oppetid",
"Usage reporting is always enabled for candidate releases.": "Forbrugsraportering er altid aktiveret for udgivelseskandidater.",
"Use HTTPS for GUI": "Anvend HTTPS til GUI-adgang",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Brugernavn/adgangskode er ikke indstillet til GUI godkendelse. Overvej at konfigurere det. ",
"Version": "Version",
"Versions": "Versioner",
"Versions Path": "Versionssti",
@@ -400,7 +400,7 @@
"You have no ignored folders.": "Du har ingen ignorerede mapper.",
"You have unsaved changes. Do you really want to discard them?": "Du har ændringer, som ikke er gemt. Er du sikker på, at du ikke vil beholde dem?",
"You must keep at least one version.": "Du skal beholde mindst én version.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Der bør aldrig tilføjes eller ændres noget lokalt i en \"{{receiveEncrypted}}\" mappe.",
"days": "dage",
"directories": "kataloger",
"files": "filer",
@@ -409,5 +409,5 @@
"seconds": "sekunder",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen “{{folder}}”.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker at dele mappen “{{folderlabel}}” ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} vil muligvis genindføre denne enhed."
}

View File

@@ -61,16 +61,16 @@
"Currently Shared With Devices": "Derzeit mit Geräten geteilt",
"Danger!": "Achtung!",
"Debugging Facilities": "Debugging-Möglichkeiten",
"Default Configuration": "Default Configuration",
"Default Device": "Default Device",
"Default Folder": "Default Folder",
"Default Configuration": "Standardkonfiguration",
"Default Device": "Standardgerät",
"Default Folder": "Standardordner",
"Default Folder Path": "Standardmäßiger Ordnerpfad",
"Defaults": "Defaults",
"Defaults": "Standards",
"Delete Unexpected Items": "Unerwartete Elemente löschen",
"Deleted": "Gelöscht",
"Deselect All": "Alle abwählen",
"Deselect devices to stop sharing this folder with.": "Die Geräte abwählen, für die dieser Ordner nicht mehr freigegeben werden soll.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Deselect folders to stop sharing with this device.": "Ordner abwählen um das Teilen mit dem Gerät zu stoppen.",
"Device": "Gerät",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät \"{{name}}\" ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Device ID": "Gerätekennung",
@@ -101,9 +101,9 @@
"Downloading": "Lädt herunter",
"Edit": "Bearbeiten",
"Edit Device": "Gerät bearbeiten",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Device Defaults": "Gerätestandards bearbeiten",
"Edit Folder": "Ordner bearbeiten",
"Edit Folder Defaults": "Edit Folder Defaults",
"Edit Folder Defaults": "Ordnerstandards bearbeiten",
"Editing {%path%}.": "Bearbeite {{path}}.",
"Enable Crash Reporting": "Absturzmeldung aktivieren",
"Enable NAT traversal": "NAT-Durchdringung aktivieren",
@@ -242,7 +242,7 @@
"Release Notes": "Veröffentlichungshinweise",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Veröffentlichungskandidaten enthalten die neuesten Funktionen und Verbesserungen. Sie gleichen den üblichen zweiwöchentlichen Syncthing-Veröffentlichungen.",
"Remote Devices": "Externe Geräte",
"Remote GUI": "Remote GUI",
"Remote GUI": "Entfernte Oberfläche",
"Remove": "Entfernen",
"Remove Device": "Gerät entfernen",
"Remove Folder": "Ordner entfernen",

View File

@@ -100,9 +100,9 @@
"Downloaded": "Elŝutita",
"Downloading": "Elŝutado",
"Edit": "Redakti",
"Edit Device": "Edit Device",
"Edit Device": "Redakti Aparaton",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Redakti Dosierujon",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Redaktado de {{path}}.",
"Enable Crash Reporting": "Ŝalti raportadon de kraŝoj",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Vi devas konservi almenaŭ unu version.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "tagoj",
"directories": "directories",
"directories": "dosierujoj",
"files": "dosieroj",
"full documentation": "tuta dokumentado",
"items": "eroj",

View File

@@ -119,7 +119,7 @@
"Failed Items": "Elementos fallidos",
"Failed to setup, retrying": "Fallo en la configuración, reintentando",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Se espera un fallo al conectar a los servidores IPv6 si no hay conectividad IPv6.",
"File Pull Order": "Orden de obtención de los ficheros",
"File Pull Order": "Orden de Obtención de los Archivos",
"File Versioning": "Versionado de ficheros",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Los ficheros son movidos a la carpeta .stversions cuando son reemplazados o borrados por Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Los ficheros son movidos a una carpeta .stversions a versiones con control de fecha cuando son reemplazados o borrados por Syncthing.",

View File

@@ -100,9 +100,9 @@
"Downloaded": "Ladattu",
"Downloading": "Ladataan",
"Edit": "Muokkaa",
"Edit Device": "Edit Device",
"Edit Device": "Muokkaa laitetta",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Muokkaa kansiota",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Muokkaa {{path}}.",
"Enable Crash Reporting": "Ota kaatumisraportointi käyttöön",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Sinun tulee säilyttää ainakin yksi versio.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "päivää",
"directories": "directories",
"directories": "kansiot",
"files": "tiedostot",
"full documentation": "täysi dokumentaatio",
"items": "kohteet",

View File

@@ -61,9 +61,9 @@
"Currently Shared With Devices": "Appareils membres actuels de ce partage :",
"Danger!": "Attention !",
"Debugging Facilities": "Outils de débogage",
"Default Configuration": "Personnalisation des créations",
"Default Device": "Définir comme référence pour les réglages",
"Default Folder": "Définir comme référence pour les réglages",
"Default Configuration": "Personnalisation des créations (non rétroactif)",
"Default Device": "Nouveaux appareils",
"Default Folder": "Nouveaux partages",
"Default Folder Path": "Chemin parent par défaut pour les nouveaux partages",
"Defaults": "Personnalisation",
"Delete Unexpected Items": "Supprimer les éléments inattendus",
@@ -212,7 +212,7 @@
"Outgoing Rate Limit (KiB/s)": "Limite du débit d'envoi (Kio/s)",
"Override Changes": "Écraser les changements",
"Path": "Chemin",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire à partager dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, ou ~+Espace sous Windows) peut être utilisé comme raccourci vers\nDans l'écran de \"Personnalisation\" des valeurs par défaut, ce champ indique le chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin de base suggéré lors de l'enregistrement des nouveaux partages. Le caractère tilde (~) est un raccourci pour {{tilde}}.",
"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": "Dans l'écran de \"Personnalisation\" des valeurs par défaut (menu Actions/Configuration), ce champ indique le chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin de base suggéré lors de l'enregistrement des nouveaux partages. Le caractère tilde (~) est un raccourci pour votre répertoire personnel {{tilde}} .\nEn création/acceptation manuelle d'un nouveau partage, c'est le chemin vers le répertoire à partager dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, taper ~+Espace sous Windows) peut être utilisé comme raccourci vers",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin suggéré lors de l'enregistrement des nouveaux partages via cette interface graphique. Le caractère tilde (~) est un raccourci pour {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Chemin où les versions seront conservées (laisser vide pour le chemin par défaut de .stversions (caché) dans le partage).\nChemin relatif ou absolu (recommandé), mais dans un répertoire non synchronisé (par masque ou hors du chemin du partage).\nSur la même partition ou système de fichiers (recommandé).",
"Pause": "Pause",

View File

@@ -100,9 +100,9 @@
"Downloaded": "Downloaded",
"Downloading": "Oan it downloaden",
"Edit": "Bewurkje",
"Edit Device": "Edit Device",
"Edit Device": "Apparaat Bewurkje",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Map Bewurkje",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "{{path}} wurd bewurke.",
"Enable Crash Reporting": "Automatyske Rapportaazje fan Fêstrinners Oansette",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Jo moatte minstens ien ferzje bewarje.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "dagen",
"directories": "directories",
"directories": "triemtafels",
"files": "triemmen",
"full documentation": "komplete dokumintaasje",
"items": "items",

View File

@@ -61,11 +61,11 @@
"Currently Shared With Devices": "Attualmente Condiviso Con Dispositivi",
"Danger!": "Pericolo!",
"Debugging Facilities": "Servizi di Debug",
"Default Configuration": "Default Configuration",
"Default Device": "Default Device",
"Default Folder": "Default Folder",
"Default Configuration": "Configurazione predefinita",
"Default Device": "Dispositivo predefinito",
"Default Folder": "Cartella predefinita",
"Default Folder Path": "Percorso Cartella di Default",
"Defaults": "Defaults",
"Defaults": "Impostazioni predefinite",
"Delete Unexpected Items": "Elimina elementi imprevisti",
"Deleted": "Cancellato",
"Deselect All": "Deseleziona tutto",
@@ -101,9 +101,9 @@
"Downloading": "Scaricamento in corso",
"Edit": "Modifica",
"Edit Device": "Modifica Dispositivo",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Device Defaults": "Modifica impostazioni predefinite dispositivo",
"Edit Folder": "Modifica Cartella",
"Edit Folder Defaults": "Edit Folder Defaults",
"Edit Folder Defaults": "Modifica impostazioni predefinite cartella",
"Editing {%path%}.": "Modifica di {{path}}.",
"Enable Crash Reporting": "Attiva la Segnalazione degli arresti anomali",
"Enable NAT traversal": "Abilita NAT traversal",

View File

@@ -100,9 +100,9 @@
"Downloaded": "ダウンロード済",
"Downloading": "ダウンロード中",
"Edit": "編集",
"Edit Device": "Edit Device",
"Edit Device": "デバイスの編集",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "フォルダーの編集",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "{{path}} を編集中",
"Enable Crash Reporting": "クラッシュレポートを有効にする",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "少なくとも一つのバージョンを保持する必要があります。",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "日",
"directories": "directories",
"directories": "個のディレクトリ",
"files": "個のファイル",
"full documentation": "詳細なマニュアル",
"items": "項目",

View File

@@ -26,7 +26,7 @@
"Anonymous Usage Reporting": "익명 사용 보고서",
"Anonymous usage report format has changed. Would you like to move to the new format?": "익명 사용 리포트의 형식이 변경되었습니다. 새 형식으로 이동 하시겠습니까?",
"Are you sure you want to permanently delete all these files?": "Are you sure you want to permanently delete all these files?",
"Are you sure you want to remove device {%name%}?": "{{name}} 장치를 제거 하시겠습니까?",
"Are you sure you want to remove device {%name%}?": "{{name}} 기기를 정말로 제거하시겠습니까?",
"Are you sure you want to remove folder {%label%}?": "{{label}} 폴더를 제거 하시겠습니까?",
"Are you sure you want to restore {%count%} files?": "{{count}} 개의 파일을 복원 하시겠습니까?",
"Are you sure you want to upgrade?": "업그레이드를 하시겠습니까?",
@@ -35,14 +35,14 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "자동 업데이트를 이제 안정 버전과 출시 후보 사이에 선택 할 수 있게 됩니다.",
"Automatic upgrades": "자동 업데이트",
"Automatic upgrades are always enabled for candidate releases.": "후보 릴리즈에선 자동 업데이트가 항상 활성화 됩니다.",
"Automatically create or share folders that this device advertises at the default path.": "이 장치가 기본 경로에서 알리는 폴더를 자동으로 만들거나 공유합니다.",
"Automatically create or share folders that this device advertises at the default path.": "이 기기가 기본 경로에서 알리는 폴더를 자동으로 만들거나 공유합니다.",
"Available debug logging facilities:": "사용 가능한 디버그 로깅 기능:",
"Be careful!": "주의!",
"Bugs": "버그",
"Changelog": "바뀐 점",
"Clean out after": "삭제 후",
"Cleaning Versions": "Cleaning Versions",
"Cleanup Interval": "Cleanup Interval",
"Cleaning Versions": "버전 정리 중",
"Cleanup Interval": "정리 간격",
"Click to see discovery failures": "탐색 실패 보기",
"Close": "닫기",
"Command": "커맨드",
@@ -61,18 +61,18 @@
"Currently Shared With Devices": "현재 공유된 기기들",
"Danger!": "경고!",
"Debugging Facilities": "디버깅 기능",
"Default Configuration": "Default Configuration",
"Default Device": "Default Device",
"Default Folder": "Default Folder",
"Default Configuration": "기본 설정",
"Default Device": "기본 기기",
"Default Folder": "기본 폴더",
"Default Folder Path": "기본 폴더 경로",
"Defaults": "Defaults",
"Defaults": "기본 설정",
"Delete Unexpected Items": "Delete Unexpected Items",
"Deleted": "삭제됨",
"Deselect All": "Deselect All",
"Deselect All": "모두 선택 해제",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Device": "기기",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "다른 기기가 \"{{name}}\" ({{device}} {{address}})에서 접속을 요청했습니다. 새 기기를 추가하시겠습니까?",
"Device ID": "기기 ID",
"Device Identification": "기기 식별자",
"Device Name": "기기 이름",
@@ -82,8 +82,8 @@
"Devices": "기기",
"Disable Crash Reporting": "충돌 보고 해제",
"Disabled": "비활성화",
"Disabled periodic scanning and disabled watching for changes": "주기적 스캔을 사용 중지하고 변경 사항을 감시하지 않음",
"Disabled periodic scanning and enabled watching for changes": "주기적 스캔을 사용 중지하고 변경 사항 감시 하기",
"Disabled periodic scanning and disabled watching for changes": "주기적 탐색을 사용 중지하고 변경 사항을 감시하지 않음",
"Disabled periodic scanning and enabled watching for changes": "주기적 탐색을 사용 중지하고 변경 사항 감시 하기",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).",
"Discard": "Discard",
@@ -101,9 +101,9 @@
"Downloading": "다운로드 중",
"Edit": "편집",
"Edit Device": "기기 수정",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Device Defaults": "기기 기본 설정 변경",
"Edit Folder": "폴더 수정",
"Edit Folder Defaults": "Edit Folder Defaults",
"Edit Folder Defaults": "폴더 기본 설정 변경",
"Editing {%path%}.": "{{path}} 수정하기.",
"Enable Crash Reporting": "충돌 보고 활성화",
"Enable NAT traversal": "NAT traversal 활성화",
@@ -123,7 +123,7 @@
"File Versioning": "파일 버전 관리",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "파일이 Syncthing에 의해서 교체되거나 삭제되면 .stversions 폴더로 이동됩니다.",
"Files are moved to date stamped versions in a .stversions directory 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 protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "다른 기기가 파일을 편집할 수 없으며 반드시 이 장치의 내용을 기준으로 동기화합니다.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Watcher Errors": "파일 시스템 감시 오류",
"Filter by date": "날짜별 정렬",
@@ -175,7 +175,7 @@
"Listeners": "수신자",
"Loading data...": "데이터 불러오는중...",
"Loading...": "불러오는 중...",
"Local Additions": "Local Additions",
"Local Additions": "로컬 변경사항",
"Local Discovery": "로컬 노드 검색",
"Local State": "로컬 상태",
"Local State (Total)": "로컬 상태 (합계)",
@@ -200,7 +200,7 @@
"No File Versioning": "파일 버전 관리 안 함",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "업데이트 안함",
"Not shared": "Not shared",
"Not shared": "공유되지 않음",
"Notice": "공지",
"OK": "확인",
"Off": "꺼짐",
@@ -229,12 +229,12 @@
"Please wait": "기다려 주십시오",
"Prefix indicating that the file can be deleted if preventing directory removal": "디렉토리 제거를 방지 할 경우 파일을 삭제할 수 있음을 나타내는 접두사",
"Prefix indicating that the pattern should be matched without case sensitivity": "대소 문자를 구분하지 않고 패턴을 일치시켜야 함을 나타내는 접두사",
"Preparing to Sync": "Preparing to Sync",
"Preparing to Sync": "동기화 준비 중",
"Preview": "미리보기",
"Preview Usage Report": "사용 보고서 미리보기",
"Quick guide to supported patterns": "지원하는 패턴에 대한 빠른 도움말",
"Random": "무작위",
"Receive Encrypted": "Receive Encrypted",
"Receive Encrypted": "암호화 수신",
"Receive Only": "수신 전용",
"Received data is already encrypted": "Received data is already encrypted",
"Recent Changes": "최근 변경",
@@ -242,11 +242,11 @@
"Release Notes": "릴리즈 노트",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "출시 후보는 최신 기능과 버그 픽스를 포함 하고 있습니다. 이 버전은 예전 방식인 2주 주기 Syncthing 출시와 비슷합니다.",
"Remote Devices": "원격 기기",
"Remote GUI": "Remote GUI",
"Remote GUI": "원격 GUI",
"Remove": "삭제",
"Remove Device": "기기 제거",
"Remove Folder": "폴더 제거",
"Required identifier for the folder. Must be the same on all cluster devices.": "폴더 식별자가 필요합니다. 모든 장치에서 동일해야 합니다.",
"Required identifier for the folder. Must be the same on all cluster devices.": "폴더 식별자가 필요합니다. 모든 기기에서 동일해야 합니다.",
"Rescan": "재탐색",
"Rescan All": "전체 재탐색",
"Rescans": "재탐색",
@@ -258,18 +258,18 @@
"Resume": "재개",
"Resume All": "모두 재개",
"Reused": "재개",
"Revert Local Changes": "Revert Local Changes",
"Revert Local Changes": "로컬 변경사항 복귀",
"Save": "저장",
"Scan Time Remaining": "탐색 남은 시간",
"Scanning": "탐색중",
"Scanning": "탐색 중",
"See external versioning help for supported templated command line parameters.": "지원되는 템플릿 명령 행 매개 변수에 대해서는 외부 버전 도움말을 참조하십시오.",
"Select All": "모두 선택",
"Select a version": "버전 선택",
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
"Select additional devices to share this folder with.": "이 폴더를 추가로 공유할 기기를 선택하세요.",
"Select additional folders to share with this device.": "Select additional folders to share with this device.",
"Select latest version": "가장 최신 버전 선택",
"Select oldest version": "가장 오래된 버전 선택",
"Select the folders to share with this device.": "이 장치와 공유할 폴더를 선택합니다.",
"Select the folders to share with this device.": "이 기기와 공유할 폴더를 선택합니다.",
"Send & Receive": "송신 & 수신",
"Send Only": "송신 전용",
"Settings": "설정",
@@ -277,21 +277,21 @@
"Share Folder": "폴더 공유",
"Share Folders With Device": "폴더를 공유할 기기",
"Share this folder?": "이 폴더를 공유하시겠습니까?",
"Shared Folders": "Shared Folders",
"Shared Folders": "공유 폴더",
"Shared With": "~와 공유",
"Sharing": "Sharing",
"Sharing": "공유",
"Show ID": "내 기기 ID",
"Show QR": "QR 코드 보기",
"Show diff with previous version": "이전 버전과 변경사항 보기",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "장치에 대한 아이디로 표시됩니다. 옵션에 얻은 기본이름으로 다른장치에 통보합니다.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "아이디가 비어있는 경우 기본 값으로 다른 장치에 업데이트 됩니다.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "기기에 대한 아이디로 표시됩니다. 옵션에 얻은 기본 이름으로 다른 기기에 통보합니다.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "아이디가 비어 있는 경우 기본 값으로 다른 기기에 업데이트됩니다.",
"Shutdown": "종료",
"Shutdown Complete": "종료 완료",
"Simple File Versioning": "간단한 파일 버전 관리",
"Single level wildcard (matches within a directory only)": "단일 레벨 와일드카드 (하나의 디렉토리만 일치하는 경우)",
"Size": "크기",
"Smallest First": "작은 파일순",
"Some items could not be restored:": "몇몇 항목들은 복구 할 수 없었습니다:",
"Some items could not be restored:": "몇몇 항목들은 복구할 수 없었습니다:",
"Source Code": "소스 코드",
"Stable releases and release candidates": "안정 버전과 출시 후보",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "안정 버전은 약 2주 정도 지연되어 출시됩니다. 그 기간 동안 출시 후보에 대한 테스트를 거칩니다.",
@@ -318,10 +318,10 @@
"The Syncthing Authors": "The Syncthing Authors",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing 관리자 인터페이스가 암호 없이 원격 접속이 허가되도록 설정되었습니다.",
"The aggregated statistics are publicly available at the URL below.": "수집된 통계는 아래 URL에서 공개적으로 볼 수 있습니다.",
"The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.",
"The cleanup interval cannot be blank.": "정리 간격은 비워 둘 수 없습니다.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "설정이 저장되었지만 활성화되지 않았습니다. 설정을 활성화 하려면 Syncthing을 다시 시작하세요.",
"The device ID cannot be blank.": "기기 ID는 비워 둘 수 없습니다.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "여기에 입력한 기기 ID가 다른 장치의 \"동작 - ID 보기\"에 표시됩니다. 공백과 하이픈은 세지 않습니다. 즉 무시됩니다.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "여기에 입력한 기기 ID가 다른 기기의 \"동작 > ID 보기\"에 표시됩니다. 공백과 하이픈은 세지 않습니다. 즉 무시됩니다.",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "암호화된 사용 보고서는 매일 전송됩니다 사용 중인 플랫폼과 폴더 크기, 앱 버전이 포함되어 있습니다. 전송되는 데이터가 변경되면 다시 이 대화 상자가 나타납니다.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "입력한 기기 ID가 올바르지 않습니다. 52/56자의 알파벳과 숫자로 구성되어 있으며, 공백과 하이픈은 포함되지 않습니다.",
"The folder ID cannot be blank.": "폴더 ID는 비워 둘 수 없습니다.",
@@ -341,8 +341,8 @@
"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 rescan interval must be a non-negative number of seconds.": "재색 간격은 초단위이며 양수로 입력해야 합니다.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"The rescan interval must be a non-negative number of seconds.": "재색 간격은 초단위이며 양수로 입력해야 합니다.",
"There are no devices to share this folder with.": "이 폴더를 공유할 기기가 없습니다.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"They are retried automatically and will be synced when the error is resolved.": "오류가 해결되면 자동적으로 동기화 됩니다.",
"This Device": "현재 기기",
@@ -362,7 +362,7 @@
"Unignore": "Unignore",
"Unknown": "알 수 없음",
"Unshared": "공유되지 않음",
"Unshared Devices": "공유되지 않은 기기",
"Unshared Devices": "공유되지 않은 기기",
"Unshared Folders": "Unshared Folders",
"Untrusted": "Untrusted",
"Up to Date": "최신 데이터",
@@ -379,9 +379,9 @@
"Versions": "버전",
"Versions Path": "버전 저장 경로",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "최대 보존 기간보다 오래되었거나 지정한 개수를 넘긴 버전은 자동으로 삭제됩니다.",
"Waiting to Clean": "Waiting to Clean",
"Waiting to Scan": "Waiting to Scan",
"Waiting to Sync": "Waiting to Sync",
"Waiting to Clean": "정리 대기 중",
"Waiting to Scan": "탐색 대기 중",
"Waiting to Sync": "동기화 대기 중",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "경고, 이 경로는 현재 존재하는 폴더 \"{{otherFolder}}\" 의 상위 폴더 입니다.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "경고, 이 경로는 현재 존재하는 폴더 \"{{otherFolderLabel}}\" ({{otherFolder}}) 의 상위 폴더 입니다.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "경고, 이 경로는 현재 존재하는 폴더 \"{{otherFolder}}\" 의 하위 폴더 입니다.",
@@ -390,19 +390,19 @@
"Watch for Changes": "변경 사항 감시",
"Watching for Changes": "변경 사항 감시",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "새 장치를 추가할 시 추가한 기기 쪽에서도 이 장치를 추가해야 합니다.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "새 기기를 추가할 시 추가한 기기 쪽에서도 이 기기를 추가해야 합니다.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 기기 간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 기기에서 같은 ID를 사용해야 합니다.",
"Yes": "예",
"You can also select one of these nearby devices:": "주변 기기 중 하나를 선택할 수 있습니다:",
"You can change your choice at any time in the Settings dialog.": "설정창 에서 언제든지 원하시는 때에 설정을 변경하는 것이 가능합니다.",
"You can read more about the two release channels at the link below.": "이 두 개의 출시 채널에 대해 아래 링크에서 자세하게 읽어 보실 수 있습니다.",
"You have no ignored devices.": "You have no ignored devices.",
"You have no ignored devices.": "무시된 기기가 없습니다.",
"You have no ignored folders.": "You have no ignored folders.",
"You have unsaved changes. Do you really want to discard them?": "저장되지 않은 변경이 있습니다. 변경사항을 무시하시겠습니까?",
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "일",
"directories": "directories",
"directories": "디렉토리",
"files": "파일",
"full documentation": "전체 문서",
"items": "항목",

View File

@@ -100,9 +100,9 @@
"Downloaded": "Lastet ned",
"Downloading": "Laster ned",
"Edit": "Rediger",
"Edit Device": "Edit Device",
"Edit Device": "Rediger enhet",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Rediger mappe",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Redigerer {{path}}.",
"Enable Crash Reporting": "Skru på krasjrapportering",
@@ -402,11 +402,11 @@
"You must keep at least one version.": "Du må beholde minst én versjon",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "dager",
"directories": "directories",
"directories": "mapper",
"files": "filer",
"full documentation": "all dokumentasjon",
"items": "elementer",
"seconds": "seconds",
"seconds": "sekunder",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappa \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker å dele mappa \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."

View File

@@ -1,7 +1,7 @@
{
"A device with that ID is already added.": "Urządzenie o tym ID już istnieje.",
"A negative number of days doesn't make sense.": "Ujemna ilość dni nie ma sensu.",
"A new major version may not be compatible with previous versions.": "Nowa wersja może być niekompatybilna z poprzednimi wersjami.",
"A negative number of days doesn't make sense.": "Ujemna liczba dni nie ma sensu.",
"A new major version may not be compatible with previous versions.": "Nowa duża wersja może nie być kompatybilna z poprzednimi wersjami.",
"API Key": "Klucz API",
"About": "O Syncthing",
"Action": "Akcja",
@@ -10,121 +10,121 @@
"Add Device": "Dodaj urządzenie",
"Add Folder": "Dodaj folder",
"Add Remote Device": "Dodaj urządzenie zdalne",
"Add devices from the introducer to our device list, for mutually shared folders.": "Dodaj urządzenia od wprowadzającego do tej maszyny alby obustronnie dzielić katalogi.",
"Add new folder?": "Dodać nowy folder?",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Dodatkowo, interwał całościowego skanowania będzie zwiększony (60x). Możesz również zmienić go później ręcznie dla każdego folderu wybierając \"Nie\"",
"Add devices from the introducer to our device list, for mutually shared folders.": "Dodaj urządzenia od wprowadzającego do własnej listy urządzeń dla folderów współdzielonych obustronnie.",
"Add new folder?": "Czy chcesz dodać nowy folder?",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Dodatkowo, czas przedziału pełnego ponownego skanowania zostanie zwiększony (o 60 razy, tj. do nowej domyślnej wartości \"1h\"). Możesz również ustawić go później ręcznie dla każdego folderu wybierając \"Nie\".",
"Address": "Adres",
"Addresses": "Adresy",
"Advanced": "Zaawansowane",
"Advanced Configuration": "Konfiguracja zaawansowana",
"Advanced Configuration": "Zaawansowane ustawienia",
"All Data": "Wszystkie dane",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.",
"Allow Anonymous Usage Reporting?": "Zezwalaj na anonimowe statystyki użycia?",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Wszystkie foldery współdzielone z tym urządzeniem muszą być zabezpieczone hasłem, tak aby żadne przesyłane dane nie były możliwe do odczytu bez podania tego hasła.",
"Allow Anonymous Usage Reporting?": "Czy chcesz zezwolić na anonimowe statystyki użycia?",
"Allowed Networks": "Dozwolone sieci",
"Alphabetic": "Alfabetycznie",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Zewnętrzne polecenie obsługuje wersjonowanie. Musi ono usunąć plik z dzielonego foldeur. Jeśli ścieżka do aplikacji zawiera odstępy (spacje), powinna być zamknięta w cudzysłowie.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Zewnętrzne polecenie odpowiedzialne jest za wersjonowanie. Musi ono usunąć plik ze współdzielonego folderu. Jeśli ścieżka do aplikacji zawiera spacje, powinna ona być zamknięta w cudzysłowie.",
"Anonymous Usage Reporting": "Anonimowe statystyki użycia",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Format anonimowego raportu zużycia uległ zmianie.\nCzy chcesz przejść na nowy format?",
"Are you sure you want to permanently delete all these files?": "Are you sure you want to permanently delete all these files?",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Format anonimowych statystyk użycia uległ zmianie. Czy chcesz przejść na nowy format?",
"Are you sure you want to permanently delete all these files?": "Czy na pewno chcesz nieodwracalnie usunąć wszystkie te pliki?",
"Are you sure you want to remove device {%name%}?": "Czy na pewno chcesz usunąć urządzenie {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Czy na pewno chcesz usunąć folder {{label}}?",
"Are you sure you want to restore {%count%} files?": "Czy na pewno chcesz przywrócić {{count}} plików?",
"Are you sure you want to upgrade?": "Are you sure you want to upgrade?",
"Are you sure you want to upgrade?": "Czy na pewno chcesz zezwolić na aktualizację?",
"Auto Accept": "Autoakceptacja",
"Automatic Crash Reporting": "Automatyczne raportowanie awarii",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatyczne aktualizacje pozwalają teraz wybrać pomiędzy wydaniami stabilnymi a wersjami kandydującymi.",
"Automatic Crash Reporting": "Automatyczne zgłaszanie awarii",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatycznie aktualizacje pozwalają teraz na wybór pomiędzy wydaniami stabilnymi oraz wydaniami kandydującymi.",
"Automatic upgrades": "Automatyczne aktualizacje",
"Automatic upgrades are always enabled for candidate releases.": "Aktualizacje automatyczne są zawsze włączone dla wydań kandydujących programu",
"Automatically create or share folders that this device advertises at the default path.": "Automatycznie utwórz lub udostępniaj katalogi udostępniane przez te urządzenie w domyślnej ścieżce",
"Available debug logging facilities:": "Dostępne narzędzia logowania debugowego",
"Be careful!": "Uważaj!",
"Automatic upgrades are always enabled for candidate releases.": "Automatyczne aktualizacje są zawsze włączone dla wydań kandydujących.",
"Automatically create or share folders that this device advertises at the default path.": "Automatycznie utwórz lub współdziel foldery wysyłane przez to urządzenie w domyślnej ścieżce.",
"Available debug logging facilities:": "Dostępne narzędzia logujące do debugowania:",
"Be careful!": "Ostrożnie!",
"Bugs": "Błędy",
"Changelog": "Historia zmian",
"Clean out after": "Uporządkuj",
"Cleaning Versions": "Wyczyść wersje",
"Cleanup Interval": "Cleanup Interval",
"Click to see discovery failures": "Kliknij aby zobaczyć błędy odnajdywania",
"Clean out after": "Opróżnij po",
"Cleaning Versions": "Czyszczenie wersji",
"Cleanup Interval": "Przedział czasowy czyszczenia",
"Click to see discovery failures": "Kliknij tutaj, aby zobaczyć błędy odnajdywania",
"Close": "Zamknij",
"Command": "Polecenie",
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
"Comment, when used at the start of a line": "Komentarz, jeżeli znajduje się na początku linii",
"Compression": "Kompresja",
"Configured": "Skonfigurowane",
"Connected (Unused)": "Połączone (nieużywane)",
"Configured": "Ustawiony",
"Connected (Unused)": "Połączony (nieużywany)",
"Connection Error": "Błąd połączenia",
"Connection Type": "Rodzaj połączenia",
"Connections": "Połączenia",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Ciągle obserwowanie zmian jest już dostępne w Syncthing. Będzie ono wykrywać zmiany na dysku i uruchamiać skanowanie tylko zmodyfikowanych plików. Zyski są takie, że zmiany są znacznie szybciej rozsyłane, i wymagana jest mniejsza ilość pełnych skanowań",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Ciągłe obserwowanie zmian jest już dostępne w Syncthing. Będzie ono wykrywać zmiany na dysku i uruchamiać skanowanie tylko w zmodyfikowanych ścieżkach. Zalety tego są takie, że zmiany rozsyłane są znacznie szybciej oraz że wymagana jest mniejsza liczba pełnych skanowań.",
"Copied from elsewhere": "Skopiowane z innego miejsca ",
"Copied from original": "Skopiowane z oryginału",
"Copyright © 2014-2019 the following Contributors:": "Wszelkie prawa zastrzeżone © 2014-2019 dla twórców:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Ustawienie wzorów ignorowania, nadpisze istniejący plik w {{path}}.",
"Currently Shared With Devices": "Aktualnie udostępniane między urządzeniami",
"Danger!": "Niebezpieczne!",
"Debugging Facilities": "Odpluskwianie",
"Default Configuration": "Default Configuration",
"Default Device": "Default Device",
"Default Folder": "Default Folder",
"Copyright © 2014-2019 the following Contributors:": "Wszelkie prawa zastrzeżone © 2014-2019 dla współtwórców:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Ustawianie wzorów ignorowania, nadpisze istniejący plik w {{path}}.",
"Currently Shared With Devices": "Obecnie współdzielony z urządzeniami",
"Danger!": "Niebezpieczeństwo!",
"Debugging Facilities": "Narzędzia do debugowania",
"Default Configuration": "Domyślne ustawienia",
"Default Device": "Domyślne urządzenie",
"Default Folder": "Domyślny folder",
"Default Folder Path": "Domyślna ścieżka folderu",
"Defaults": "Defaults",
"Delete Unexpected Items": "Delete Unexpected Items",
"Defaults": "Domyślne",
"Delete Unexpected Items": "Usuń elementy nieoczekiwane",
"Deleted": "Usunięto",
"Deselect All": "Odznacz wszystko",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
"Deselect All": "Odznacz wszystkie",
"Deselect devices to stop sharing this folder with.": "Odznacz urządzenia, z którymi chcesz przestać współdzielić ten folder.",
"Deselect folders to stop sharing with this device.": "Odznacz foldery, które chcesz przestać współdzielić z tym urządzeniem.",
"Device": "Urządzenie",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie \"{{name}}\" {{device}} ({{address}}) chce się połączyć. Dodać nowe urządzenie?",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie \"{{name}}\" {{device}} pod ({{address}}) chce się połączyć. Dodać nowe urządzenie?",
"Device ID": "ID urządzenia",
"Device Identification": "Identyfikator urządzenia",
"Device Name": "Nazwa urządzenia",
"Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password",
"Device rate limits": "Limity transferu urządzenia",
"Device that last modified the item": "Urządzenie, które jako ostatnie zmodyfikowało element",
"Device is untrusted, enter encryption password": "Urządzenie jest niezaufane. Wprowadź szyfrujące hasło.",
"Device rate limits": "Ograniczenia prędkości urządzenia",
"Device that last modified the item": "Urządzenie, które jako ostatnie zmodyfikowało ten element",
"Devices": "Urządzenia",
"Disable Crash Reporting": "Wyłącz raportowanie błędów",
"Disable Crash Reporting": "Wyłącz zgłaszanie awarii",
"Disabled": "Wyłączone",
"Disabled periodic scanning and disabled watching for changes": "Wyłączono okresowe skanowanie i wyłączono obserwowanie zmian",
"Disabled periodic scanning and enabled watching for changes": "Wyłączono okresowe skanowanie i włączono obserwowanie zmian",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Wyłączono okresowe skanowanie i nie udało się skonfigurować obserwowania zmian, powtórzę co minutę:",
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).",
"Disabled periodic scanning and disabled watching for changes": "Wyłączone okresowe skanowanie i wyłączone obserwowanie zmian",
"Disabled periodic scanning and enabled watching for changes": "Wyłączone okresowe skanowanie i włączone obserwowanie zmian",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Wyłączone okresowe skanowanie i nieudane ustawienie obserwowania zmian, ponawiam co minutę:",
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Wyłącza porównywanie i synchronizację uprawnień plików. Przydatne w systemach, w których uprawnienia nie istnieją bądź są one niestandardowe (np. FAT, exFAT, Synology, Android).",
"Discard": "Odrzuć",
"Disconnected": "Rozłączony",
"Disconnected (Unused)": "Rozłączono (nieużywane)",
"Discovered": "Odkryte",
"Disconnected (Unused)": "Rozłączony (nieużywany)",
"Discovered": "Odnaleziony",
"Discovery": "Odnajdywanie",
"Discovery Failures": "Błędy odnajdowania",
"Discovery Failures": "Błędy odnajdywania",
"Do not restore": "Nie przywracaj",
"Do not restore all": "Nie przywracaj wszystkich",
"Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich swoich folderach?",
"Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich folderach?",
"Documentation": "Dokumentacja",
"Download Rate": "Prędkość pobierania",
"Downloaded": "Pobrane",
"Downloading": "Pobieranie",
"Edit": "Edytuj",
"Edit Device": "Edytuj urządzenie",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Device Defaults": "Edytuj domyślne ustawienia urządzeń",
"Edit Folder": "Edytuj folder",
"Edit Folder Defaults": "Edit Folder Defaults",
"Edit Folder Defaults": "Edytuj domyślne ustawienia folderów",
"Editing {%path%}.": "Edytowanie {{path}}.",
"Enable Crash Reporting": "Włącz raportowanie błędów",
"Enable Crash Reporting": "Włącz zgłaszanie awarii",
"Enable NAT traversal": "Włącz trawersowanie NAT",
"Enable Relaying": "Włącz przekazywanie",
"Enabled": "Włączone",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Wpisz nieujemną liczbę (np. \"2.35\") oraz wybierz jednostkę. Wartość procentowa odnosi się do rozmiaru całego dysku.",
"Enter a non-privileged port number (1024 - 65535).": "Wpisz nieuprzywilejowany numer portu (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Wprowadź adresy oddzielone przecinkiem (\"tcp://ip:port\", \"tcp://host:port\") lub \"dynamic\" celem automatycznego odkrycia adresu.",
"Enter ignore patterns, one per line.": "Wprowadź wzorce ignorowania, jeden w każdej linii.",
"Enter up to three octal digits.": "Enter up to three octal digits.",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Wprowadź nieujemną liczbę (np. \"2.35\") oraz wybierz jednostkę. Procenty odnos się do rozmiaru całego dysku.",
"Enter a non-privileged port number (1024 - 65535).": "Wprowadź nieuprzywilejowany numer portu (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Wprowadź adresy oddzielone przecinkiem (\"tcp://ip:port\", \"tcp://host:port\") lub \"dynamic\" w celu automatycznego odnajdywania adresu.",
"Enter ignore patterns, one per line.": "Wprowadź wzorce ignorowania, po jednym w każdej linii.",
"Enter up to three octal digits.": "Wprowadź maksymalnie trzy cyfry ósemkowe.",
"Error": "Błąd",
"External File Versioning": "Zewnętrzne wersjonowanie pliku",
"Failed Items": "Niepowodzenia",
"Failed to setup, retrying": "Nie udało się skonfigurować, ponawiam",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Błąd połączenia do serwerów IPv6 może wystąpić, jeśli brakuje połączenia po IPv6 w ogóle.",
"External File Versioning": "Zewnętrzne wersjonowanie plików",
"Failed Items": "Elementy zakończone niepowodzeniem",
"Failed to setup, retrying": "Nie udało się ustawić, ponawiam",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Błąd połączenia do serwerów IPv6 może wystąpić, gdy w ogóle nie ma połączenia po IPv6.",
"File Pull Order": "Kolejność pobierania plików",
"File Versioning": "Kontrola wersji",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Pliki są przenoszone do folderu .stversions przez Syncthing, kiedy zostaną zmienione lub usunięte.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Pliki są datowane i przenoszone do folderu .stversions przez Syncthing, kiedy zostaną zmienione lub usunięte.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Pliki są zabezpieczone przed zmianami na innym urządzeniu, jednak zmiany w tym urządzeniu będą wysłane do reszty.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Pliki są synchronizowane z klastrem, ale wszelkie zmiany wprowadzone lokalnie nie będą wysyłane do innych urządzeń.",
"File Versioning": "Wersjonowanie plików",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Pliki zmienione lub usunięte przez Syncthing są przenoszone do katalogu .stversions.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Pliki zmienione lub usunięte przez Syncthing są datowane i przenoszone do katalogu .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.": "Pliki są zabezpieczone przed zmianami dokonanymi na innych urządzeniach, ale zmiany dokonane na tym urządzeniu będą wysyłane do pozostałych urządzeń.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Pliki są synchronizowane z pozostałych urządzeń, ale jakiekolwiek zmiany dokonane lokalnie nie będą wysyłanie do innych urządzeń.",
"Filesystem Watcher Errors": "Błędy obserwatora plików",
"Filter by date": "Filtruj według daty",
"Filter by name": "Filtruj według nazwy",
@@ -133,122 +133,122 @@
"Folder Label": "Etykieta folderu",
"Folder Path": "Ścieżka folderu",
"Folder Type": "Rodzaj folderu",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Rodzaj folderu \"{{receiveEncrypted}}\" nie może być zmieniony po dodaniu folderu. Musisz najpierw usunąć folder, skasować bądź też odszyfrować dane na dysku, a następnie dodać folder ponownie.",
"Folders": "Foldery",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "W przypadku następujących folderów wystąpił błąd podczas rozpoczynania monitorowania zmian. Akcja będzie ponawiana co minutę, więc błędy mogą zniknąć. Jeśli nie uda się pozbyć błędów, spróbuj naprawić zasadniczy problem lub poproś o pomoc, jeśli nie możesz tego zrobić.",
"Full Rescan Interval (s)": "Interwał pełnego skanowania (s)",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Wystąpił błąd podczas rozpoczynania obserwowania zmian w następujących folderach. Akcja będzie ponawiana co minutę, więc błędy mogą niebawem zniknąć. Jeżeli nie uda pozbyć się błędów, spróbuj naprawić ukryty problem lub poproś o pomoc, jeżeli nie będziesz w stanie tego zrobić.",
"Full Rescan Interval (s)": "Przedział czasowy pełnego skanowania (s)",
"GUI": "GUI",
"GUI Authentication Password": "Hasło",
"GUI Authentication User": "Użytkownik",
"GUI Authentication: Set User and Password": "GUI Authentication: Set User and Password",
"GUI Authentication Password": "Hasło uwierzytelniające GUI",
"GUI Authentication User": "Użytkownik uwierzytelniający GUI",
"GUI Authentication: Set User and Password": "Uwierzytelnianie GUI: ustaw użytkownika i hasło",
"GUI Listen Address": "Adres nasłuchu GUI",
"GUI Theme": "Motyw GUI",
"General": "Ogólne",
"Generate": "Generuj",
"Global Discovery": "Globalne odnajdywanie",
"Global Discovery Servers": "Globalne serwery odkrywania",
"Global State": "Status globalny",
"Global Discovery": "Odnajdywanie globalne",
"Global Discovery Servers": "Serwery odnajdywania globalnego",
"Global State": "Stan globalny",
"Help": "Pomoc",
"Home page": "Strona domowa",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Twoje aktualne ustawienia wskazują iż być może nie chciał(a)byś wysyłać raportów. Wyłączyliśmy automatyczne raportowanie awarii.",
"If untrusted, enter encryption password": "If untrusted, enter encryption password",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Niemniej jednak, obecne ustawienia wskazują, że możesz nie chcieć włączać tej funkcji. Automatyczne zgłaszanie awarii zostało wyłączone na tym urządzeniu.",
"If untrusted, enter encryption password": "Jeżeli folder jest niezaufany, wprowadź szyfrujące hasło",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Jeżeli chcesz zakazać innym użytkownikom tego komputera dostępu do Syncthing, a przez niego do swoich plików, zastanów się nad włączeniem uwierzytelniania.",
"Ignore": "Ignoruj",
"Ignore Patterns": "Wzorce ignorowania",
"Ignore Permissions": "Ignoruj uprawnienia",
"Ignore Permissions": "Ignorowanie uprawnień",
"Ignored Devices": "Ignorowane urządzenia",
"Ignored Folders": "Ignorowane katalogi",
"Ignored Folders": "Ignorowane foldery",
"Ignored at": "Ignorowane od",
"Incoming Rate Limit (KiB/s)": "Ograniczenie prędkości odbierania (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Niepoprawna konfiguracja może uszkodzić zawartośc Twojego folderu i uczynić Syncthing niedziałającym.",
"Incoming Rate Limit (KiB/s)": "Ograniczenie prędkości pobierania (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Niepoprawne ustawienia mo uszkodzić zawartość folderów oraz uczynić Syncthing niesprawnym.",
"Introduced By": "Wprowadzony przez",
"Introducer": "Wprowadzający",
"Inversion of the given condition (i.e. do not exclude)": "Odwrócenie podanego wzorca (np. nie wykluczaj)",
"Inversion of the given condition (i.e. do not exclude)": "Odwrotność danego warunku (np. nie wykluczaj)",
"Keep Versions": "Zachowuj wersje",
"LDAP": "LDAP",
"Largest First": "Największe na początku",
"Last Scan": "Czas ostatniego skanu",
"Last Scan": "Ostatnie skanowanie",
"Last seen": "Ostatnio widziany",
"Latest Change": "Ostatnia zmiana",
"Learn more": "Zobacz więcej",
"Limit": "Limit",
"Learn more": "Dowiedz się więcej",
"Limit": "Ograniczenie",
"Listeners": "Nasłuchujący",
"Loading data...": "Ładowanie danych...",
"Loading...": "Ładowanie...",
"Local Additions": "Local Additions",
"Local Discovery": "Lokalne odnajdywanie",
"Local State": "Status lokalny",
"Local State (Total)": "Status lokalny (suma)",
"Local Additions": "Zmiany lokalne",
"Local Discovery": "Odnajdywanie lokalne",
"Local State": "Stan lokalny",
"Local State (Total)": "Stan lokalny (suma)",
"Locally Changed Items": "Elementy zmodyfikowane lokalnie",
"Log": "Log",
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
"Log tailing paused. Scroll to the bottom to continue.": "Zatrzymano wypisywanie logów. Przewiń w dół, aby je wznowić.",
"Logs": "Logi",
"Major Upgrade": "Ważna aktualizacja",
"Major Upgrade": "Duża aktualizacja",
"Mass actions": "Działania masowe",
"Maximum Age": "Maksymalny wiek",
"Metadata Only": "Tylko metadane",
"Minimum Free Disk Space": "Minimum wolnego miejsca na dysku",
"Mod. Device": "Urządzenie modyfikacji",
"Mod. Time": "Czas modyfikacji",
"Mod. Device": "Urządzenie mod.",
"Mod. Time": "Czas mod.",
"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)",
"Multi level wildcard (matches multiple directory levels)": "Wieloznacznik wielopoziomowy (wyszukuje na wielu poziomach katalogów)",
"Never": "Nigdy",
"New Device": "Nowe urządzenie",
"New Folder": "Nowy folder",
"Newest First": "Najnowsze na początku",
"No": "Nie",
"No File Versioning": "Bez wersjonowania pliku",
"No files will be deleted as a result of this operation.": "W wyniku tej operacji żadne pliki nie zostaną usunięte.",
"No File Versioning": "Bez wersjonowania plików",
"No files will be deleted as a result of this operation.": "Żadne pliki nie zostaną usunięte w wyniki tego działania.",
"No upgrades": "Brak aktualizacji",
"Not shared": "Not shared",
"Not shared": "Niewspółdzielony",
"Notice": "Wskazówka",
"OK": "OK",
"Off": "Wyłącz",
"Off": "Wyłączona",
"Oldest First": "Najstarsze na początku",
"Optional descriptive label for the folder. Can be different on each device.": "Opcjonalna opisowa etykieta dla folderu. Może być różna na każdym urządzeniu.",
"Options": "Opcje",
"Out of Sync": "Niezsynchronizowane",
"Out of Sync Items": "Niezsynchronizowane pliki",
"Out of Sync Items": "Elementy niezsynchronizowane",
"Outgoing Rate Limit (KiB/s)": "Ograniczenie prędkości wysyłania (KiB/s)",
"Override Changes": "Nadpisz zmiany",
"Path": "Ścieżka",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Ścieżka, w której zostaną utworzone nowe automatycznie akceptowane foldery, a także domyślna sugerowana ścieżka podczas dodawania nowych folderów za pośrednictwem interfejsu użytkownika. Znak tyldy (~) rozwija się do {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Ścieżka gdzie wersje będą przechowywane (pozostaw puste dla domyślnego folderu .stversions we współ. folderze).",
"Path 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 folderu na komputerze lokalnym. Zostanie utworzona, jeżeli jeszcze nie istnieje. Znak tyldy (~) może zostać użyty jako skrót do",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Ścieżka, w której zostaną utworzone nowe autoakceptowane foldery, a także domyślna sugerowana ścieżka podczas dodawania nowych folderów za pośrednictwem interfejsu użytkownika. Znak tyldy (~) rozwija się do {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Ścieżka przechowywania wersji (pozostaw pustą dla domyślnego katalogu .stversions we współdzielonym folderze).",
"Pause": "Zatrzymaj",
"Pause All": "Zatrzymaj wszystkie",
"Paused": "Zatrzymany",
"Paused (Unused)": "Wstrzymane (nieużywane)",
"Paused (Unused)": "Zatrzymany (nieużywany)",
"Pending changes": "Oczekujące zmiany",
"Periodic scanning at given interval and disabled watching for changes": "Okresowe skanowanie w podanym przedziale czasu i wyłączone obserwowanie zmian",
"Periodic scanning at given interval and enabled watching for changes": "Okresowe skanowanie w podanym przedziale czasu i włączone obserwowanie zmian",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Okresowe skanowanie w podanym przedziale czasu i nieudane konfigurowanie obserwowania zmian, ponowna próba co minutę:",
"Periodic scanning at given interval and disabled watching for changes": "Okresowe skanowanie w podanym przedziale czasowym i wyłączone obserwowanie zmian",
"Periodic scanning at given interval and enabled watching for changes": "Okresowe skanowanie w podanym przedziale czasowym i włączone obserwowanie zmian",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Okresowe skanowanie w podanym przedziale czasowym i nieudane ustawienie obserwowania zmian, ponawiam co minutę:",
"Permissions": "Uprawnienia",
"Please consult the release notes before performing a major upgrade.": "Zaleca się przeanalizowanie \"release notes\" przed przeprowadzeniem znaczącej aktualizacji.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ustaw proszę użytkownika i hasło dostępowe do GUI w Ustawieniach",
"Please consult the release notes before performing a major upgrade.": "Zapoznaj się z informacjami o wersji przed przeprowadzeniem dużej aktualizacji.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ustaw użytkownika i hasło do uwierzytelniania GUI w oknie Ustawień.",
"Please wait": "Proszę czekać",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefiks wskazujący, że plik może zostać usunięty w przypadku zapobiegania usunięciu katalogu",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefiks wskazujący, że wzorzec powinien być dopasowany bez rozróżniania wielkości liter",
"Preparing to Sync": "Przygotowanie do synchronizacji",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefiks wskazujący, że plik może zostać usunięty, gdy blokuje on usunięcie katalogu",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefiks wskazujący, że wzorzec ma być wyszukiwany bez rozróżniania wielkości liter",
"Preparing to Sync": "Przygotowywanie do synchronizacji",
"Preview": "Podgląd",
"Preview Usage Report": "Podgląd raportu użycia.",
"Preview Usage Report": "Podgląd statystyk użycia",
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"Random": "Losowo",
"Receive Encrypted": "Odbieraj zaszyfrowane",
"Receive Only": "Tylko odbieraj",
"Received data is already encrypted": "Received data is already encrypted",
"Receive Encrypted": "Odbierz zaszyfrowane",
"Receive Only": "Tylko odbierz",
"Received data is already encrypted": "Odebrane dane są już zaszyfrowane",
"Recent Changes": "Ostatnie zmiany",
"Reduced by ignore patterns": "Ograniczono przez wzorce ignorowania",
"Release Notes": "Informacje o wydaniu",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Wydania kandydujące zawierają najnowsze funkcje oraz poprawki błędów. Są one podobne do tradycyjnych co dwutygodniowych wydań Syncthing.",
"Release Notes": "Informacje o wersji",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Wydania kandydujące zawierają najnowsze funkcje oraz poprawki błędów. Są one podobne do tradycyjnych codwutygodniowych wydań Syncthing.",
"Remote Devices": "Urządzenia zdalne",
"Remote GUI": "Remote GUI",
"Remote GUI": "Zdalne GUI",
"Remove": "Usuń",
"Remove Device": "Usuń urządzenie",
"Remove Folder": "Usuń folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Wymagany identyfikator dla folderu. Musi być taki sam na wszystkich urządzeniach.",
"Rescan": "Skanuj ponownie",
"Rescan All": "Skanuj wszystko ponownie",
"Rescan All": "Skanuj wszystkie ponownie",
"Rescans": "Skanowania",
"Restart": "Uruchom ponownie",
"Restart Needed": "Wymagane ponowne uruchomienie",
@@ -262,109 +262,109 @@
"Save": "Zapisz",
"Scan Time Remaining": "Pozostały czas skanowania",
"Scanning": "Skanowanie",
"See external versioning help for supported templated command line parameters.": "Dostępne zmienne dla polecenia opisane są w dokumentacji w sekcji Zewnętrzne wersjonowanie plików.",
"Select All": "Zaznacz wszystko",
"See external versioning help for supported templated command line parameters.": "Zmienne dostępne dla polecenia opisane są w dokumentacji w sekcji Zewnętrzne wersjonowanie plików.",
"Select All": "Zaznacz wszystkie",
"Select a version": "Wybierz wersję",
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
"Select additional folders to share with this device.": "Select additional folders to share with this device.",
"Select additional devices to share this folder with.": "Zaznacz dodatkowe urządzenia, z którymi chcesz współdzielić ten folder.",
"Select additional folders to share with this device.": "Zaznacz dodatkowe foldery, które chcesz współdzielić z tym urządzeniem.",
"Select latest version": "Wybierz najnowszą wersję",
"Select oldest version": "Wybierz najstarszą wersję",
"Select the folders to share with this device.": "Wybierz foldery do współdzielenia z tym urządzeniem.",
"Select the folders to share with this device.": "Zaznacz foldery, które chcesz współdziel z tym urządzeniem.",
"Send & Receive": "Wyślij i odbierz",
"Send Only": "Tylko wysyłanie",
"Send Only": "Tylko wyślij",
"Settings": "Ustawienia",
"Share": "Udostępnij",
"Share Folder": "Udostępnij folder",
"Share Folders With Device": "Udostępnij foldery między urządzeniami",
"Share this folder?": "Udostępnić ten folder?",
"Shared Folders": "Udostępnione foldery",
"Share": "Współdziel",
"Share Folder": "Współdziel folder",
"Share Folders With Device": "Współdziel foldery z urządzeniem",
"Share this folder?": "Czy chcesz współdzielić ten folder?",
"Shared Folders": "Współdzielone foldery",
"Shared With": "Współdzielony z",
"Sharing": "Współdzielenie",
"Show ID": "Pokaż ID",
"Show QR": "Pokaż kod QR",
"Show diff with previous version": "Pokaż różnice z poprzednią wersją",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Pokazane w statusie zamiast ID urządzenia.Zostanie wysłane do innych urządzeń jako opcjonalna domyślna nazwa.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Pokazane w statusie zamiast ID urządzenia. Zostanie zaktualizowane do nazwy urządzenia jeżeli pozostanie puste.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Widoczna na liście urządzeń zamiast ID urządzenia. Zostanie wysłana do innych urządzeń jako opcjonalna nazwa domyślna.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Widoczna na liście urządzeń zamiast ID urządzenia. Zostanie zaktualizowana do nazwy wysłanej przez urządzenie, jeżeli pozostanie pusta.",
"Shutdown": "Wyłącz",
"Shutdown Complete": "Wyłączanie ukończone",
"Simple File Versioning": "Proste wersjonowanie pliku",
"Single level wildcard (matches within a directory only)": "Wieloznaczność na poziomie plików (uwzględnia nazwy plików)",
"Simple File Versioning": "Proste wersjonowanie plików",
"Single level wildcard (matches within a directory only)": "Wieloznacznik jednopoziomowy (wyszukuje tylko wewnątrz katalogu)",
"Size": "Rozmiar",
"Smallest First": "Najmniejsze na początku",
"Some items could not be restored:": "Niektórych elementów nie można przywrócić:",
"Some items could not be restored:": "Niektórych elementów nie udało się przywrócić:",
"Source Code": "Kod źródłowy",
"Stable releases and release candidates": "Wydania stabilne i wydania kandydujące",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Wydania stabilne są opóźnione ok. dwa tygodnie. W tym czasie są testowane jako wydania kandydujące.",
"Stable releases only": "Tylko stabilne wydania",
"Staggered File Versioning": "Rozbudowane wersjonowanie pliku",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Wydania stabilne są opóźnione o ok. dwa tygodnie. W tym czasie są one testowane jako wydania kandydujące.",
"Stable releases only": "Tylko wydania stabilne",
"Staggered File Versioning": "Rozbudowane wersjonowanie plików",
"Start Browser": "Uruchom przeglądarkę",
"Statistics": "Statystyki",
"Stopped": "Zatrzymany",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{{receiveEncrypted}}\" too.",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Przechowuje i synchronizuje tylko zaszyfrowane dane. Foldery na wszystkich połączonych urządzeniach muszą używać tego samego hasła bądź też być rodzaju \"{{receiveEncrypted}}\".",
"Support": "Wsparcie",
"Support Bundle": "Wsparcie",
"Support Bundle": "Pakiet wsparcia",
"Sync Protocol Listen Addresses": "Adres nasłuchu protokołu synchronizacji",
"Syncing": "Synchronizowanie",
"Syncthing has been shut down.": "Syncthing został wyłączony",
"Syncthing includes the following software or portions thereof:": "Syncthing zawiera następujące oprogramowanie lub ich częśći:",
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing to wolne oprogramowanie na licencji MPL 2.0",
"Syncthing is restarting.": "Restart Syncthing",
"Syncthing is upgrading.": "Aktualizowanie Syncthing",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing zawiera teraz automatyczne wysyłanie raportów o awariach do autorów. Ta funkcja jest domyślnie włączona.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing wydaje się być wyłączony lub jest problem z twoim połączeniem internetowym. Próbuje ponownie...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing nie może przetworzyć twojego zapytania. Proszę przeładuj stronę lub zrestartuj Syncthing, jeśli problem pozostanie.",
"Take me back": "Zabierz mnie z powrotem",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Adres GUI jest nadpisywany przez opcje uruchamiania. Zmiany tutaj nie będą obowiązywać, dopóki ta opcja uruchamiania jest używana.",
"The Syncthing Authors": "Autorzy Syncthing",
"The Syncthing admin interface is configured to allow remote access without a password.": "Interfejs administracyjny Syncthing jest skonfigurowany w sposób pozwalający na zdalny dostęp bez hasła.",
"The aggregated statistics are publicly available at the URL below.": "Zebrane statystyki są publicznie dostępne pod poniższym linkiem.",
"The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfiguracja została zapisana lecz nie jest aktywna. Syncthing musi zostać zrestartowany aby aktywować nową konfiguracje.",
"Syncthing has been shut down.": "Syncthing został wyłączony.",
"Syncthing includes the following software or portions thereof:": "Syncthing zawiera następujące oprogramowanie lub ich części:",
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing to wolne i otwarte oprogramowanie na licencji MPL 2.0.",
"Syncthing is restarting.": "Syncthing jest uruchamiany ponownie.",
"Syncthing is upgrading.": "Syncthing jest aktualizowany.",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing zawiera teraz automatyczne zgłaszanie awarii do autorów. Ta funkcja jest domyślnie włączona.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing wydaje się być wyłączony lub wystąpił problem z połączeniem internetowym. Próbuję ponownie",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing wydaje się mieć trudności z przetworzeniem tego zapytania. Odśwież stronę lub uruchom Syncthing ponownie, jeżeli problem nie ustąpi.",
"Take me back": "Powrót",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Adres GUI jest nadpisywany przez opcje uruchamiania. Zmiany dokonane tutaj nie będą obowiązywać, dopóki nadpisywanie jest w użyciu.",
"The Syncthing Authors": "The Syncthing Authors",
"The Syncthing admin interface is configured to allow remote access without a password.": "Ustawienia interfejsu administracyjnego Syncthing zezwalają na zdalny dostęp bez hasła.",
"The aggregated statistics are publicly available at the URL below.": "Zebrane statystyki są publicznie dostępne pod poniższym adresem.",
"The cleanup interval cannot be blank.": "Przedział czasowy czyszczenia nie może być pusty.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Ustawienia zostały zapisane, ale nie są jeszcze aktywne. Syncthing musi zostać uruchomiony ponownie, aby aktywować nowe ustawienia.",
"The device ID cannot be blank.": "ID urządzenia nie może być puste.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID urządzenia może być znalezione w \"Akcja > Pokaż ID\" na innym urządzeniu. Spacje i myślniki są opcjonalne (są one ignorowane).",
"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.": "Zaszyfrowane raporty użycia są wysyłane codziennie. Są one używane w celach statystycznych platform, rozmiarów katalogów i wersji programu. Jeżeli zgłaszane dane ulegną zmianie, ponownie wyświetli się ta informacja.",
"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.": "Wprowadzone ID urządzenia wygląda na niepoprawne. Musi zawierać 52 lub 56 znaków składających się z liter i cyfr. Odstępy i myślniki są opcjonalne.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID urządzenia do wpisania tutaj można znaleźć w oknie \"Akcje > Pokaż ID\" na innym urządzeniu. Spacje i myślniki są opcjonalne (ignorowane).",
"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.": "Zaszyfrowane statystyki użycia są wysyłane codziennie. Używane są one do śledzenia popularności systemów, rozmiarów folderów oraz wersji programu. Jeżeli wysyłane statystyki ulegną zmianie, zostaniesz poproszony o ponowne udzielenie zgody w tym oknie.",
"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.": "Wprowadzone ID urządzenia wygląda na niepoprawne. Musi ono zawierać 52 lub 56 znaków składających się z liter i cyfr. Spacje i myślniki są opcjonalne.",
"The folder ID cannot be blank.": "ID folderu nie może być puste.",
"The folder ID must be unique.": "ID folderu musi być unikalne.",
"The folder ID must be unique.": "ID folderu musi być unikatowe.",
"The folder path cannot be blank.": "Ścieżka folderu nie może być pusta.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Następujący interwał jest używany: dla pierwszej godziny wersja jest zachowywana co 30 sekund, dla pierwszego dnia wersja jest zachowywana co godzinę, dla pierwszego miesiąca wersja jest zachowywana codziennie, aż do maksymalnego odstępu zapisywania wersji co tydzień.",
"The 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.": "Używane są następujące przedziały czasowe: w pierwszej godzinie wersja zachowywana jest co 30 sekund, w pierwszym dniu co godzinę, w pierwszych 30 dniach codziennie, a do czasu osiągnięcia maksymalnego wieku co tydzień.",
"The following items could not be synchronized.": "Następujące elementy nie mogły zostać zsynchronizowane.",
"The following items were changed locally.": "Następujące elementy zostały zmienione lokalnie.",
"The following unexpected items were found.": "The following unexpected items were found.",
"The interval must be a positive number of seconds.": "The interval must be a positive number of seconds.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.",
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być liczbą i nie może być pusty.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
"The number of days must be a number and cannot be blank.": "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 following unexpected items were found.": "Znaleziono następujące elementy nieoczekiwane.",
"The interval must be a positive number of seconds.": "Przedział czasowy musi być dodatnią liczbą sekund.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Przedział czasowy, w sekundach, w którym nastąpi czyszczenie katalogu wersjonowania. Ustaw na zero, aby wyłączyć czyszczenie okresowe.",
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być liczbą oraz 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 na 0, aby zachować na zawsze).",
"The number of days must be a number and cannot be blank.": "Liczba dni musi być wartością liczbową oraz nie może być pusta.",
"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 nieskończoność.",
"The number of old versions to keep, per file.": "Liczba starszych wersji do zachowania, dla pojedynczego pliku.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być wartością liczbową oraz 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)": "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.",
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
"There are no folders to share with this device.": "There are no folders to share with this device.",
"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.",
"The rate limit must be a non-negative number (0: no limit)": "Ograniczenie prędkości musi być nieujemną liczbą (0: brak ograniczeń)",
"The rescan interval must be a non-negative number of seconds.": "Przedział czasowy ponownego skanowania musi być nieujemną liczbą sekund.",
"There are no devices to share this folder with.": "Brak urządzeń, z którymi możesz współdzielić ten folder.",
"There are no folders to share with this device.": "Brak folderów, które możesz współdzielić z tym urządzeniem.",
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie. Synchronizacja nastąpi po usunięciu błędu.",
"This Device": "To urządzenie",
"This can easily give hackers access to read and change any files on your computer.": "Może to umożliwić osobom trzecim dostęp do odczytu i zmian dowolnych plików na urządzeniu.",
"This is a major version upgrade.": "To jest ważna aktualizacja",
"This setting controls the free space required on the home (i.e., index database) disk.": "Te ustawienia kontrolują ilość potrzebnej wolnej przestrzeni na dysku domowym (np. indeksowanie bazy danych).",
"This can easily give hackers access to read and change any files on your computer.": "Może to umożliwić hakerom dostęp do odczytu i zmian dowolnych plików na tym komputerze.",
"This is a major version upgrade.": "To jest duża aktualizacja.",
"This setting controls the free space required on the home (i.e., index database) disk.": "To ustawienie kontroluje ilość wolnej przestrzeni na dysku domowym (np. do indeksowania bazy danych).",
"Time": "Czas",
"Time the item was last modified": "Czas ostatniej modyfikacji elementu",
"Trash Can File Versioning": "Kontrola werjsi plików w koszu",
"Type": "Typ",
"UNIX Permissions": "UNIX Permissions",
"Trash Can File Versioning": "Wersjonowanie plików w koszu",
"Type": "Rodzaj",
"UNIX Permissions": "UNIX-owe uprawnienia",
"Unavailable": "Niedostępne",
"Unavailable/Disabled by administrator or maintainer": "Niedostępne/Wyłączone przez administratora lub opiekuna",
"Unavailable/Disabled by administrator or maintainer": "Niedostępne/wyłączone przez administratora lub serwisanta",
"Undecided (will prompt)": "Jeszcze nie zdecydowałem (przypomnij później)",
"Unexpected Items": "Unexpected Items",
"Unexpected items have been found in this folder.": "Unexpected items have been found in this folder.",
"Unexpected Items": "Elementy nieoczekiwane",
"Unexpected items have been found in this folder.": "Znaleziono elementy nieoczekiwane w tym folderze.",
"Unignore": "Wyłącz ignorowanie",
"Unknown": "Nieznany",
"Unshared": "Nieudostępnione",
"Unshared Devices": "Unshared Devices",
"Unshared Folders": "Nieudostępnione foldery",
"Untrusted": "Niezaufane",
"Unshared": "Niewspółdzielony",
"Unshared Devices": "Niewspółdzielone urządzenia",
"Unshared Folders": "Niewspółdzielone foldery",
"Untrusted": "Niezaufany",
"Up to Date": "Aktualny",
"Updated": "Zaktualizowano",
"Upgrade": "Aktualizacja",
@@ -372,42 +372,42 @@
"Upgrading": "Aktualizowanie",
"Upload Rate": "Prędkość wysyłania",
"Uptime": "Czas działania",
"Usage reporting is always enabled for candidate releases.": "Raportowanie użycia dla wydań kandydujących jest zawsze włączone.",
"Use HTTPS for GUI": "Używaj HTTPS",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.",
"Usage reporting is always enabled for candidate releases.": "Statystyki użycia są zawsze włączone dla wydań kandydujących.",
"Use HTTPS for GUI": "Użyj HTTPS dla GUI",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Użytkownik i hasło do uwierzytelniania GUI nie zostały skonfigurowane. Zastanów się nad ich ustawieniem.",
"Version": "Wersja",
"Versions": "Wersje",
"Versions Path": "Ścieżka wersji",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Wersje zostają automatycznie usunięte jeżeli są starsze niż maksymalny wiek lub przekraczają liczbę dopuszczalnych wersji.",
"Waiting to Clean": "Waiting to Clean",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Wersje są automatycznie usuwane po przekroczeniu maksymalnego wieku lub liczby plików dozwolonych w danym przedziale czasowym.",
"Waiting to Clean": "Oczekiwanie na czyszczenie",
"Waiting to Scan": "Oczekiwanie na skanowanie",
"Waiting to Sync": "Oczekiwanie na synchronizację",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka jest nadrzędnym folderem istniejącego folderu \"{{otherFolder}}\".",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ten folder jest nadfolderem istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka to nadkatalog istniejącego folderu \"{{otherFolder}}\".",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ta ścieżka to nadkatalog istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka to podkatalog istniejącego folderu \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ten folder jest podfolderem istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeśli korzystasz z zewnętrznego obserwatora takiego jak {{syncthingInotify}}, upewnij się, że jest on dezaktywowany.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ten folder to podkatalog istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeżeli korzystasz z zewnętrznego obserwatora, takiego jak {{syncthingInotify}}, upewnij się, że ta opcja jest wyłączona.",
"Watch for Changes": "Obserwuj zmiany",
"Watching for Changes": "Obserwowanie zmian",
"Watching for changes discovers most changes without periodic scanning.": "Obserwowanie wykrywa większość zmian bez potrzeby okresowego skanowania.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Gdy dodajesz nowe urządzenie, pamiętaj że urządzenie musi zostać dodane także po drugiej stronie.",
"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.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Gdy dodajesz nowe urządzenie pamiętaj, że to urządzenie musi zostać dodane także po drugiej stronie.",
"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.": "Gdy dodajesz nowy folder pamiętaj, że ID folderu używane jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ma znaczenie i musi ono być identyczne na wszystkich urządzeniach.",
"Yes": "Tak",
"You can also select one of these nearby devices:": "Możesz również wybrać jedno z pobliskich urządzeń:",
"You can change your choice at any time in the Settings dialog.": "Możesz zmienić swój wybór w dowolnej chwili w Ustawieniach",
"You can read more about the two release channels at the link below.": "Możesz więcej poczytać na temat obydwu wydań klikając poniższy link.",
"You have no ignored devices.": "Nie posiadasz zignorowanych urządzeń.",
"You have no ignored folders.": "Nie posiadasz zignorowanych katalogów.",
"You have unsaved changes. Do you really want to discard them?": "Masz niezapisane zmiany. Czy naprawdę chcesz je odrzucić?",
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"You can change your choice at any time in the Settings dialog.": "Możesz zmienić swój wybór w dowolnej chwili w oknie Ustawień.",
"You can read more about the two release channels at the link below.": "Możesz przeczytać więcej na temat obu kanałów wydawniczych pod poniższym odnośnikiem.",
"You have no ignored devices.": "Brak ignorowanych urządzeń.",
"You have no ignored folders.": "Brak ignorowanych folderów.",
"You have unsaved changes. Do you really want to discard them?": "Masz niezapisane zmiany. Czy na pewno chcesz je odrzucić?",
"You must keep at least one version.": "Musisz zachować co najmniej jedną wersję.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Nigdy nie powinieneś dodawać lub zmieniać czegokolwiek lokalnie w folderze \"{{receiveEncrypted}}\".",
"days": "dni",
"directories": "directories",
"directories": "katalogi",
"files": "pliki",
"full documentation": "pełna dokumentacja",
"items": "pozycji",
"seconds": "seconds",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\"",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce udostępnić folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
"items": "elementy",
"seconds": "sekundy",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce współdzielić folder \"{{folder}}\"",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} chce współdzielić folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} może ponownie wprowadzić to urządzenie."
}

View File

@@ -100,9 +100,9 @@
"Downloaded": "Recebido",
"Downloading": "Recebendo",
"Edit": "Editar",
"Edit Device": "Edit Device",
"Edit Device": "Editar dispositivo",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Editar pasta",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Editando {{path}}.",
"Enable Crash Reporting": "Habilitar relatório de falhas",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "dias",
"directories": "directories",
"directories": "direrios",
"files": "arquivos",
"full documentation": "documentação completa",
"items": "itens",

View File

@@ -100,9 +100,9 @@
"Downloaded": "Загружено",
"Downloading": "Загрузка",
"Edit": "Редактировать",
"Edit Device": "Edit Device",
"Edit Device": "Редактирование устройства",
"Edit Device Defaults": "Edit Device Defaults",
"Edit Folder": "Edit Folder",
"Edit Folder": "Редактирование папки",
"Edit Folder Defaults": "Edit Folder Defaults",
"Editing {%path%}.": "Правка {{path}}.",
"Enable Crash Reporting": "Включить отчёты о сбоях",
@@ -402,7 +402,7 @@
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "дней",
"directories": "directories",
"directories": "папок",
"files": "файлов",
"full documentation": "полная документация",
"items": "элементы",

View File

@@ -22,7 +22,7 @@
"Allow Anonymous Usage Reporting?": "允許回報匿名數據?",
"Allowed Networks": "允許的網路",
"Alphabetic": "字母順序",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "外部指令接管了版本控制。它必須將檔案自分享資料夾中移除。如果應用程式的路徑包含了空格,則必須使用雙引號刮起。",
"Anonymous Usage Reporting": "匿名數據回報",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名數據回報格式已經變更,想要移至新格式嗎?",
"Are you sure you want to permanently delete all these files?": "確認永久刪除檔案?",
@@ -111,7 +111,7 @@
"Enabled": "啟用",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "請輸入一非負數(如:\"2.35\")並選擇一個單位。百分比表示佔用磁碟容量的大小。",
"Enter a non-privileged port number (1024 - 65535).": "輸入一個非特權通訊埠號 (1024 - 65535)。",
"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.": "輸入忽略樣式,每行一種。",
"Enter up to three octal digits.": "輸入最多三位八進位數字。",
"Error": "錯誤",
@@ -135,7 +135,7 @@
"Folder Type": "資料夾類型",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "資料夾類型 \"{{receiveEncrypted}}\" 無法在新增後變更。您需要移除資料夾、刪除或解密磁碟上的資料,並再次新增資料夾。",
"Folders": "資料夾",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "啟動監視下列資料夾時發生錯誤。由於每分鐘將進行重試,錯誤可能很快就消失。若錯誤仍存在,請嘗試修復潛在問題,或請求協助。",
"Full Rescan Interval (s)": "完全重新掃描間隔 (秒)",
"GUI": "GUI",
"GUI Authentication Password": "GUI 驗證密碼",
@@ -175,7 +175,7 @@
"Listeners": "監聽者",
"Loading data...": "正在載入資料...",
"Loading...": "正在載入...",
"Local Additions": "Local Additions",
"Local Additions": "本機添加",
"Local Discovery": "本機探索",
"Local State": "本機狀態",
"Local State (Total)": "本機狀態 (總結)",
@@ -300,7 +300,7 @@
"Start Browser": "啟動瀏覽器",
"Statistics": "統計",
"Stopped": "已停止",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{{receiveEncrypted}}\" too.",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "僅儲存並同步已加密的資料。所有位於已連接裝置上的資料夾,必須設定相同的密碼,或屬於 \"{{receiveEncrypted}}\" 類型。",
"Support": "支援",
"Support Bundle": "支援包",
"Sync Protocol Listen Addresses": "同步通訊協定監聽位址",
@@ -313,8 +313,8 @@
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing 已支援將當機報告回傳至開發者。此功能預設為啟用。",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing 似乎離線了,或者您的網際網路連線出現問題。正在重試...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing 在處理您的請求時似乎遇到了問題。請重新整理本頁面,若問題持續發生,請重新啟動 Syncthing。",
"Take me back": "Take me back",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.",
"Take me back": "帶我回去",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "GUI 位址已被自啟動選項覆寫。當覆寫啟用時,此處變更將不會生效。",
"The Syncthing Authors": "Syncthing 作者們",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing 管理介面被設定允許無密碼的遠端存取。",
"The aggregated statistics are publicly available at the URL below.": "匯總統計資訊可於下方網址取得。",

View File

@@ -250,7 +250,7 @@
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, offeringDevice.label, deviceID)" ng-if="!folders[folderID]">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, pendingFolder, deviceID)" ng-if="!folders[folderID]">
<span class="fas fa-check"></span>&nbsp;<span translate>Add</span>
</button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(folderID, deviceID)" ng-if="folders[folderID]">

View File

@@ -19,7 +19,7 @@
<h4 class="text-center" translate>The Syncthing Authors</h4>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Tomasz Wilczyński, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eric Lesiuta, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, mv1005, otbutz, perewa, rubenbe, wangguoliang, xarx00, xjtdy888, 佛跳墙
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Tomasz Wilczyński, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eric Lesiuta, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, bt90, chenrui, chucic, dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, mv1005, otbutz, perewa, rubenbe, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙
</div>
</div>
<hr />

View File

@@ -51,6 +51,7 @@ angular.module('syncthing.core')
$scope.globalChangeEvents = {};
$scope.metricRates = false;
$scope.folderPathErrors = {};
$scope.currentSharing = {};
$scope.currentFolder = {};
$scope.currentDevice = {};
$scope.ignores = {
@@ -275,7 +276,8 @@ angular.module('syncthing.core')
arg.data.added.forEach(function (rejected) {
var offeringDevice = {
time: arg.time,
label: rejected.folderLabel
label: rejected.folderLabel,
receiveEncrypted: rejected.receiveEncrypted,
};
console.log("rejected folder", rejected.folderID, "from device:", rejected.deviceID, offeringDevice);
@@ -598,7 +600,13 @@ angular.module('syncthing.core')
}
$scope.completion[device][folder] = data;
recalcCompletion(device);
}).error($scope.emitHTTPError);
}).error(function(data, status, headers, config) {
if (status === 404) {
console.log("refreshCompletion:", data);
} else {
$scope.emitHTTPError(data, status, headers, config);
}
});
}
function refreshConnectionStats() {
@@ -1813,12 +1821,18 @@ angular.module('syncthing.core')
$scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
});
$scope.fsWatcherToggled = function () {
if ($scope.currentFolder.fsWatcherEnabled) {
$scope.currentFolder.rescanIntervalS = 3600;
} else {
$scope.currentFolder.rescanIntervalS = 60;
$scope.setFSWatcherIntervalDefault = function () {
var defaultRescanIntervals = [60, 3600];
if (defaultRescanIntervals.indexOf($scope.currentFolder.rescanIntervalS) === -1) {
return;
}
var idx;
if ($scope.currentFolder.fsWatcherEnabled) {
idx = 1;
} else {
idx = 0;
}
$scope.currentFolder.rescanIntervalS = defaultRescanIntervals[idx];
};
$scope.loadFormIntoScope = function (form) {
@@ -1925,6 +1939,7 @@ angular.module('syncthing.core')
$scope.editFolderExisting = function(folderCfg) {
$scope.editingExisting = true;
$scope.editingDefaults = false;
$scope.currentFolder = angular.copy(folderCfg);
$scope.ignores.text = 'Loading...';
@@ -1981,13 +1996,13 @@ angular.module('syncthing.core')
});
};
$scope.addFolderAndShare = function (folderID, folderLabel, device) {
$scope.addFolderAndShare = function (folderID, pendingFolder, device) {
addFolderInit(folderID).then(function() {
$scope.currentFolder.viewFlags = {
importFromOtherDevice: true
};
$scope.currentSharing.selected[device] = true;
$scope.currentFolder.label = folderLabel;
$scope.currentFolder.label = pendingFolder.offeredBy[device].label;
editFolderModal();
});
};
@@ -2038,6 +2053,9 @@ angular.module('syncthing.core')
folderCfg.devices = newDevices;
delete $scope.currentSharing;
if (!folderCfg.versioning) {
folderCfg.versioning = {params: {}};
}
folderCfg.versioning.type = folderCfg._guiVersioning.selector;
if ($scope.internalVersioningEnabled()) {
folderCfg.versioning.cleanupIntervalS = folderCfg._guiVersioning.cleanupIntervalS;

View File

@@ -98,7 +98,7 @@
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
@@ -106,30 +106,30 @@
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">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.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()">
@@ -140,22 +140,22 @@
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.</p>
<label translate for="externalCommand">Command</label>
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder._guiVersioning.externalCommand" required="" aria-required="true" />
<textarea name="externalCommand" id="externalCommand" class="form-control" rows="1" ng-model="currentFolder._guiVersioning.externalCommand" required="" aria-required="true" />
<p class="help-block">
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">See external versioning help for supported templated command line parameters.</span>
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor.cleanupIntervalS.$invalid && folderEditor.cleanupIntervalS.$dirty}">
<label translate for="cleanupIntervalS">Cleanup Interval</label>
<div class="input-group">
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<input name="cleanupIntervalS" id="cleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<div class="input-group-addon" translate>seconds</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$valid || folderEditor.cleanupIntervalS.$pristine" class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.required && folderEditor.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.min && folderEditor.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
</p>
</div>
</div>
@@ -198,7 +198,7 @@
<div class="row">
<div class="col-md-6">
<label>
<input type="checkbox" ng-model="currentFolder.fsWatcherEnabled" ng-change="fsWatcherToggled()" tooltip data-original-title="{{'Use notifications from the filesystem to detect changed items.' | translate }}">&nbsp;<span translate>Watch for Changes</span>
<input type="checkbox" ng-model="currentFolder.fsWatcherEnabled" ng-change="setFSWatcherIntervalDefault()" tooltip data-original-title="{{'Use notifications from the filesystem to detect changed items.' | translate }}">&nbsp;<span translate>Watch for Changes</span>
</label>
<p translate class="help-block">Watching for changes discovers most changes without periodic scanning.</p>
</div>

View File

@@ -250,7 +250,7 @@
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, offeringDevice.label, deviceID)" ng-if="!folders[folderID]">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, pendingFolder, deviceID)" ng-if="!folders[folderID]">
<span class="fas fa-check"></span>&nbsp;<span translate>Add</span>
</button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(folderID, deviceID)" ng-if="folders[folderID]">
@@ -417,7 +417,7 @@
<span class="far fa-copy"></span>&nbsp;{{model[folder.id].localFiles | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-folder"></span>&nbsp;{{model[folder.id].localDirectories | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-hdd"></span>&nbsp;~{{model[folder.id].localBytes | binary}}B<!-- get rid of the annoying trailing whitespace
--><span ng-if="model[folder.id].ignorePatterns"><br/><i><small translate class="text-muted">Reduced by ignore patterns</small></i></span>
--><span ng-if="model[folder.id].ignorePatterns"><br/><i><small translate class="text-muted">Reduced by ignore patterns</small></i></span>
</span>
</td>
</tr>

View File

@@ -51,6 +51,7 @@ angular.module('syncthing.core')
$scope.globalChangeEvents = {};
$scope.metricRates = false;
$scope.folderPathErrors = {};
$scope.currentSharing = {};
$scope.currentFolder = {};
$scope.currentDevice = {};
$scope.ignores = {
@@ -275,7 +276,8 @@ angular.module('syncthing.core')
arg.data.added.forEach(function (rejected) {
var offeringDevice = {
time: arg.time,
label: rejected.folderLabel
label: rejected.folderLabel,
receiveEncrypted: rejected.receiveEncrypted,
};
console.log("rejected folder", rejected.folderID, "from device:", rejected.deviceID, offeringDevice);
@@ -598,7 +600,13 @@ angular.module('syncthing.core')
}
$scope.completion[device][folder] = data;
recalcCompletion(device);
}).error($scope.emitHTTPError);
}).error(function(data, status, headers, config) {
if (status === 404) {
console.log("refreshCompletion:", data);
} else {
$scope.emitHTTPError(data, status, headers, config);
}
})
}
function refreshConnectionStats() {
@@ -1830,12 +1838,31 @@ angular.module('syncthing.core')
$scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
});
$scope.fsWatcherToggled = function () {
if ($scope.currentFolder.fsWatcherEnabled) {
$scope.currentFolder.rescanIntervalS = 3600;
} else {
$scope.currentFolder.rescanIntervalS = 60;
$scope.setFSWatcherIntervalDefault = function () {
var defaultRescanIntervals = [60, 3600, 3600*24];
if (defaultRescanIntervals.indexOf($scope.currentFolder.rescanIntervalS) === -1) {
return;
}
var idx;
if ($scope.currentFolder.fsWatcherEnabled) {
idx = 1;
} else if ($scope.currentFolder.type === 'receiveencrypted') {
idx = 2;
} else {
idx = 0;
}
$scope.currentFolder.rescanIntervalS = defaultRescanIntervals[idx];
};
$scope.setDefaultsForFolderType = function () {
if ($scope.currentFolder.type === 'receiveencrypted') {
$scope.currentFolder.fsWatcherEnabled = false;
$scope.currentFolder.ignorePerms = true;
delete $scope.currentFolder.versioning;
} else {
$scope.currentFolder.fsWatcherEnabled = true;
}
$scope.setFSWatcherIntervalDefault();
};
$scope.loadFormIntoScope = function (form) {
@@ -1856,6 +1883,7 @@ angular.module('syncthing.core')
function editFolderModal() {
initVersioningEditing();
$scope.currentFolder._recvEnc = $scope.currentFolder.type === 'receiveencrypted';
$scope.folderPathErrors = {};
$scope.folderEditor.$setPristine();
$('#editFolder').modal().one('shown.bs.tab', function (e) {
@@ -1942,6 +1970,7 @@ angular.module('syncthing.core')
$scope.editFolderExisting = function(folderCfg) {
$scope.editingExisting = true;
$scope.editingDefaults = false;
$scope.currentFolder = angular.copy(folderCfg);
$scope.ignores.text = 'Loading...';
@@ -1998,13 +2027,20 @@ angular.module('syncthing.core')
});
};
$scope.addFolderAndShare = function (folderID, folderLabel, device) {
$scope.addFolderAndShare = function (folderID, pendingFolder, device) {
addFolderInit(folderID).then(function() {
$scope.currentFolder.viewFlags = {
importFromOtherDevice: true
};
$scope.currentSharing.selected[device] = true;
$scope.currentFolder.label = folderLabel;
$scope.currentFolder.label = pendingFolder.offeredBy[device].label;
for (var k in pendingFolder.offeredBy) {
if (pendingFolder.offeredBy[k].receiveEncrypted) {
$scope.currentFolder.type = "receiveencrypted";
$scope.setDefaultsForFolderType();
break;
}
}
editFolderModal();
});
};
@@ -2057,6 +2093,9 @@ angular.module('syncthing.core')
folderCfg.devices = newDevices;
delete $scope.currentSharing;
if (!folderCfg.versioning) {
folderCfg.versioning = {params: {}};
}
folderCfg.versioning.type = folderCfg._guiVersioning.selector;
if ($scope.internalVersioningEnabled()) {
folderCfg.versioning.cleanupIntervalS = folderCfg._guiVersioning.cleanupIntervalS;

View File

@@ -5,7 +5,7 @@
<li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
<li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
<li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
<li ng-if="!editingDefaults"><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
<li ng-if="!editingDefaults" ng-class="{'disabled': currentFolder._recvEnc}"><a ng-attr-data-toggle="{{ currentFolder._recvEnc ? undefined : 'tab'}}" href="{{currentFolder._recvEnc ? '#' : '#folder-ignores'}}"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
<li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
</ul>
<div class="tab-content">
@@ -86,7 +86,7 @@
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
@@ -94,30 +94,30 @@
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">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.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()">
@@ -128,22 +128,22 @@
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.</p>
<label translate for="externalCommand">Command</label>
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder._guiVersioning.externalCommand" required="" aria-required="true" />
<textarea name="externalCommand" id="externalCommand" class="form-control" rows="1" ng-model="currentFolder._guiVersioning.externalCommand" required="" aria-required="true" />
<p class="help-block">
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">See external versioning help for supported templated command line parameters.</span>
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor.cleanupIntervalS.$invalid && folderEditor.cleanupIntervalS.$dirty}">
<label translate for="cleanupIntervalS">Cleanup Interval</label>
<div class="input-group">
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<input name="cleanupIntervalS" id="cleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<div class="input-group-addon" translate>seconds</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$valid || folderEditor.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.required && folderEditor.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.min && folderEditor.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
</p>
</div>
</div>
@@ -186,7 +186,7 @@
<div class="row">
<div class="col-md-6">
<label>
<input type="checkbox" ng-model="currentFolder.fsWatcherEnabled" ng-change="fsWatcherToggled()" tooltip data-original-title="{{'Use notifications from the filesystem to detect changed items.' | translate }}">&nbsp;<span translate>Watch for Changes</span>
<input type="checkbox" ng-model="currentFolder.fsWatcherEnabled" ng-change="setFSWatcherIntervalDefault()" tooltip data-original-title="{{'Use notifications from the filesystem to detect changed items.' | translate }}">&nbsp;<span translate>Watch for Changes</span>
</label>
<p translate class="help-block">Watching for changes discovers most changes without periodic scanning.</p>
</div>
@@ -205,7 +205,7 @@
<div class="col-md-6 form-group">
<label translate>Folder Type</label>
&nbsp;<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.type" ng-disabled="editingExisting && currentFolder.type === 'receiveencrypted'">
<select class="form-control" ng-change="setDefaultsForFolderType()" ng-model="currentFolder.type" ng-disabled="editingExisting && currentFolder.type == 'receiveencrypted'">
<option value="sendreceive" translate>Send &amp; Receive</option>
<option value="sendonly" translate>Send Only</option>
<option value="receiveonly" translate>Receive Only</option>
@@ -214,7 +214,8 @@
<p ng-if="currentFolder.type == 'sendonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
<p ng-if="currentFolder.type == 'receiveonly'" translate class="help-block">Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.</p>
<p ng-if="currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type "{%receiveEncrypted%}" too.</p>
<p ng-if="editingExisting" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
<p ng-if="editingExisting && currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
<p ng-if="editingExisting && currentFolder.type != 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" can only be set when adding a new folder.</p>
</div>
<div class="col-md-6 form-group">
<label translate>File Pull Order</label>
@@ -255,7 +256,7 @@
</div>
<div class="col-md-6 form-group">
<label>
<input type="checkbox" ng-model="currentFolder.ignorePerms" /> <span translate>Ignore Permissions</span>
<input type="checkbox" ng-disabled="currentFolder._recvEnc" ng-model="currentFolder.ignorePerms" /> <span translate>Ignore Permissions</span>
</label>
<p translate class="help-block">
Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).

View File

@@ -23,18 +23,21 @@ import (
"os"
"path/filepath"
"reflect"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"unicode"
"github.com/julienschmidt/httprouter"
metrics "github.com/rcrowley/go-metrics"
"github.com/thejerf/suture/v4"
"github.com/vitrun/qart/qr"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
@@ -56,9 +59,6 @@ import (
"github.com/syncthing/syncthing/lib/ur"
)
// matches a bcrypt hash and not too much else
var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
const (
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
@@ -153,6 +153,10 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
if err != nil {
name = s.tlsDefaultCommonName
}
name, err = sanitizedHostname(name)
if err != nil {
name = s.tlsDefaultCommonName
}
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
}
@@ -297,7 +301,8 @@ func (s *service) Serve(ctx context.Context) error {
}
configBuilder.registerConfig("/rest/config")
configBuilder.registerConfigInsync("/rest/config/insync")
configBuilder.registerConfigInsync("/rest/config/insync") // deprecated
configBuilder.registerConfigRequiresRestart("/rest/config/restart-required")
configBuilder.registerFolders("/rest/config/folders")
configBuilder.registerDevices("/rest/config/devices")
configBuilder.registerFolder("/rest/config/folders/:id")
@@ -745,7 +750,15 @@ func (s *service) getDBCompletion(w http.ResponseWriter, r *http.Request) {
}
}
sendJSON(w, s.model.Completion(device, folder).Map())
if comp, err := s.model.Completion(device, folder); err != nil {
status := http.StatusInternalServerError
if isFolderNotFound(err) {
status = http.StatusNotFound
}
http.Error(w, err.Error(), status)
} else {
sendJSON(w, comp.Map())
}
}
func (s *service) getDBStatus(w http.ResponseWriter, r *http.Request) {
@@ -877,8 +890,25 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
file := qs.Get("file")
gf, gfOk := s.model.CurrentGlobalFile(folder, file)
lf, lfOk := s.model.CurrentFolderFile(folder, file)
errStatus := http.StatusInternalServerError
gf, gfOk, err := s.model.CurrentGlobalFile(folder, file)
if err != nil {
if isFolderNotFound(err) {
errStatus = http.StatusNotFound
}
http.Error(w, err.Error(), errStatus)
return
}
lf, lfOk, err := s.model.CurrentFolderFile(folder, file)
if err != nil {
if isFolderNotFound(err) {
errStatus = http.StatusNotFound
}
http.Error(w, err.Error(), errStatus)
return
}
if !(gfOk || lfOk) {
// This file for sure does not exist.
@@ -886,7 +916,11 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
return
}
av := s.model.Availability(folder, gf, protocol.BlockInfo{})
av, err := s.model.Availability(folder, gf, protocol.BlockInfo{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
sendJSON(w, map[string]interface{}{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
@@ -1497,7 +1531,12 @@ func (s *service) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
for _, device := range folder.DeviceIDs() {
deviceStr := device.String()
if _, ok := s.model.Connection(device); ok {
tot[deviceStr] += s.model.Completion(device, folder.ID).CompletionPct
comp, err := s.model.Completion(device, folder.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tot[deviceStr] += comp.CompletionPct
} else {
tot[deviceStr] = 0
}
@@ -1771,8 +1810,13 @@ func addressIsLocalhost(addr string) bool {
// There was no port, so we assume the address was just a hostname
host = addr
}
switch strings.ToLower(host) {
case "localhost", "localhost.":
host = strings.ToLower(host)
switch {
case host == "localhost":
return true
case host == "localhost.":
return true
case strings.HasSuffix(host, ".localhost"):
return true
default:
ip := net.ParseIP(host)
@@ -1854,3 +1898,48 @@ func errorString(err error) *string {
}
return nil
}
// sanitizedHostname returns the given name in a suitable form for use as
// the common name in a certificate, or an error.
func sanitizedHostname(name string) (string, error) {
// Remove diacritics and non-alphanumerics. This works by first
// transforming into normalization form D (things with diacriticals are
// split into the base character and the mark) and then removing
// undesired characters.
t := transform.Chain(
// Split runes with diacritics into base character and mark.
norm.NFD,
// Leave only [A-Za-z0-9-.].
runes.Remove(runes.Predicate(func(r rune) bool {
return r > unicode.MaxASCII ||
!unicode.IsLetter(r) && !unicode.IsNumber(r) &&
r != '.' && r != '-'
})))
name, _, err := transform.String(t, name)
if err != nil {
return "", err
}
// Name should not start or end with a dash or dot.
name = strings.Trim(name, "-.")
// Name should not be empty.
if name == "" {
return "", errors.New("no suitable name")
}
return strings.ToLower(name), nil
}
func isFolderNotFound(err error) bool {
for _, target := range []error{
model.ErrFolderMissing,
model.ErrFolderPaused,
model.ErrFolderNotRunning,
} {
if errors.Is(err, target) {
return true
}
}
return false
}

View File

@@ -28,9 +28,15 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/assets"
"github.com/syncthing/syncthing/lib/config"
connmocks "github.com/syncthing/syncthing/lib/connections/mocks"
discovermocks "github.com/syncthing/syncthing/lib/discover/mocks"
"github.com/syncthing/syncthing/lib/events"
eventmocks "github.com/syncthing/syncthing/lib/events/mocks"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/logger"
loggermocks "github.com/syncthing/syncthing/lib/logger/mocks"
modelmocks "github.com/syncthing/syncthing/lib/model/mocks"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
@@ -40,13 +46,16 @@ import (
)
var (
confDir = filepath.Join("testdata", "config")
token = filepath.Join(confDir, "csrftokens.txt")
dev1 protocol.DeviceID
confDir = filepath.Join("testdata", "config")
token = filepath.Join(confDir, "csrftokens.txt")
dev1 protocol.DeviceID
apiCfg = newMockedConfig()
testAPIKey = "foobarbaz"
)
func init() {
dev1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
apiCfg.GUIReturns(config.GUIConfiguration{APIKey: testAPIKey})
}
func TestMain(m *testing.M) {
@@ -246,10 +255,7 @@ type httpTestCase struct {
func TestAPIServiceRequests(t *testing.T) {
t.Parallel()
const testAPIKey = "foobarbaz"
cfg := new(mockedConfig)
cfg.gui.APIKey = testAPIKey
baseURL, cancel, err := startHTTP(cfg)
baseURL, cancel, err := startHTTP(apiCfg)
if err != nil {
t.Fatal(err)
}
@@ -515,9 +521,11 @@ func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase, apikey strin
func TestHTTPLogin(t *testing.T) {
t.Parallel()
cfg := new(mockedConfig)
cfg.gui.User = "üser"
cfg.gui.Password = "$2a$10$IdIZTxTg/dCNuNEGlmLynOjqg4B1FvDKuIV5e0BB3pnWVHNb8.GSq" // bcrypt of "räksmörgås" in UTF-8
cfg := newMockedConfig()
cfg.GUIReturns(config.GUIConfiguration{
User: "üser",
Password: "$2a$10$IdIZTxTg/dCNuNEGlmLynOjqg4B1FvDKuIV5e0BB3pnWVHNb8.GSq", // bcrypt of "räksmörgås" in UTF-8
})
baseURL, cancel, err := startHTTP(cfg)
if err != nil {
t.Fatal(err)
@@ -581,19 +589,29 @@ func TestHTTPLogin(t *testing.T) {
}
func startHTTP(cfg config.Wrapper) (string, context.CancelFunc, error) {
m := new(mockedModel)
m := new(modelmocks.Model)
assetDir := "../../gui"
eventSub := new(mockedEventSub)
diskEventSub := new(mockedEventSub)
discoverer := new(mockedCachingMux)
connections := new(mockedConnections)
errorLog := new(mockedLoggerRecorder)
systemLog := new(mockedLoggerRecorder)
eventSub := new(eventmocks.BufferedSubscription)
diskEventSub := new(eventmocks.BufferedSubscription)
discoverer := new(discovermocks.Manager)
connections := new(connmocks.Service)
errorLog := new(loggermocks.Recorder)
systemLog := new(loggermocks.Recorder)
for _, l := range []*loggermocks.Recorder{errorLog, systemLog} {
l.SinceReturns([]logger.Line{
{
When: time.Now(),
Message: "Test message",
},
})
}
addrChan := make(chan string)
mockedSummary := &modelmocks.FolderSummaryService{}
mockedSummary.SummaryReturns(map[string]interface{}{"mocked": true}, nil)
// Instantiate the API service
urService := ur.New(cfg, m, connections, false)
svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, &mockedFolderSummaryService{}, errorLog, systemLog, false).(*service)
svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, mockedSummary, errorLog, systemLog, false).(*service)
defer os.Remove(token)
svc.started = addrChan
@@ -625,10 +643,7 @@ func startHTTP(cfg config.Wrapper) (string, context.CancelFunc, error) {
func TestCSRFRequired(t *testing.T) {
t.Parallel()
const testAPIKey = "foobarbaz"
cfg := new(mockedConfig)
cfg.gui.APIKey = testAPIKey
baseURL, cancel, err := startHTTP(cfg)
baseURL, cancel, err := startHTTP(apiCfg)
if err != nil {
t.Fatal("Unexpected error from getting base URL:", err)
}
@@ -701,10 +716,7 @@ func TestCSRFRequired(t *testing.T) {
func TestRandomString(t *testing.T) {
t.Parallel()
const testAPIKey = "foobarbaz"
cfg := new(mockedConfig)
cfg.gui.APIKey = testAPIKey
baseURL, cancel, err := startHTTP(cfg)
baseURL, cancel, err := startHTTP(apiCfg)
if err != nil {
t.Fatal(err)
}
@@ -794,10 +806,7 @@ func TestConfigPostDupFolder(t *testing.T) {
}
func testConfigPost(data io.Reader) (*http.Response, error) {
const testAPIKey = "foobarbaz"
cfg := new(mockedConfig)
cfg.gui.APIKey = testAPIKey
baseURL, cancel, err := startHTTP(cfg)
baseURL, cancel, err := startHTTP(apiCfg)
if err != nil {
return nil, err
}
@@ -816,8 +825,8 @@ func TestHostCheck(t *testing.T) {
// An API service bound to localhost should reject non-localhost host Headers
cfg := new(mockedConfig)
cfg.gui.RawAddress = "127.0.0.1:0"
cfg := newMockedConfig()
cfg.GUIReturns(config.GUIConfiguration{RawAddress: "127.0.0.1:0"})
baseURL, cancel, err := startHTTP(cfg)
if err != nil {
t.Fatal(err)
@@ -876,9 +885,11 @@ func TestHostCheck(t *testing.T) {
// A server with InsecureSkipHostCheck set behaves differently
cfg = new(mockedConfig)
cfg.gui.RawAddress = "127.0.0.1:0"
cfg.gui.InsecureSkipHostCheck = true
cfg = newMockedConfig()
cfg.GUIReturns(config.GUIConfiguration{
RawAddress: "127.0.0.1:0",
InsecureSkipHostCheck: true,
})
baseURL, cancel, err = startHTTP(cfg)
if err != nil {
t.Fatal(err)
@@ -900,9 +911,11 @@ func TestHostCheck(t *testing.T) {
// A server bound to a wildcard address also doesn't do the check
cfg = new(mockedConfig)
cfg.gui.RawAddress = "0.0.0.0:0"
cfg.gui.InsecureSkipHostCheck = true
cfg = newMockedConfig()
cfg.GUIReturns(config.GUIConfiguration{
RawAddress: "0.0.0.0:0",
InsecureSkipHostCheck: true,
})
baseURL, cancel, err = startHTTP(cfg)
if err != nil {
t.Fatal(err)
@@ -929,8 +942,10 @@ func TestHostCheck(t *testing.T) {
return
}
cfg = new(mockedConfig)
cfg.gui.RawAddress = "[::1]:0"
cfg = newMockedConfig()
cfg.GUIReturns(config.GUIConfiguration{
RawAddress: "[::1]:0",
})
baseURL, cancel, err = startHTTP(cfg)
if err != nil {
t.Fatal(err)
@@ -995,14 +1010,14 @@ func TestAddressIsLocalhost(t *testing.T) {
{"[::1]:8080", true},
{"127.0.0.1:8080", true},
{"127.23.45.56:8080", true},
{"www.localhost", true},
{"www.localhost:8080", true},
// These are all non-localhost addresses
{"example.com", false},
{"example.com:8080", false},
{"localhost.com", false},
{"localhost.com:8080", false},
{"www.localhost", false},
{"www.localhost:8080", false},
{"192.0.2.10", false},
{"192.0.2.10:8080", false},
{"0.0.0.0", false},
@@ -1023,10 +1038,7 @@ func TestAddressIsLocalhost(t *testing.T) {
func TestAccessControlAllowOriginHeader(t *testing.T) {
t.Parallel()
const testAPIKey = "foobarbaz"
cfg := new(mockedConfig)
cfg.gui.APIKey = testAPIKey
baseURL, cancel, err := startHTTP(cfg)
baseURL, cancel, err := startHTTP(apiCfg)
if err != nil {
t.Fatal(err)
}
@@ -1054,10 +1066,7 @@ func TestAccessControlAllowOriginHeader(t *testing.T) {
func TestOptionsRequest(t *testing.T) {
t.Parallel()
const testAPIKey = "foobarbaz"
cfg := new(mockedConfig)
cfg.gui.APIKey = testAPIKey
baseURL, cancel, err := startHTTP(cfg)
baseURL, cancel, err := startHTTP(apiCfg)
if err != nil {
t.Fatal(err)
}
@@ -1090,9 +1099,9 @@ func TestOptionsRequest(t *testing.T) {
func TestEventMasks(t *testing.T) {
t.Parallel()
cfg := new(mockedConfig)
defSub := new(mockedEventSub)
diskSub := new(mockedEventSub)
cfg := newMockedConfig()
defSub := new(eventmocks.BufferedSubscription)
diskSub := new(eventmocks.BufferedSubscription)
svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, events.NoopLogger, nil, nil, nil, nil, nil, nil, false).(*service)
defer os.Remove(token)
@@ -1253,11 +1262,9 @@ func TestConfigChanges(t *testing.T) {
defer os.Remove(tmpFile.Name())
w := config.Wrap(tmpFile.Name(), cfg, protocol.LocalDeviceID, events.NoopLogger)
tmpFile.Close()
if cfgService, ok := w.(suture.Service); ok {
cfgCtx, cfgCancel := context.WithCancel(context.Background())
go cfgService.Serve(cfgCtx)
defer cfgCancel()
}
cfgCtx, cfgCancel := context.WithCancel(context.Background())
go w.Serve(cfgCtx)
defer cfgCancel()
baseURL, cancel, err := startHTTP(w)
if err != nil {
t.Fatal("Unexpected error from getting base URL:", err)
@@ -1361,6 +1368,27 @@ func TestConfigChanges(t *testing.T) {
}
}
func TestSanitizedHostname(t *testing.T) {
cases := []struct {
in, out string
}{
{"foo.BAR-baz", "foo.bar-baz"},
{"~.~-Min 1:a Räksmörgås-dator 😀😎 ~.~-", "min1araksmorgas-dator"},
{"Vicenç-PC", "vicenc-pc"},
{"~.~-~.~-", ""},
{"", ""},
}
for _, tc := range cases {
res, err := sanitizedHostname(tc.in)
if tc.out == "" && err == nil {
t.Errorf("%q should cause error", tc.in)
} else if res != tc.out {
t.Errorf("%q => %q, expected %q", tc.in, res, tc.out)
}
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false

32
lib/api/auto/noassets.go Normal file
View File

@@ -0,0 +1,32 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//+build noassets
package auto
import (
"bytes"
"compress/gzip"
"github.com/syncthing/syncthing/lib/assets"
)
func Assets() map[string]assets.Asset {
// Return a minimal index.html and nothing else, to allow the trivial
// test to pass.
buf := new(bytes.Buffer)
gw := gzip.NewWriter(buf)
_, _ = gw.Write([]byte("<html></html>"))
_ = gw.Flush()
return map[string]assets.Asset{
"default/index.html": {
Gzipped: true,
Content: buf.String(),
},
}
}

View File

@@ -51,6 +51,12 @@ func (c *configMuxBuilder) registerConfigInsync(path string) {
})
}
func (c *configMuxBuilder) registerConfigRequiresRestart(path string) {
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, map[string]bool{"requiresRestart": c.cfg.RequiresRestart()})
})
}
func (c *configMuxBuilder) registerFolders(path string) {
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, c.cfg.FolderList())
@@ -181,6 +187,10 @@ func (c *configMuxBuilder) registerDevice(path string) {
c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
id, err := protocol.DeviceIDFromString(p.ByName("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
waiter, err := c.cfg.RemoveDevice(id)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)

View File

@@ -7,136 +7,15 @@
package api
import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/config/mocks"
)
type mockedConfig struct {
gui config.GUIConfiguration
}
func (c *mockedConfig) GUI() config.GUIConfiguration {
return c.gui
}
func (c *mockedConfig) ListenAddresses() []string {
return nil
}
func (c *mockedConfig) LDAP() config.LDAPConfiguration {
return config.LDAPConfiguration{}
}
func (c *mockedConfig) RawCopy() config.Configuration {
cfg := config.Configuration{}
util.SetDefaults(&cfg.Options)
return cfg
}
func (c *mockedConfig) Options() config.OptionsConfiguration {
return config.OptionsConfiguration{}
}
func (c *mockedConfig) Modify(config.ModifyFunction) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (c *mockedConfig) Subscribe(cm config.Committer) config.Configuration {
return config.Configuration{}
}
func (c *mockedConfig) Unsubscribe(cm config.Committer) {}
func (c *mockedConfig) Folders() map[string]config.FolderConfiguration {
return nil
}
func (c *mockedConfig) Devices() map[protocol.DeviceID]config.DeviceConfiguration {
return nil
}
func (c *mockedConfig) DeviceList() []config.DeviceConfiguration {
return nil
}
func (c *mockedConfig) Save() error {
return nil
}
func (c *mockedConfig) RequiresRestart() bool {
return false
}
func (c *mockedConfig) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {}
func (c *mockedConfig) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {}
func (c *mockedConfig) ConfigPath() string {
return ""
}
func (c *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
return config.FolderConfiguration{}, false
}
func (c *mockedConfig) FolderList() []config.FolderConfiguration {
return nil
}
func (c *mockedConfig) RemoveFolder(id string) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (c *mockedConfig) FolderPasswords(device protocol.DeviceID) map[string]string {
return nil
}
func (c *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
return config.DeviceConfiguration{}, false
}
func (c *mockedConfig) RemoveDevice(id protocol.DeviceID) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (c *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
return false
}
func (c *mockedConfig) IgnoredDevices() []config.ObservedDevice {
return nil
}
func (c *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
return false
}
func (c *mockedConfig) DefaultFolder() config.FolderConfiguration {
return config.FolderConfiguration{}
}
func (c *mockedConfig) SetDefaultFolder(config.FolderConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (c *mockedConfig) DefaultDevice() config.DeviceConfiguration {
return config.DeviceConfiguration{}
}
func (c *mockedConfig) SetDefaultDevice(config.DeviceConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (c *mockedConfig) GlobalDiscoveryServers() []string {
return nil
}
func (c *mockedConfig) StunServers() []string {
return nil
}
func (c *mockedConfig) MyID() protocol.DeviceID {
return protocol.DeviceID{}
func newMockedConfig() *mocks.Wrapper {
m := &mocks.Wrapper{}
m.ModifyReturns(noopWaiter{}, nil)
m.RemoveFolderReturns(noopWaiter{}, nil)
m.RemoveDeviceReturns(noopWaiter{}, nil)
return m
}
type noopWaiter struct{}

View File

@@ -1,33 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package api
import (
"context"
"github.com/syncthing/syncthing/lib/connections"
)
type mockedConnections struct{}
func (m *mockedConnections) ListenerStatus() map[string]connections.ListenerStatusEntry {
return nil
}
func (m *mockedConnections) ConnectionStatus() map[string]connections.ConnectionStatusEntry {
return nil
}
func (m *mockedConnections) NATType() string {
return ""
}
func (m *mockedConnections) Serve(ctx context.Context) error { return nil }
func (m *mockedConnections) ExternalAddresses() []string { return nil }
func (m *mockedConnections) AllAddresses() []string { return nil }

View File

@@ -1,46 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package api
import (
"context"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/protocol"
)
type mockedCachingMux struct{}
// from suture.Service
func (m *mockedCachingMux) Serve(ctx context.Context) error {
select {}
}
// from events.Finder
func (m *mockedCachingMux) Lookup(ctx context.Context, deviceID protocol.DeviceID) (direct []string, err error) {
return nil, nil
}
func (m *mockedCachingMux) Error() error {
return nil
}
func (m *mockedCachingMux) String() string {
return "mockedCachingMux"
}
func (m *mockedCachingMux) Cache() map[protocol.DeviceID]discover.CacheEntry {
return nil
}
// from events.Manager
func (m *mockedCachingMux) ChildErrors() map[string]error {
return nil
}

View File

@@ -1,21 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package api
import (
"time"
"github.com/syncthing/syncthing/lib/events"
)
type mockedEventSub struct{}
func (s *mockedEventSub) Since(id int, into []events.Event, timeout time.Duration) []events.Event {
select {}
}
func (s *mockedEventSub) Mask() events.EventType { return 0 }

View File

@@ -1,26 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package api
import (
"time"
"github.com/syncthing/syncthing/lib/logger"
)
type mockedLoggerRecorder struct{}
func (r *mockedLoggerRecorder) Since(t time.Time) []logger.Line {
return []logger.Line{
{
When: time.Now(),
Message: "Test message",
},
}
}
func (r *mockedLoggerRecorder) Clear() {}

View File

@@ -1,199 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package api
import (
"context"
"net"
"time"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/ur/contract"
"github.com/syncthing/syncthing/lib/versioner"
)
type mockedModel struct{}
func (m *mockedModel) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly bool) ([]*model.TreeEntry, error) {
return nil, nil
}
func (m *mockedModel) Completion(device protocol.DeviceID, folder string) model.FolderCompletion {
return model.FolderCompletion{}
}
func (m *mockedModel) Override(folder string) {}
func (m *mockedModel) Revert(folder string) {}
func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, error) {
return nil, nil, nil, nil
}
func (*mockedModel) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, page, perpage int) ([]db.FileInfoTruncated, error) {
return nil, nil
}
func (*mockedModel) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
return nil, nil
}
func (m *mockedModel) FolderProgressBytesCompleted(_ string) int64 {
return 0
}
func (m *mockedModel) NumConnections() int {
return 0
}
func (m *mockedModel) ConnectionStats() map[string]interface{} {
return nil
}
func (m *mockedModel) DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error) {
return nil, nil
}
func (m *mockedModel) FolderStatistics() (map[string]stats.FolderStatistics, error) {
return nil, nil
}
func (m *mockedModel) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
return protocol.FileInfo{}, false
}
func (m *mockedModel) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
return protocol.FileInfo{}, false
}
func (m *mockedModel) ResetFolder(folder string) {
}
func (m *mockedModel) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []model.Availability {
return nil
}
func (m *mockedModel) LoadIgnores(folder string) ([]string, []string, error) {
return nil, nil, nil
}
func (m *mockedModel) CurrentIgnores(folder string) ([]string, []string, error) {
return nil, nil, nil
}
func (m *mockedModel) SetIgnores(folder string, content []string) error {
return nil
}
func (m *mockedModel) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) {
return nil, nil
}
func (m *mockedModel) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]error, error) {
return nil, nil
}
func (m *mockedModel) PauseDevice(device protocol.DeviceID) {
}
func (m *mockedModel) ResumeDevice(device protocol.DeviceID) {}
func (m *mockedModel) DelayScan(folder string, next time.Duration) {}
func (m *mockedModel) ScanFolder(folder string) error {
return nil
}
func (m *mockedModel) ScanFolders() map[string]error {
return nil
}
func (m *mockedModel) ScanFolderSubdirs(folder string, subs []string) error {
return nil
}
func (m *mockedModel) BringToFront(folder, file string) {}
func (m *mockedModel) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) {
return nil, false
}
func (m *mockedModel) State(folder string) (string, time.Time, error) {
return "", time.Time{}, nil
}
func (m *mockedModel) UsageReportingStats(r *contract.Report, version int, preview bool) {
}
func (m *mockedModel) PendingDevices() (map[protocol.DeviceID]db.ObservedDevice, error) {
return nil, nil
}
func (m *mockedModel) PendingFolders(device protocol.DeviceID) (map[string]db.PendingFolder, error) {
return nil, nil
}
func (m *mockedModel) FolderErrors(folder string) ([]model.FileError, error) {
return nil, nil
}
func (m *mockedModel) WatchError(folder string) error {
return nil
}
func (m *mockedModel) Serve(ctx context.Context) error { return nil }
func (m *mockedModel) Index(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) error {
return nil
}
func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) error {
return nil
}
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
return nil, nil
}
func (m *mockedModel) ClusterConfig(deviceID protocol.DeviceID, config protocol.ClusterConfig) error {
return nil
}
func (m *mockedModel) Closed(conn protocol.Connection, err error) {}
func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) error {
return nil
}
func (m *mockedModel) AddConnection(conn protocol.Connection, hello protocol.Hello) {}
func (m *mockedModel) OnHello(protocol.DeviceID, net.Addr, protocol.Hello) error {
return nil
}
func (m *mockedModel) GetHello(protocol.DeviceID) protocol.HelloIntf {
return nil
}
func (m *mockedModel) StartDeadlockDetector(timeout time.Duration) {}
func (m *mockedModel) DBSnapshot(_ string) (*db.Snapshot, error) {
return nil, nil
}
type mockedFolderSummaryService struct{}
func (m *mockedFolderSummaryService) Serve(context.Context) error { return nil }
func (m *mockedFolderSummaryService) Summary(folder string) (map[string]interface{}, error) {
return map[string]interface{}{"mocked": true}, nil
}
func (m *mockedFolderSummaryService) OnEventRequest() {}

View File

@@ -23,7 +23,6 @@ import (
"testing"
"github.com/d4l3k/messagediff"
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
@@ -1301,16 +1300,10 @@ func startWrapper(wrapper Wrapper) *testWrapper {
Wrapper: wrapper,
done: make(chan struct{}),
}
s, ok := wrapper.(suture.Service)
if !ok {
tw.cancel = func() {}
close(tw.done)
return tw
}
ctx, cancel := context.WithCancel(context.Background())
tw.cancel = cancel
go func() {
s.Serve(ctx)
wrapper.Serve(ctx)
close(tw.done)
}()
return tw

View File

@@ -29,6 +29,7 @@ var (
const (
DefaultMarkerName = ".stfolder"
EncryptionTokenName = "syncthing-encryption_password_token"
maxConcurrentWritesDefault = 2
maxConcurrentWritesLimit = 64
)
@@ -46,11 +47,11 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
// cfg.Folders["default"].Filesystem() should be valid.
var opts []fs.Option
if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs {
opts = append(opts, fs.WithJunctionsAsDirs())
opts = append(opts, new(fs.OptionJunctionsAsDirs))
}
filesystem := fs.NewFilesystem(f.FilesystemType, f.Path, opts...)
if !f.CaseSensitiveFS {
filesystem = fs.NewCaseFilesystem(filesystem, opts...)
filesystem = fs.NewCaseFilesystem(filesystem)
}
return filesystem
}
@@ -211,6 +212,10 @@ func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices ma
} else if f.MaxConcurrentWrites > maxConcurrentWritesLimit {
f.MaxConcurrentWrites = maxConcurrentWritesLimit
}
if f.Type == FolderTypeReceiveEncrypted {
f.IgnorePerms = true
}
}
// RequiresRestartOnly returns a copy with only the attributes that require

View File

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:generate counterfeiter -o mocks/mocked_wrapper.go --fake-name Wrapper . Wrapper
package config
import (
@@ -18,6 +20,7 @@ import (
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture/v4"
)
const (
@@ -109,6 +112,8 @@ type Wrapper interface {
Subscribe(c Committer) Configuration
Unsubscribe(c Committer)
suture.Service
}
type wrapper struct {

View File

@@ -17,7 +17,6 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/thejerf/suture/v4"
"golang.org/x/time/rate"
)
@@ -45,12 +44,8 @@ func initConfig() (config.Wrapper, context.CancelFunc) {
dev3Conf = newDeviceConfiguration(wrapper, device3, "device3")
dev4Conf = newDeviceConfiguration(wrapper, device4, "device4")
var cancel context.CancelFunc = func() {}
if wrapperService, ok := wrapper.(suture.Service); ok {
var ctx context.Context
ctx, cancel = context.WithCancel(context.Background())
go wrapperService.Serve(ctx)
}
ctx, cancel := context.WithCancel(context.Background())
go wrapper.Serve(ctx)
dev2Conf.MaxRecvKbps = rand.Int() % 100000
dev2Conf.MaxSendKbps = rand.Int() % 100000

View File

@@ -0,0 +1,437 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mocks
import (
"context"
"sync"
"github.com/syncthing/syncthing/lib/connections"
)
type Service struct {
AllAddressesStub func() []string
allAddressesMutex sync.RWMutex
allAddressesArgsForCall []struct {
}
allAddressesReturns struct {
result1 []string
}
allAddressesReturnsOnCall map[int]struct {
result1 []string
}
ConnectionStatusStub func() map[string]connections.ConnectionStatusEntry
connectionStatusMutex sync.RWMutex
connectionStatusArgsForCall []struct {
}
connectionStatusReturns struct {
result1 map[string]connections.ConnectionStatusEntry
}
connectionStatusReturnsOnCall map[int]struct {
result1 map[string]connections.ConnectionStatusEntry
}
ExternalAddressesStub func() []string
externalAddressesMutex sync.RWMutex
externalAddressesArgsForCall []struct {
}
externalAddressesReturns struct {
result1 []string
}
externalAddressesReturnsOnCall map[int]struct {
result1 []string
}
ListenerStatusStub func() map[string]connections.ListenerStatusEntry
listenerStatusMutex sync.RWMutex
listenerStatusArgsForCall []struct {
}
listenerStatusReturns struct {
result1 map[string]connections.ListenerStatusEntry
}
listenerStatusReturnsOnCall map[int]struct {
result1 map[string]connections.ListenerStatusEntry
}
NATTypeStub func() string
nATTypeMutex sync.RWMutex
nATTypeArgsForCall []struct {
}
nATTypeReturns struct {
result1 string
}
nATTypeReturnsOnCall map[int]struct {
result1 string
}
ServeStub func(context.Context) error
serveMutex sync.RWMutex
serveArgsForCall []struct {
arg1 context.Context
}
serveReturns struct {
result1 error
}
serveReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *Service) AllAddresses() []string {
fake.allAddressesMutex.Lock()
ret, specificReturn := fake.allAddressesReturnsOnCall[len(fake.allAddressesArgsForCall)]
fake.allAddressesArgsForCall = append(fake.allAddressesArgsForCall, struct {
}{})
stub := fake.AllAddressesStub
fakeReturns := fake.allAddressesReturns
fake.recordInvocation("AllAddresses", []interface{}{})
fake.allAddressesMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Service) AllAddressesCallCount() int {
fake.allAddressesMutex.RLock()
defer fake.allAddressesMutex.RUnlock()
return len(fake.allAddressesArgsForCall)
}
func (fake *Service) AllAddressesCalls(stub func() []string) {
fake.allAddressesMutex.Lock()
defer fake.allAddressesMutex.Unlock()
fake.AllAddressesStub = stub
}
func (fake *Service) AllAddressesReturns(result1 []string) {
fake.allAddressesMutex.Lock()
defer fake.allAddressesMutex.Unlock()
fake.AllAddressesStub = nil
fake.allAddressesReturns = struct {
result1 []string
}{result1}
}
func (fake *Service) AllAddressesReturnsOnCall(i int, result1 []string) {
fake.allAddressesMutex.Lock()
defer fake.allAddressesMutex.Unlock()
fake.AllAddressesStub = nil
if fake.allAddressesReturnsOnCall == nil {
fake.allAddressesReturnsOnCall = make(map[int]struct {
result1 []string
})
}
fake.allAddressesReturnsOnCall[i] = struct {
result1 []string
}{result1}
}
func (fake *Service) ConnectionStatus() map[string]connections.ConnectionStatusEntry {
fake.connectionStatusMutex.Lock()
ret, specificReturn := fake.connectionStatusReturnsOnCall[len(fake.connectionStatusArgsForCall)]
fake.connectionStatusArgsForCall = append(fake.connectionStatusArgsForCall, struct {
}{})
stub := fake.ConnectionStatusStub
fakeReturns := fake.connectionStatusReturns
fake.recordInvocation("ConnectionStatus", []interface{}{})
fake.connectionStatusMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Service) ConnectionStatusCallCount() int {
fake.connectionStatusMutex.RLock()
defer fake.connectionStatusMutex.RUnlock()
return len(fake.connectionStatusArgsForCall)
}
func (fake *Service) ConnectionStatusCalls(stub func() map[string]connections.ConnectionStatusEntry) {
fake.connectionStatusMutex.Lock()
defer fake.connectionStatusMutex.Unlock()
fake.ConnectionStatusStub = stub
}
func (fake *Service) ConnectionStatusReturns(result1 map[string]connections.ConnectionStatusEntry) {
fake.connectionStatusMutex.Lock()
defer fake.connectionStatusMutex.Unlock()
fake.ConnectionStatusStub = nil
fake.connectionStatusReturns = struct {
result1 map[string]connections.ConnectionStatusEntry
}{result1}
}
func (fake *Service) ConnectionStatusReturnsOnCall(i int, result1 map[string]connections.ConnectionStatusEntry) {
fake.connectionStatusMutex.Lock()
defer fake.connectionStatusMutex.Unlock()
fake.ConnectionStatusStub = nil
if fake.connectionStatusReturnsOnCall == nil {
fake.connectionStatusReturnsOnCall = make(map[int]struct {
result1 map[string]connections.ConnectionStatusEntry
})
}
fake.connectionStatusReturnsOnCall[i] = struct {
result1 map[string]connections.ConnectionStatusEntry
}{result1}
}
func (fake *Service) ExternalAddresses() []string {
fake.externalAddressesMutex.Lock()
ret, specificReturn := fake.externalAddressesReturnsOnCall[len(fake.externalAddressesArgsForCall)]
fake.externalAddressesArgsForCall = append(fake.externalAddressesArgsForCall, struct {
}{})
stub := fake.ExternalAddressesStub
fakeReturns := fake.externalAddressesReturns
fake.recordInvocation("ExternalAddresses", []interface{}{})
fake.externalAddressesMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Service) ExternalAddressesCallCount() int {
fake.externalAddressesMutex.RLock()
defer fake.externalAddressesMutex.RUnlock()
return len(fake.externalAddressesArgsForCall)
}
func (fake *Service) ExternalAddressesCalls(stub func() []string) {
fake.externalAddressesMutex.Lock()
defer fake.externalAddressesMutex.Unlock()
fake.ExternalAddressesStub = stub
}
func (fake *Service) ExternalAddressesReturns(result1 []string) {
fake.externalAddressesMutex.Lock()
defer fake.externalAddressesMutex.Unlock()
fake.ExternalAddressesStub = nil
fake.externalAddressesReturns = struct {
result1 []string
}{result1}
}
func (fake *Service) ExternalAddressesReturnsOnCall(i int, result1 []string) {
fake.externalAddressesMutex.Lock()
defer fake.externalAddressesMutex.Unlock()
fake.ExternalAddressesStub = nil
if fake.externalAddressesReturnsOnCall == nil {
fake.externalAddressesReturnsOnCall = make(map[int]struct {
result1 []string
})
}
fake.externalAddressesReturnsOnCall[i] = struct {
result1 []string
}{result1}
}
func (fake *Service) ListenerStatus() map[string]connections.ListenerStatusEntry {
fake.listenerStatusMutex.Lock()
ret, specificReturn := fake.listenerStatusReturnsOnCall[len(fake.listenerStatusArgsForCall)]
fake.listenerStatusArgsForCall = append(fake.listenerStatusArgsForCall, struct {
}{})
stub := fake.ListenerStatusStub
fakeReturns := fake.listenerStatusReturns
fake.recordInvocation("ListenerStatus", []interface{}{})
fake.listenerStatusMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Service) ListenerStatusCallCount() int {
fake.listenerStatusMutex.RLock()
defer fake.listenerStatusMutex.RUnlock()
return len(fake.listenerStatusArgsForCall)
}
func (fake *Service) ListenerStatusCalls(stub func() map[string]connections.ListenerStatusEntry) {
fake.listenerStatusMutex.Lock()
defer fake.listenerStatusMutex.Unlock()
fake.ListenerStatusStub = stub
}
func (fake *Service) ListenerStatusReturns(result1 map[string]connections.ListenerStatusEntry) {
fake.listenerStatusMutex.Lock()
defer fake.listenerStatusMutex.Unlock()
fake.ListenerStatusStub = nil
fake.listenerStatusReturns = struct {
result1 map[string]connections.ListenerStatusEntry
}{result1}
}
func (fake *Service) ListenerStatusReturnsOnCall(i int, result1 map[string]connections.ListenerStatusEntry) {
fake.listenerStatusMutex.Lock()
defer fake.listenerStatusMutex.Unlock()
fake.ListenerStatusStub = nil
if fake.listenerStatusReturnsOnCall == nil {
fake.listenerStatusReturnsOnCall = make(map[int]struct {
result1 map[string]connections.ListenerStatusEntry
})
}
fake.listenerStatusReturnsOnCall[i] = struct {
result1 map[string]connections.ListenerStatusEntry
}{result1}
}
func (fake *Service) NATType() string {
fake.nATTypeMutex.Lock()
ret, specificReturn := fake.nATTypeReturnsOnCall[len(fake.nATTypeArgsForCall)]
fake.nATTypeArgsForCall = append(fake.nATTypeArgsForCall, struct {
}{})
stub := fake.NATTypeStub
fakeReturns := fake.nATTypeReturns
fake.recordInvocation("NATType", []interface{}{})
fake.nATTypeMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Service) NATTypeCallCount() int {
fake.nATTypeMutex.RLock()
defer fake.nATTypeMutex.RUnlock()
return len(fake.nATTypeArgsForCall)
}
func (fake *Service) NATTypeCalls(stub func() string) {
fake.nATTypeMutex.Lock()
defer fake.nATTypeMutex.Unlock()
fake.NATTypeStub = stub
}
func (fake *Service) NATTypeReturns(result1 string) {
fake.nATTypeMutex.Lock()
defer fake.nATTypeMutex.Unlock()
fake.NATTypeStub = nil
fake.nATTypeReturns = struct {
result1 string
}{result1}
}
func (fake *Service) NATTypeReturnsOnCall(i int, result1 string) {
fake.nATTypeMutex.Lock()
defer fake.nATTypeMutex.Unlock()
fake.NATTypeStub = nil
if fake.nATTypeReturnsOnCall == nil {
fake.nATTypeReturnsOnCall = make(map[int]struct {
result1 string
})
}
fake.nATTypeReturnsOnCall[i] = struct {
result1 string
}{result1}
}
func (fake *Service) Serve(arg1 context.Context) error {
fake.serveMutex.Lock()
ret, specificReturn := fake.serveReturnsOnCall[len(fake.serveArgsForCall)]
fake.serveArgsForCall = append(fake.serveArgsForCall, struct {
arg1 context.Context
}{arg1})
stub := fake.ServeStub
fakeReturns := fake.serveReturns
fake.recordInvocation("Serve", []interface{}{arg1})
fake.serveMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Service) ServeCallCount() int {
fake.serveMutex.RLock()
defer fake.serveMutex.RUnlock()
return len(fake.serveArgsForCall)
}
func (fake *Service) ServeCalls(stub func(context.Context) error) {
fake.serveMutex.Lock()
defer fake.serveMutex.Unlock()
fake.ServeStub = stub
}
func (fake *Service) ServeArgsForCall(i int) context.Context {
fake.serveMutex.RLock()
defer fake.serveMutex.RUnlock()
argsForCall := fake.serveArgsForCall[i]
return argsForCall.arg1
}
func (fake *Service) ServeReturns(result1 error) {
fake.serveMutex.Lock()
defer fake.serveMutex.Unlock()
fake.ServeStub = nil
fake.serveReturns = struct {
result1 error
}{result1}
}
func (fake *Service) ServeReturnsOnCall(i int, result1 error) {
fake.serveMutex.Lock()
defer fake.serveMutex.Unlock()
fake.ServeStub = nil
if fake.serveReturnsOnCall == nil {
fake.serveReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.serveReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *Service) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.allAddressesMutex.RLock()
defer fake.allAddressesMutex.RUnlock()
fake.connectionStatusMutex.RLock()
defer fake.connectionStatusMutex.RUnlock()
fake.externalAddressesMutex.RLock()
defer fake.externalAddressesMutex.RUnlock()
fake.listenerStatusMutex.RLock()
defer fake.listenerStatusMutex.RUnlock()
fake.nATTypeMutex.RLock()
defer fake.nATTypeMutex.RUnlock()
fake.serveMutex.RLock()
defer fake.serveMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *Service) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ connections.Service = new(Service)

View File

@@ -90,10 +90,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
return newInternalConn(&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority), nil
}
type quicDialerFactory struct {
cfg config.Wrapper
tlsCfg *tls.Config
}
type quicDialerFactory struct{}
func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
return &quicDialer{commonDialer{

View File

@@ -79,7 +79,7 @@ func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string)
}
func (t *quicListener) serve(ctx context.Context) error {
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
network := strings.ReplaceAll(t.uri.Scheme, "quic", "udp")
packetConn, err := net.ListenPacket(network, t.uri.Host)
if err != nil {
@@ -90,13 +90,17 @@ func (t *quicListener) serve(ctx context.Context) error {
svc, conn := stun.New(t.cfg, t, packetConn)
defer func() { _ = conn.Close() }()
wrapped := &stunConnQUICWrapper{
PacketConn: conn,
underlying: packetConn.(*net.UDPConn),
}
go svc.Serve(ctx)
registry.Register(t.uri.Scheme, conn)
defer registry.Unregister(t.uri.Scheme, conn)
registry.Register(t.uri.Scheme, wrapped)
defer registry.Unregister(t.uri.Scheme, wrapped)
listener, err := quic.Listen(conn, t.tlsCfg, quicConfig)
listener, err := quic.Listen(wrapped, t.tlsCfg, quicConfig)
if err != nil {
l.Infoln("Listen (BEP/quic):", err)
return err
@@ -170,7 +174,7 @@ func (t *quicListener) WANAddresses() []*url.URL {
func (t *quicListener) LANAddresses() []*url.URL {
addrs := []*url.URL{t.uri}
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
network := strings.ReplaceAll(t.uri.Scheme, "quic", "udp")
addrs = append(addrs, getURLsForAllAdaptersIfUnspecified(network, t.uri)...)
return addrs
}
@@ -213,3 +217,13 @@ func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.
func (quicListenerFactory) Enabled(cfg config.Configuration) bool {
return true
}
type stunConnQUICWrapper struct {
net.PacketConn
underlying *net.UDPConn
}
// SetReadBuffer is required by QUIC.
func (s *stunConnQUICWrapper) SetReadBuffer(size int) error {
return s.underlying.SetReadBuffer(size)
}

View File

@@ -4,6 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:generate counterfeiter -o mocks/service.go --fake-name Service . Service
package connections
import (
@@ -242,7 +244,7 @@ func (s *service) handle(ctx context.Context) error {
// though, especially in the presence of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == s.myID {
l.Infof("Connected to myself (%s) at %s - should not happen", remoteID, c)
l.Debugf("Connected to myself (%s) at %s", remoteID, c)
c.Close()
continue
}
@@ -333,13 +335,7 @@ func (s *service) handle(ctx context.Context) error {
isLAN := s.isLAN(c.RemoteAddr())
rd, wr := s.limiter.getLimiters(remoteID, c, isLAN)
var protoConn protocol.Connection
passwords := s.cfg.FolderPasswords(remoteID)
if len(passwords) > 0 {
protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, c, s.model, c, deviceCfg.Compression)
} else {
protoConn = protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression)
}
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID))
l.Infof("Established secure connection to %s at %s", remoteID, c)
@@ -476,7 +472,7 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
// doesn't have much effect, but it may result in getting up and running
// quicker if only a subset of configured devices are actually reachable
// (by prioritizing those that were reachable recently).
dialQueue.Sort(queue)
queue.Sort()
// Perform dials according to the queue, stopping when we've reached the
// allowed additional number of connections (if limited).
@@ -1021,7 +1017,7 @@ func (s *service) validateIdentity(c internalConn, expectedID protocol.DeviceID)
// though, especially in the presence of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == s.myID {
l.Infof("Connected to myself (%s) at %s - should not happen", remoteID, c)
l.Debugf("Connected to myself (%s) at %s", remoteID, c)
c.Close()
return errors.New("connected to self")
}

View File

@@ -89,7 +89,7 @@ func newInternalConn(tc tlsConn, connType connType, priority int) internalConn {
tlsConn: tc,
connType: connType,
priority: priority,
establishedAt: time.Now(),
establishedAt: time.Now().Truncate(time.Second),
}
}

View File

@@ -8,12 +8,7 @@ package backend
import (
"errors"
"os"
"strings"
"sync"
"time"
"github.com/syncthing/syncthing/lib/locations"
)
// CommitHook is a function that is executed before a WriteTransaction is
@@ -131,10 +126,6 @@ const (
)
func Open(path string, tuning Tuning) (Backend, error) {
if err := maybeCopyDatabase(path, strings.Replace(path, locations.LevelDBDir, locations.BadgerDir, 1), OpenLevelDBAuto, OpenBadger); err != nil {
return nil, err
}
return OpenLevelDB(path, tuning)
}
@@ -209,70 +200,3 @@ func (cg *closeWaitGroup) CloseWait() {
cg.closeMut.Unlock()
cg.WaitGroup.Wait()
}
type opener func(path string) (Backend, error)
// maybeCopyDatabase copies the database if the destination doesn't exist
// but the source does.
func maybeCopyDatabase(toPath, fromPath string, toOpen, fromOpen opener) error {
if _, err := os.Lstat(toPath); !os.IsNotExist(err) {
// Destination database exists (or is otherwise unavailable), do not
// attempt to overwrite it.
return nil
}
if _, err := os.Lstat(fromPath); err != nil {
// Source database is not available, so nothing to copy
return nil
}
fromDB, err := fromOpen(fromPath)
if err != nil {
return err
}
defer fromDB.Close()
toDB, err := toOpen(toPath)
if err != nil {
// That's odd, but it will be handled & reported in the usual path
// so we can ignore it here.
return err
}
defer toDB.Close()
l.Infoln("Copying database for format conversion...")
if err := copyBackend(toDB, fromDB); err != nil {
return err
}
// Move the old database out of the way to mark it as migrated.
fromDB.Close()
_ = os.Rename(fromPath, fromPath+".migrated."+time.Now().Format("20060102150405"))
return nil
}
func copyBackend(to, from Backend) error {
srcIt, err := from.NewPrefixIterator(nil)
if err != nil {
return err
}
defer srcIt.Release()
dstTx, err := to.NewWriteTransaction()
if err != nil {
return err
}
defer dstTx.Release()
for srcIt.Next() {
if err := dstTx.Put(srcIt.Key(), srcIt.Value()); err != nil {
return err
}
}
if srcIt.Error() != nil {
return err
}
srcIt.Release()
return dstTx.Commit()
}

View File

@@ -1,467 +0,0 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import (
"bytes"
"errors"
"time"
badger "github.com/dgraph-io/badger/v2"
)
const (
checkpointFlushMinSize = 128 << KiB
maxCacheSize = 64 << MiB
)
func OpenBadger(path string) (Backend, error) {
opts := badger.DefaultOptions(path)
opts = opts.WithMaxCacheSize(maxCacheSize).WithCompactL0OnClose(false)
opts.Logger = nil
backend, err := openBadger(opts)
if err != nil {
return nil, err
}
backend.location = path
return backend, nil
}
func OpenBadgerMemory() Backend {
opts := badger.DefaultOptions("").WithInMemory(true)
opts.Logger = nil
backend, err := openBadger(opts)
if err != nil {
// Opening in-memory should never be able to fail, and is anyway
// used just by tests.
panic(err)
}
return backend
}
func openBadger(opts badger.Options) (*badgerBackend, error) {
// XXX: We should find good values for memory utilization in the "small"
// and "large" cases we support for LevelDB. Some notes here:
// https://github.com/dgraph-io/badger/tree/v2.0.3#memory-usage
bdb, err := badger.Open(opts)
if err != nil {
return nil, wrapBadgerErr(err)
}
return &badgerBackend{
bdb: bdb,
closeWG: &closeWaitGroup{},
}, nil
}
// badgerBackend implements Backend on top of a badger
type badgerBackend struct {
bdb *badger.DB
closeWG *closeWaitGroup
location string
}
func (b *badgerBackend) NewReadTransaction() (ReadTransaction, error) {
rel, err := newReleaser(b.closeWG)
if err != nil {
return nil, err
}
return badgerSnapshot{
txn: b.bdb.NewTransaction(false),
rel: rel,
}, nil
}
func (b *badgerBackend) NewWriteTransaction(hooks ...CommitHook) (WriteTransaction, error) {
rel1, err := newReleaser(b.closeWG)
if err != nil {
return nil, err
}
rel2, err := newReleaser(b.closeWG)
if err != nil {
rel1.Release()
return nil, err
}
// We use two transactions here to preserve the property that our
// leveldb wrapper has, that writes in a transaction are completely
// invisible until it's committed, even inside that same transaction.
rtxn := b.bdb.NewTransaction(false)
wtxn := b.bdb.NewTransaction(true)
return &badgerTransaction{
badgerSnapshot: badgerSnapshot{
txn: rtxn,
rel: rel1,
},
txn: wtxn,
bdb: b.bdb,
rel: rel2,
commitHooks: hooks,
}, nil
}
func (b *badgerBackend) Close() error {
b.closeWG.CloseWait()
return wrapBadgerErr(b.bdb.Close())
}
func (b *badgerBackend) Get(key []byte) ([]byte, error) {
if err := b.closeWG.Add(1); err != nil {
return nil, err
}
defer b.closeWG.Done()
txn := b.bdb.NewTransaction(false)
defer txn.Discard()
item, err := txn.Get(key)
if err != nil {
return nil, wrapBadgerErr(err)
}
val, err := item.ValueCopy(nil)
if err != nil {
return nil, wrapBadgerErr(err)
}
return val, nil
}
func (b *badgerBackend) NewPrefixIterator(prefix []byte) (Iterator, error) {
if err := b.closeWG.Add(1); err != nil {
return nil, err
}
txn := b.bdb.NewTransaction(false)
it := badgerPrefixIterator(txn, prefix)
it.releaseFn = func() {
defer b.closeWG.Done()
txn.Discard()
}
return it, nil
}
func (b *badgerBackend) NewRangeIterator(first, last []byte) (Iterator, error) {
if err := b.closeWG.Add(1); err != nil {
return nil, err
}
txn := b.bdb.NewTransaction(false)
it := badgerRangeIterator(txn, first, last)
it.releaseFn = func() {
defer b.closeWG.Done()
txn.Discard()
}
return it, nil
}
func (b *badgerBackend) Put(key, val []byte) error {
if err := b.closeWG.Add(1); err != nil {
return err
}
defer b.closeWG.Done()
txn := b.bdb.NewTransaction(true)
if err := txn.Set(key, val); err != nil {
txn.Discard()
return wrapBadgerErr(err)
}
return wrapBadgerErr(txn.Commit())
}
func (b *badgerBackend) Delete(key []byte) error {
if err := b.closeWG.Add(1); err != nil {
return err
}
defer b.closeWG.Done()
txn := b.bdb.NewTransaction(true)
if err := txn.Delete(key); err != nil {
txn.Discard()
return wrapBadgerErr(err)
}
return wrapBadgerErr(txn.Commit())
}
func (b *badgerBackend) Compact() error {
if err := b.closeWG.Add(1); err != nil {
return err
}
defer b.closeWG.Done()
// This weird looking loop is as recommended in the README
// (https://github.com/dgraph-io/badger/tree/v2.0.3#garbage-collection).
// Basically, the RunValueLogGC will pick some promising thing to
// garbage collect at random and return nil if it improved the
// situation, then return ErrNoRewrite when there is nothing more to GC.
// The 0.5 is the discard ratio, for which the method docs say they
// "recommend setting discardRatio to 0.5, thus indicating that a file
// be rewritten if half the space can be discarded".
var err error
t0 := time.Now()
for err == nil {
if time.Since(t0) > time.Hour {
l.Warnln("Database compaction is taking a long time, performance may be impacted. Consider investigating and/or opening an issue if this warning repeats.")
t0 = time.Now()
}
err = b.bdb.RunValueLogGC(0.5)
}
if errors.Is(err, badger.ErrNoRewrite) {
// GC did nothing, because nothing needed to be done
return nil
}
if errors.Is(err, badger.ErrRejected) {
// GC was already running (could possibly happen), or the database
// is closed (can't happen).
return nil
}
if errors.Is(err, badger.ErrGCInMemoryMode) {
// GC in in-memory mode, which is fine.
return nil
}
return err
}
func (b *badgerBackend) Location() string {
return b.location
}
// badgerSnapshot implements backend.ReadTransaction
type badgerSnapshot struct {
txn *badger.Txn
rel *releaser
}
func (l badgerSnapshot) Get(key []byte) ([]byte, error) {
item, err := l.txn.Get(key)
if err != nil {
return nil, wrapBadgerErr(err)
}
val, err := item.ValueCopy(nil)
if err != nil {
return nil, wrapBadgerErr(err)
}
return val, nil
}
func (l badgerSnapshot) NewPrefixIterator(prefix []byte) (Iterator, error) {
return badgerPrefixIterator(l.txn, prefix), nil
}
func (l badgerSnapshot) NewRangeIterator(first, last []byte) (Iterator, error) {
return badgerRangeIterator(l.txn, first, last), nil
}
func (l badgerSnapshot) Release() {
defer l.rel.Release()
l.txn.Discard()
}
type badgerTransaction struct {
badgerSnapshot
txn *badger.Txn
bdb *badger.DB
rel *releaser
size int
commitHooks []CommitHook
}
func (t *badgerTransaction) Delete(key []byte) error {
t.size += len(key)
kc := make([]byte, len(key))
copy(kc, key)
return t.transactionRetried(func(txn *badger.Txn) error {
return txn.Delete(kc)
})
}
func (t *badgerTransaction) Put(key, val []byte) error {
t.size += len(key) + len(val)
kc := make([]byte, len(key))
copy(kc, key)
vc := make([]byte, len(val))
copy(vc, val)
return t.transactionRetried(func(txn *badger.Txn) error {
return txn.Set(kc, vc)
})
}
// transactionRetried performs the given operation in the current
// transaction, with commit and retry if Badger says the transaction has
// grown too large.
func (t *badgerTransaction) transactionRetried(fn func(*badger.Txn) error) error {
if err := fn(t.txn); err == badger.ErrTxnTooBig {
if err := t.txn.Commit(); err != nil {
return wrapBadgerErr(err)
}
t.size = 0
t.txn = t.bdb.NewTransaction(true)
return wrapBadgerErr(fn(t.txn))
} else if err != nil {
return wrapBadgerErr(err)
}
return nil
}
func (t *badgerTransaction) Commit() error {
defer t.rel.Release()
defer t.badgerSnapshot.Release()
for _, hook := range t.commitHooks {
if err := hook(t); err != nil {
return err
}
}
return wrapBadgerErr(t.txn.Commit())
}
func (t *badgerTransaction) Checkpoint() error {
if t.size < checkpointFlushMinSize {
return nil
}
for _, hook := range t.commitHooks {
if err := hook(t); err != nil {
return err
}
}
err := t.txn.Commit()
if err == nil {
t.size = 0
t.txn = t.bdb.NewTransaction(true)
}
return wrapBadgerErr(err)
}
func (t *badgerTransaction) Release() {
defer t.rel.Release()
defer t.badgerSnapshot.Release()
t.txn.Discard()
}
type badgerIterator struct {
it *badger.Iterator
prefix []byte
first []byte
last []byte
releaseFn func()
released bool
didSeek bool
err error
}
func (i *badgerIterator) Next() bool {
if i.err != nil {
return false
}
for {
if !i.didSeek {
if i.first != nil {
// Range iterator
i.it.Seek(i.first)
} else {
// Prefix iterator
i.it.Seek(i.prefix)
}
i.didSeek = true
} else {
i.it.Next()
}
if !i.it.ValidForPrefix(i.prefix) {
// Done
return false
}
if i.first == nil && i.last == nil {
// No range checks required
return true
}
key := i.it.Item().Key()
if bytes.Compare(key, i.last) > 0 {
// Key is after range last
return false
}
return true
}
}
func (i *badgerIterator) Key() []byte {
if i.err != nil {
return nil
}
return i.it.Item().Key()
}
func (i *badgerIterator) Value() []byte {
if i.err != nil {
return nil
}
val, err := i.it.Item().ValueCopy(nil)
if err != nil {
i.err = err
}
return val
}
func (i *badgerIterator) Error() error {
return wrapBadgerErr(i.err)
}
func (i *badgerIterator) Release() {
if i.released {
// We already closed this iterator, no need to do it again
// (and the releaseFn might hang if we do).
return
}
i.released = true
i.it.Close()
if i.releaseFn != nil {
i.releaseFn()
}
}
// wrapBadgerErr wraps errors so that the backend package can recognize them
func wrapBadgerErr(err error) error {
if err == nil {
return nil
}
if err == badger.ErrDiscardedTxn {
return &errClosed{}
}
if err == badger.ErrKeyNotFound {
return &errNotFound{}
}
return err
}
func badgerPrefixIterator(txn *badger.Txn, prefix []byte) *badgerIterator {
it := iteratorForPrefix(txn, prefix)
return &badgerIterator{it: it, prefix: prefix}
}
func badgerRangeIterator(txn *badger.Txn, first, last []byte) *badgerIterator {
prefix := commonPrefix(first, last)
it := iteratorForPrefix(txn, prefix)
return &badgerIterator{it: it, prefix: prefix, first: first, last: last}
}
func iteratorForPrefix(txn *badger.Txn, prefix []byte) *badger.Iterator {
opts := badger.DefaultIteratorOptions
opts.Prefix = prefix
return txn.NewIterator(opts)
}
func commonPrefix(a, b []byte) []byte {
minLen := len(a)
if len(b) < minLen {
minLen = len(b)
}
prefix := make([]byte, 0, minLen)
for i := 0; i < minLen; i++ {
if a[i] != b[i] {
break
}
prefix = append(prefix, a[i])
}
return prefix
}

View File

@@ -1,38 +0,0 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import "testing"
func TestCommonPrefix(t *testing.T) {
cases := []struct {
a string
b string
common string
}{
{"", "", ""},
{"a", "b", ""},
{"aa", "ab", "a"},
{"aa", "a", "a"},
{"a", "aa", "a"},
{"aabab", "ab", "a"},
{"ab", "aabab", "a"},
{"abac", "ababab", "aba"},
{"ababab", "abac", "aba"},
}
for _, tc := range cases {
pref := string(commonPrefix([]byte(tc.a), []byte(tc.b)))
if pref != tc.common {
t.Errorf("commonPrefix(%q, %q) => %q, expected %q", tc.a, tc.b, pref, tc.common)
}
}
}
func TestBadgerBackendBehavior(t *testing.T) {
testBackendBehavior(t, OpenBadgerMemory)
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
)
var files, filesUpdated, oneFile, firstHalf, secondHalf, changed100, unchanged100 []protocol.FileInfo
var files, oneFile, firstHalf, secondHalf, changed100, unchanged100 []protocol.FileInfo
func lazyInitBenchFiles() {
if files != nil {
@@ -185,7 +185,7 @@ func BenchmarkNeedHalf(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithNeed(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -209,7 +209,7 @@ func BenchmarkNeedHalfRemote(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := fset.Snapshot()
snap := snapshot(b, fset)
snap.WithNeed(remoteDevice0, func(fi protocol.FileIntf) bool {
count++
return true
@@ -230,7 +230,7 @@ func BenchmarkHave(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -251,7 +251,7 @@ func BenchmarkGlobal(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithGlobal(func(fi protocol.FileIntf) bool {
count++
return true
@@ -272,7 +272,7 @@ func BenchmarkNeedHalfTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithNeedTruncated(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -293,7 +293,7 @@ func BenchmarkHaveTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithHaveTruncated(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -314,7 +314,7 @@ func BenchmarkGlobalTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithGlobalTruncated(func(fi protocol.FileIntf) bool {
count++
return true
@@ -336,7 +336,7 @@ func BenchmarkNeedCount(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
_ = snap.NeedSize(protocol.LocalDeviceID)
snap.Release()
}

View File

@@ -77,7 +77,7 @@ func TestIgnoredFiles(t *testing.T) {
// Local files should have the "ignored" bit in addition to just being
// generally invalid if we want to look at the simulation of that bit.
snap := fs.Snapshot()
snap := snapshot(t, fs)
defer snap.Release()
fi, ok := snap.Get(protocol.LocalDeviceID, "foo")
if !ok {
@@ -262,6 +262,9 @@ func TestUpdate0to3(t *testing.T) {
t.Fatal(err)
}
key, err = trans.keyer.GenerateDeviceFileKey(key, folder, vl.Versions[0].Device, name)
if err != nil {
t.Fatal(err)
}
fi, ok, err := trans.getFileTrunc(key, false)
if err != nil {
t.Fatal(err)
@@ -866,7 +869,7 @@ func TestCheckLocalNeed(t *testing.T) {
fs.Update(remoteDevice0, files)
checkNeed := func() {
snap := fs.Snapshot()
snap := snapshot(t, fs)
defer snap.Release()
c := snap.NeedSize(protocol.LocalDeviceID)
if c.Files != 2 {
@@ -974,7 +977,7 @@ func TestNeedAfterDropGlobal(t *testing.T) {
fs.Update(remoteDevice1, files[1:])
// remoteDevice1 needs one file: test
snap := fs.Snapshot()
snap := snapshot(t, fs)
c := snap.NeedSize(remoteDevice1)
if c.Files != 1 {
t.Errorf("Expected 1 needed files initially, got %v", c.Files)
@@ -986,7 +989,7 @@ func TestNeedAfterDropGlobal(t *testing.T) {
fs.Drop(remoteDevice0)
// remoteDevice1 still needs test.
snap = fs.Snapshot()
snap = snapshot(t, fs)
c = snap.NeedSize(remoteDevice1)
if c.Files != 1 {
t.Errorf("Expected still 1 needed files, got %v", c.Files)

View File

@@ -846,6 +846,10 @@ func (db *Lowlevel) getMetaAndCheck(folder string) (*metadataTracker, error) {
db.gcMut.RLock()
defer db.gcMut.RUnlock()
return db.getMetaAndCheckGCLocked(folder)
}
func (db *Lowlevel) getMetaAndCheckGCLocked(folder string) (*metadataTracker, error) {
fixed, err := db.checkLocalNeed([]byte(folder))
if err != nil {
return nil, fmt.Errorf("checking local need: %w", err)
@@ -928,6 +932,9 @@ func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) {
meta.addFile(protocol.GlobalDeviceID, f)
return true
})
if err != nil {
return nil, err
}
meta.emptyNeeded(protocol.LocalDeviceID)
err = t.withNeed(folder, protocol.LocalDeviceID[:], true, func(f protocol.FileIntf) bool {

View File

@@ -148,10 +148,9 @@ func (m *metadataTracker) countsPtr(dev protocol.DeviceID, flag uint32) *Counts
// the metadatatracker, even if there's no change to the need
// bucket itself.
nkey := metaKey{dev, needFlag}
nidx, ok := m.indexes[nkey]
if !ok {
if _, ok := m.indexes[nkey]; !ok {
// Initially a new device needs everything, except deletes
nidx = len(m.counts.Counts)
nidx := len(m.counts.Counts)
m.counts.Counts = append(m.counts.Counts, m.allNeededCounts(dev))
m.indexes[nkey] = nidx
}

View File

@@ -117,7 +117,7 @@ func TestRecalcMeta(t *testing.T) {
s1.Update(protocol.LocalDeviceID, files)
// Verify local/global size
snap := s1.Snapshot()
snap := snapshot(t, s1)
ls := snap.LocalSize()
gs := snap.GlobalSize()
snap.Release()
@@ -149,7 +149,7 @@ func TestRecalcMeta(t *testing.T) {
}
// Verify that our bad data "took"
snap = s1.Snapshot()
snap = snapshot(t, s1)
ls = snap.LocalSize()
gs = snap.GlobalSize()
snap.Release()
@@ -164,7 +164,7 @@ func TestRecalcMeta(t *testing.T) {
s2 := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, "fake"), ldb)
// Verify local/global size
snap = s2.Snapshot()
snap = snapshot(t, s2)
ls = snap.LocalSize()
gs = snap.GlobalSize()
snap.Release()

View File

@@ -15,7 +15,7 @@ import (
func (db *Lowlevel) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error {
key := db.keyer.GeneratePendingDeviceKey(nil, device[:])
od := ObservedDevice{
Time: time.Now().Round(time.Second),
Time: time.Now().Truncate(time.Second),
Name: name,
Address: address,
}
@@ -66,14 +66,15 @@ func (db *Lowlevel) PendingDevices() (map[protocol.DeviceID]ObservedDevice, erro
return res, nil
}
func (db *Lowlevel) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) error {
func (db *Lowlevel) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID, receiveEncrypted bool) error {
key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id))
if err != nil {
return err
}
of := ObservedFolder{
Time: time.Now().Round(time.Second),
Label: label,
Time: time.Now().Truncate(time.Second),
Label: label,
ReceiveEncrypted: receiveEncrypted,
}
bs, err := of.Marshal()
if err != nil {
@@ -123,7 +124,7 @@ func (db *Lowlevel) RemovePendingFoldersBeforeTime(device protocol.DeviceID, old
return nil, err
}
defer iter.Release()
oldest = oldest.Round(time.Second)
oldest = oldest.Truncate(time.Second)
var res []string
for iter.Next() {
var of ObservedFolder

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