Compare commits

...
Author SHA1 Message Date
Jakob Borg 240e7b0835 Log/error fields changed name 2015-10-12 14:18:53 +09:00
Jakob Borg baf5191433 Add mateon1 2015-10-12 14:13:38 +09:00
Jakob Borg 2645e87766 Errors may now be null, and that's fine 2015-10-12 10:12:57 +09:00
Jakob Borg ec8bc02d33 Silence spurious debug (fixes #2369) 2015-10-12 10:11:58 +09:00
Jakob Borg c1c41242bb Merge pull request #2362 from AudriusButkevicius/sleepysleep
Make puller pause configurable
2015-10-10 20:13:10 +09:00
Audrius Butkevicius 169ff73d26 Merge pull request #2365 from mateon1/master
Change Out of Sync message in folder details (fixes #2364)
2015-10-10 11:37:24 +01:00
Audrius Butkevicius d985ed553a Make puller pause configurable 2015-10-10 11:36:09 +01:00
Mateon1 dea1ef24d9 Change Out of Sync message in folder details (fixes #2364) 2015-10-09 01:34:11 +02:00
Jakob Borg 49f29a0453 Merge pull request #2355 from calmh/trace3
New debug logging infrastructure
2015-10-05 21:01:37 +09:00
Jakob Borg 76af9ba53d Implement facility based logger, debugging via REST API
This implements a new debug/trace infrastructure based on a slightly
hacked up logger. Instead of the traditional "if debug { ... }" I've
rewritten the logger to have no-op Debugln and Debugf, unless debugging
has been enabled for a given "facility". The "facility" is just a
string, typically a package name.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This removes the configurability of it, as the timeout on one side is
dependent on the send interval on the other side. Do we still need it
configurable?
2015-09-21 08:51:42 +02:00
Audrius Butkevicius 6e126fb97e Merge pull request #8 from syncthing/latency
Connected clients should know their own latency
2015-09-20 12:47:37 +01:00
Jakob Borg 22783d8f6c Connected clients should know their own latency 2015-09-20 13:40:24 +02:00
Jakob Borg 372c96c6b2 Merge pull request #5 from syncthing/info
Tweaks
2015-09-14 16:20:08 +02:00
Audrius Butkevicius 7073b8721a Merge pull request #7 from syncthing/ping
Server should respond to ping
2015-09-14 12:49:12 +01:00
Jakob Borg fccb9c0bf4 Server should respond to ping 2015-09-14 13:46:20 +02:00
Audrius Butkevicius f9f12131ae Drop all sessions when we realize a node has gone away 2015-09-11 22:29:50 +01:00
Audrius Butkevicius 7e0106da0c Tweaks
1. Advertise relay server paramters so that clients could make a decision wether or not to connect
2. Generate certificate if it's not there.
2015-09-11 20:06:14 +01:00
AudriusButkevicius d180bc794b Handle 403 2015-09-07 18:12:18 +01:00
AudriusButkevicius eab5fd5bdd Join relay pool by default 2015-09-07 09:21:23 +01:00
AudriusButkevicius e3ca797dad Receive the invite, otherwise stop blocks, add extra arguments 2015-09-06 20:25:53 +01:00
AudriusButkevicius 11b2815b88 Add a test method, fix nil pointer panic 2015-09-06 18:35:38 +01:00
AudriusButkevicius 541d05df1b Use new method name 2015-09-02 22:02:17 +01:00
AudriusButkevicius c0554c9fbf Use a single socket for relaying 2015-09-02 21:35:52 +01:00
Jakob Borg 84365882de Fix tests 2015-08-28 09:01:21 +02:00
AudriusButkevicius 68c5dcd83d Add CachedSize field 2015-08-26 22:33:03 +01:00
Jakob Borg 7fe1fdd8c7 Improve status reporter 2015-08-20 14:29:57 +02:00
Jakob Borg 37cbe68204 Fix broken connection close 2015-08-20 13:58:07 +02:00
Jakob Borg f76a66fc55 Very basic status service 2015-08-20 12:59:44 +02:00
Jakob Borg d7949aa58e I contribute stuff 2015-08-20 12:33:52 +02:00
Jakob Borg f0c0c5483f Cleaner build 2015-08-20 12:33:11 +02:00
Audrius Butkevicius 388a29bbe2 Merge pull request #17 from syncthing/fuzzing
Add some robustness for failure modes detected by go-fuzz
2015-08-20 09:47:47 +01:00
Audrius Butkevicius 7c6a310179 Fix after package move 2015-08-19 20:49:34 +01:00
Jakob Borg 6a36ec63d7 Empty messages with the compression bit set should be accepted 2015-08-18 08:46:40 +02:00
Jakob Borg 9c8b907ff1 All slice types must have limits
The XDR unmarshaller allocates a []T when it sees a slice type and reads
the expected length, so we must always limit the length in order to
avoid allocating too much memory when encountering corruption.
2015-08-18 08:46:40 +02:00
Jakob Borg f769df16e8 Reject unreasonably large messages
We allocate a []byte to read the message into, so if the header says the
messages is several gigabytes large we may run into trouble. In reality,
a message should never be that large so we impose a limit.
2015-08-18 08:46:40 +02:00
Jakob Borg c6f5075721 Enable testing with go-fuzz 2015-08-18 08:46:40 +02:00
Jakob Borg a693698279 Mend tests 2015-08-09 10:00:28 +02:00
Jakob Borg 4949e3ba41 Merge pull request #16 from syncthing/bufs
Use bytepool for response buffers
2015-08-02 08:08:10 +02:00
Audrius Butkevicius ebcdea63c0 Use sync.Pool for response buffers 2015-08-01 12:21:49 +01:00
Audrius Butkevicius eb29989dff Connection errors are debug errors 2015-07-23 20:53:16 +01:00
Audrius Butkevicius 78ef42daa1 Add ability to lookup relay status 2015-07-22 22:34:05 +01:00
Audrius Butkevicius 22e24fc387 Merge pull request #15 from syncthing/conflictwins
Base for better conflict resolution
2015-07-21 14:45:17 +01:00
Jakob Borg 516d88b072 Base for better conflict resolution 2015-07-21 15:40:02 +02:00
Audrius Butkevicius 77457e91e9 Merge pull request #1 from syncthing/review
Code review
2015-07-20 18:43:31 +01:00
Jakob Borg 049d92b525 Style and minor fixes, client package 2015-07-20 14:04:34 +02:00
Jakob Borg f9bd59f031 Style and minor fixes, main package 2015-07-20 14:04:34 +02:00
Audrius Butkevicius 6d0d5bd566 Merge pull request #2 from syncthing/ratelimit
Implement global and per session rate limiting
2015-07-20 12:57:55 +01:00
Jakob Borg 35d20a19bc Implement global and per session rate limiting 2015-07-20 13:37:11 +02:00
Jakob Borg dab1c4cfc9 Build script from discosrv 2015-07-20 12:11:06 +02:00
Audrius Butkevicius f86946c6df Fix bugs 2015-07-17 22:04:02 +01:00
Audrius Butkevicius e97f75cad5 Change receiver type, add GoStringer 2015-07-17 21:49:45 +01:00
Audrius Butkevicius 2505f82ce5 General cleanup 2015-07-17 20:17:49 +01:00
Audrius Butkevicius 7996ef0d45 Merge pull request #14 from syncthing/clusterconfigrace
Connection now needs explicit Start()
2015-07-10 08:31:57 +01:00
Jakob Borg b05c1a5bb9 Connection now needs explicit Start() 2015-07-10 16:40:39 +10:00
Audrius Butkevicius 9dd6f848bd Name the folder in error messages 2015-07-09 22:38:21 +01:00
Audrius Butkevicius e1959afb6b Change EOL 2015-06-28 21:18:38 +01:00
Audrius Butkevicius c68c78d412 Do scheme validation in the client 2015-06-28 20:34:28 +01:00
Audrius Butkevicius b72d31f87f Progress 2015-06-28 19:57:13 +01:00
Audrius Butkevicius 95e15c95f2 Merge pull request #13 from syncthing/timeout
Expose timeouts in protocol
2015-06-28 11:39:51 +01:00
Audrius Butkevicius 9f871a3726 Expose timeouts in protocol 2015-06-27 11:07:44 +01:00
Audrius Butkevicius e19e2c123e Merge pull request #12 from syncthing/ccreq
Enforce ClusterConfiguration at start, then no ordering
2015-06-26 14:44:56 +01:00
Jakob Borg cbe44e1fff Enforce ClusterConfiguration at start, then no ordering 2015-06-26 15:38:56 +02:00
Audrius Butkevicius 8e191c8e6b Add initial code 2015-06-24 15:02:23 +01:00
Audrius Butkevicius 19d742b9e4 Initial commit 2015-06-24 00:34:16 +01:00
Jakob Borg e7db264803 Extract counter value from vector 2015-04-09 12:51:21 +02:00
Jakob Borg 3d8a71fdb2 Generate with updated XDR package 2015-04-08 14:46:08 +02:00
Jakob Borg 6277c0595c Merge pull request #3 from syncthing/changes
Changes for temp indexes
2015-03-26 22:31:45 +01:00
Audrius Butkevicius aa9eda1979 Add IndexTemporary and RequestTemporary flags 2015-03-26 21:30:20 +00:00
Audrius Butkevicius 1d76efcbcd Remove duplication 2015-03-26 21:30:19 +00:00
Audrius Butkevicius 34c2c1ec16 Send and receive Request error codes 2015-03-26 21:30:19 +00:00
Audrius Butkevicius bf7fea9a0a Rename error to code, update xdr path 2015-03-26 21:30:19 +00:00
Audrius Butkevicius fdf15f3ca3 Flag checking is now responsibility of the model 2015-03-26 21:30:18 +00:00
Audrius Butkevicius 1cb5875b20 Expose flags, options in Index{,Update} 2015-03-26 21:30:18 +00:00
Audrius Butkevicius 1a59a5478f Expose hash, flags, options in Request 2015-03-26 21:30:17 +00:00
Audrius Butkevicius 5750443371 Merge pull request #10 from syncthing/vv
Implement version vectors
2015-03-26 13:25:04 +00:00
Jakob Borg f9132cae85 Implement version vectors 2015-03-25 22:49:53 +01:00
Audrius Butkevicius 17149741a7 Merge pull request #9 from syncthing/flags
Add flags and options for future extensibility
2015-03-25 21:04:14 +00:00
Jakob Borg d2ec40bb67 Add flags and options for future extensibility 2015-03-25 21:20:04 +01:00
Jakob Borg 1a4398cc55 Tests should actually pass 2015-03-11 21:11:43 +01:00
Audrius Butkevicius 529d91fb9d Merge pull request #7 from syncthing/compression
Add more fine grained compression control
2015-03-11 19:36:02 +00:00
Jakob Borg 108b4e2e10 Add more fine grained compression control 2015-03-11 19:09:58 +01:00
Jakob Borg cd0cce4195 gofmt 2015-03-09 21:21:20 +01:00
Jakob Borg 9f1a72ec88 Merge pull request #6 from syncthing/limit
Remove 64 folder limit
2015-03-09 21:02:07 +01:00
Audrius Butkevicius a10c621e33 Remove 64 folder limit 2015-03-08 16:55:01 +00:00
Jakob Borg 2e2d479103 Merge pull request #5 from syncthing/iota
We are not using iota
2015-02-15 09:40:19 +01:00
Audrius Butkevicius 35f0e355bf We are not using iota 2015-02-12 21:59:33 +00:00
Jakob Borg 4395711d26 Merge pull request #4 from syncthing/allflags
Add FlagsAll bit mask
2015-02-09 15:45:05 +01:00
Audrius Butkevicius aba915037f Add FlagsAll bit mask 2015-02-01 16:33:52 +00:00
Audrius Butkevicius 442e93d3fc Merge pull request #2 from syncthing/integers
Integer type policy
2015-01-19 20:40:17 +00:00
Jakob Borg 3450b5f80c Integer type policy
Integers are for numbers, enabling arithmetic like subtractions and for
loops without getting shot in the foot. Unsigneds are for bitfields.

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

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

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

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

- "int8", "int16" and plain "uint" are almost never useful.
2015-01-18 02:13:25 +01:00
Jakob Borg f76b5d8002 rm '.gitignore' 2015-01-13 13:47:16 +01:00
Audrius Butkevicius 7e54868206 Merge pull request #1 from syncthing/mit
Relicense as MIT
2015-01-13 12:38:13 +00:00
Jakob Borg d84a8e6404 Relicense as MIT 2015-01-13 13:31:14 +01:00
Jakob Borg 4833b6085c The luhn package moved 2015-01-13 13:20:29 +01:00
Jakob Borg 2ceaca8828 Add documentation copied from Syncthing 2015-01-13 13:09:59 +01:00
Jakob Borg d02158c0ef Also filter out some other obviously invalid filenames (ref #1243) 2015-01-13 12:28:35 +01:00
Jakob Borg 6213c4f2cd Remove nil filenames from database and indexes (fixes #1243) 2015-01-13 09:20:14 +01:00
Jakob Borg cd34eea017 Reject Index and Request messages with unexpected flags 2015-01-11 13:29:01 +01:00
Jakob Borg 7a0a702ec0 Refactor readerLoop to switch on message type directly 2015-01-11 13:24:56 +01:00
Jakob Borg d9ed8e125e Move FileInfoTruncated to files package
This is where it's used, and it clarifies that it's never used over the
wire.
2015-01-09 08:28:24 +01:00
Jakob Borg 36708a5067 Move FileIntf to files package, expose Iterator type
This is where FileIntf is used, so it should be defined here (it's not
a protocol thing, really).
2015-01-09 08:18:42 +01:00
Jakob Borg 8c32955da1 Actually close connection based on unknown protocol version 2015-01-08 22:11:26 +01:00
Jakob Borg c111ed4b20 Add fields for future extensibility
This adds a number of fields to the end of existing messages. This is a
backwards compatible change.
2015-01-08 22:11:26 +01:00
Jakob Borg a3ea9427d1 Ensure backwards compatibility before modifying protocol
This change makes sure that things work smoothly when "we" are a newer
version than our peer and have more fields in our messages than they do.
Missing fields will be left at zero/nil.

(The other side will ignore our extra fields, for the same effect.)
2015-01-08 14:25:11 +01:00
Audrius ButkeviciusandJakob Borg 09b534b8a3 Add job queue (fixes #629)
Request to terminate currently ongoing downloads and jump to the bumped file
incoming in 3, 2, 1.

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

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

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

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

My ideal scenario would be to check if copyChan is writeable, pop job from the
queue and shove it down handleFile. This way jobs would stay in the queue while
they cannot be handled, meaning that the `Bump` could bring your file up higher.
2015-01-02 15:33:39 +01:00
Jakob Borg 190c61ba2f Use Go 1.4 'generate' to create XDR codec 2014-12-06 14:23:10 +01:00
Jakob Borg dc71ec734d Dependency update, new golang.org/x package names 2014-11-30 00:17:00 +01:00
Audrius Butkevicius 3af96e50bd Use custom structure for /need calls (fixes #1001)
Also, remove trimming by number of blocks as this no longer affects the size
of the response.
2014-11-23 00:52:48 +00:00
Audrius ButkeviciusandJakob Borg ddc56c8a0d Add symlink support at the protocol level 2014-11-20 16:32:00 +01:00
Audrius ButkeviciusandJakob Borg e0da2764c9 Code smell 2014-11-20 16:32:00 +01:00
Jakob Borg ad29093ac1 Use more inclusive copyright header 2014-11-17 12:54:42 +01:00
Jakob Borg 28610a9a42 Break out logger as a reusable component 2014-10-26 13:16:54 +01:00
Jakob Borg 65eb528e2d Update xdr; handle marshalling errors 2014-10-21 09:20:14 +02:00
Audrius Butkevicius ec9d68960f Remove 64 device limit 2014-10-20 21:46:53 +01:00
Audrius ButkeviciusandJakob Borg c618eba9a9 Implement BlockMap 2014-10-16 12:26:27 +02:00
Jakob Borg 1ef8378a30 FileInfoTruncated.String() for stindex' benefit 2014-10-16 09:26:24 +02:00
Jakob Borg 43289103cb Relicense to GPL 2014-10-01 07:53:59 +02:00
Audrius Butkevicius 1bc5632771 Run go fmt -w 2014-09-28 14:23:08 +01:00
Audrius Butkevicius 4b488a2d28 Rename Repository -> Folder, Node -> Device (fixes #739) 2014-09-28 14:23:07 +01:00
Jakob Borg f28367bcfc Move top level packages to internal. 2014-09-27 09:42:10 +02:00
195 changed files with 2371 additions and 2172 deletions
+3
View File
@@ -1,6 +1,7 @@
# This is the official list of Syncthing authors for copyright purposes.
Aaron Bieber <qbit@deftly.net>
Adam Piggott <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
Alexander Graf <register-github@alex-graf.de>
Andrew Dunham <andrew@du.nham.ca>
Antony Male <antony.male@gmail.com>
@@ -47,6 +48,8 @@ Lord Landon Agahnim <lordlandon@gmail.com>
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
Marc Pujol <kilburn@la3.org>
Marcin Dziadus <dziadus.marcin@gmail.com>
Mateon1 <matin1111@wp.pl>
Matt Burke <mburke@amplify.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com>
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
+4 -19
View File
@@ -13,10 +13,6 @@
"ImportPath": "github.com/calmh/du",
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24"
},
{
"ImportPath": "github.com/calmh/logger",
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
},
{
"ImportPath": "github.com/calmh/luhn",
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
@@ -37,25 +33,14 @@
"ImportPath": "github.com/kardianos/osext",
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5"
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "f91191218b192ace841c878f161832d19c09145a"
},
{
"ImportPath": "github.com/syncthing/relaysrv/client",
"Rev": "6e126fb97e2ff566d35f8d8824e86793d22b2147"
},
{
"ImportPath": "github.com/syncthing/relaysrv/protocol",
"Rev": "6e126fb97e2ff566d35f8d8824e86793d22b2147"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "1a9d62f03ea92815b46fcaab357cfd4df264b1a0"
},
{
"ImportPath": "github.com/thejerf/suture",
"Rev": "860b44045335c64a6d54ac7eed22a3aedfc687c9"
"Comment": "v1.0.1",
"Rev": "99c1f2d613756768fc4299acd9dc621e11ed3fd7"
},
{
"ImportPath": "github.com/vitrun/qart/coding",
@@ -79,11 +64,11 @@
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "66f0418ca41253f8d1a024eb9754e9441a8e79b9"
"Rev": "db8e4de5b2d6653f66aea53094624468caad15d2"
},
{
"ImportPath": "golang.org/x/net/ipv6",
"Rev": "66f0418ca41253f8d1a024eb9754e9441a8e79b9"
"Rev": "db8e4de5b2d6653f66aea53094624468caad15d2"
},
{
"ImportPath": "golang.org/x/text/transform",
-15
View File
@@ -1,15 +0,0 @@
logger
======
A small wrapper around `log` to provide log levels.
Documentation
-------------
http://godoc.org/github.com/calmh/logger
License
-------
MIT
-187
View File
@@ -1,187 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// Package logger implements a standardized logger with callback functionality
package logger
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"sync"
)
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelVerbose
LevelInfo
LevelOK
LevelWarn
LevelFatal
NumLevels
)
// A MessageHandler is called with the log level and message text.
type MessageHandler func(l LogLevel, msg string)
type Logger struct {
logger *log.Logger
handlers [NumLevels][]MessageHandler
mut sync.Mutex
}
// The default logger logs to standard output with a time prefix.
var DefaultLogger = New()
func New() *Logger {
if os.Getenv("LOGGER_DISCARD") != "" {
// Hack to completely disable logging, for example when running benchmarks.
return &Logger{
logger: log.New(ioutil.Discard, "", 0),
}
}
return &Logger{
logger: log.New(os.Stdout, "", log.Ltime),
}
}
// AddHandler registers a new MessageHandler to receive messages with the
// specified log level or above.
func (l *Logger) AddHandler(level LogLevel, h MessageHandler) {
l.mut.Lock()
defer l.mut.Unlock()
l.handlers[level] = append(l.handlers[level], h)
}
// See log.SetFlags
func (l *Logger) SetFlags(flag int) {
l.logger.SetFlags(flag)
}
// See log.SetPrefix
func (l *Logger) SetPrefix(prefix string) {
l.logger.SetPrefix(prefix)
}
func (l *Logger) callHandlers(level LogLevel, s string) {
for _, h := range l.handlers[level] {
h(level, strings.TrimSpace(s))
}
}
// Debugln logs a line with a DEBUG prefix.
func (l *Logger) Debugln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "DEBUG: "+s)
l.callHandlers(LevelDebug, s)
}
// Debugf logs a formatted line with a DEBUG prefix.
func (l *Logger) Debugf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "DEBUG: "+s)
l.callHandlers(LevelDebug, s)
}
// Infoln logs a line with a VERBOSE prefix.
func (l *Logger) Verboseln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "VERBOSE: "+s)
l.callHandlers(LevelVerbose, s)
}
// Infof logs a formatted line with a VERBOSE prefix.
func (l *Logger) Verbosef(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "VERBOSE: "+s)
l.callHandlers(LevelVerbose, s)
}
// Infoln logs a line with an INFO prefix.
func (l *Logger) Infoln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "INFO: "+s)
l.callHandlers(LevelInfo, s)
}
// Infof logs a formatted line with an INFO prefix.
func (l *Logger) Infof(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "INFO: "+s)
l.callHandlers(LevelInfo, s)
}
// Okln logs a line with an OK prefix.
func (l *Logger) Okln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "OK: "+s)
l.callHandlers(LevelOK, s)
}
// Okf logs a formatted line with an OK prefix.
func (l *Logger) Okf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "OK: "+s)
l.callHandlers(LevelOK, s)
}
// Warnln logs a formatted line with a WARNING prefix.
func (l *Logger) Warnln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "WARNING: "+s)
l.callHandlers(LevelWarn, s)
}
// Warnf logs a formatted line with a WARNING prefix.
func (l *Logger) Warnf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "WARNING: "+s)
l.callHandlers(LevelWarn, s)
}
// Fatalln logs a line with a FATAL prefix and exits the process with exit
// code 1.
func (l *Logger) Fatalln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}
// Fatalf logs a formatted line with a FATAL prefix and exits the process with
// exit code 1.
func (l *Logger) Fatalf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}
-58
View File
@@ -1,58 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
package logger
import (
"strings"
"testing"
)
func TestAPI(t *testing.T) {
l := New()
l.SetFlags(0)
l.SetPrefix("testing")
debug := 0
l.AddHandler(LevelDebug, checkFunc(t, LevelDebug, "test 0", &debug))
info := 0
l.AddHandler(LevelInfo, checkFunc(t, LevelInfo, "test 1", &info))
warn := 0
l.AddHandler(LevelWarn, checkFunc(t, LevelWarn, "test 2", &warn))
ok := 0
l.AddHandler(LevelOK, checkFunc(t, LevelOK, "test 3", &ok))
l.Debugf("test %d", 0)
l.Debugln("test", 0)
l.Infof("test %d", 1)
l.Infoln("test", 1)
l.Warnf("test %d", 2)
l.Warnln("test", 2)
l.Okf("test %d", 3)
l.Okln("test", 3)
if debug != 2 {
t.Errorf("Debug handler called %d != 2 times", debug)
}
if info != 2 {
t.Errorf("Info handler called %d != 2 times", info)
}
if warn != 2 {
t.Errorf("Warn handler called %d != 2 times", warn)
}
if ok != 2 {
t.Errorf("Ok handler called %d != 2 times", ok)
}
}
func checkFunc(t *testing.T, expectl LogLevel, expectmsg string, counter *int) func(LogLevel, string) {
return func(l LogLevel, msg string) {
*counter++
if l != expectl {
t.Errorf("Incorrect message level %d != %d", l, expectl)
}
if !strings.HasSuffix(msg, expectmsg) {
t.Errorf("%q does not end with %q", msg, expectmsg)
}
}
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright (C) 2014 The Protocol Authors.
package protocol
import (
"os"
"strings"
"github.com/calmh/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "protocol") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)
-15
View File
@@ -1,15 +0,0 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
package client
import (
"os"
"strings"
"github.com/calmh/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "relay") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)
+11
View File
@@ -37,3 +37,14 @@ easily fit into the OTP paradigm. It ought to someday be considered a good
idea to distribute libraries that provide some sort of supervisor tree
functionality out of the box. It is possible to provide this functionality
without explicitly depending on the Suture library.
Changelog
---------
suture uses semantic versioning.
1. 1.0.0
* Initial release.
2. 1.0.1
* Fixed data race on the .state variable.
+23 -4
View File
@@ -59,6 +59,7 @@ import (
"log"
"math"
"runtime"
"sync"
"sync/atomic"
"time"
)
@@ -123,7 +124,6 @@ type Supervisor struct {
lastFail time.Time
failures float64
restartQueue []serviceID
state uint8
serviceCounter serviceID
control chan supervisorMessage
resumeTimer <-chan time.Time
@@ -143,6 +143,9 @@ type Supervisor struct {
// a minimal chunk.
getNow func() time.Time
getResume func(time.Duration) <-chan time.Time
sync.Mutex
state uint8
}
// Spec is used to pass arguments to the New function to create a
@@ -373,6 +376,7 @@ func (s *Supervisor) Add(service Service) ServiceToken {
supervisor.logBackoff = s.logBackoff
}
s.Lock()
if s.state == notRunning {
id := s.serviceCounter
s.serviceCounter++
@@ -380,8 +384,10 @@ func (s *Supervisor) Add(service Service) ServiceToken {
s.services[id] = service
s.restartQueue = append(s.restartQueue, id)
s.Unlock()
return ServiceToken{uint64(s.id)<<32 | uint64(id)}
}
s.Unlock()
response := make(chan serviceID)
s.control <- addService{service, response}
@@ -408,16 +414,19 @@ func (s *Supervisor) Serve() {
}
defer func() {
s.Lock()
s.state = notRunning
s.Unlock()
}()
s.Lock()
if s.state != notRunning {
// FIXME: Don't explain why I don't need a semaphore, just use one
// This doesn't use a semaphore because it's just a sanity check.
s.Unlock()
panic("Running a supervisor while it is already running?")
}
s.state = normal
s.Unlock()
// for all the services I currently know about, start them
for _, id := range s.restartQueue {
@@ -472,7 +481,9 @@ func (s *Supervisor) Serve() {
// excessive thrashing
// FIXME: Ought to permit some spacing of these functions, rather
// than simply hammering through them
s.Lock()
s.state = normal
s.Unlock()
s.failures = 0
s.logBackoff(s, false)
for _, id := range s.restartQueue {
@@ -499,7 +510,9 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
}
if s.failures > s.failureThreshold {
s.Lock()
s.state = paused
s.Unlock()
s.logBackoff(s, true)
s.resumeTimer = s.getResume(s.failureBackoff)
}
@@ -511,7 +524,13 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
// It is possible for a service to be no longer monitored
// by the time we get here. In that case, just ignore it.
if monitored {
if s.state == normal {
// this may look dangerous because the state could change, but this
// code is only ever run in the one goroutine that is permitted to
// change the state, so nothing else will.
s.Lock()
curState := s.state
s.Unlock()
if curState == normal {
s.runService(failedService, id)
s.logFailure(s, failedService, s.failures, s.failureThreshold, true, err, stacktrace)
} else {
+1 -1
View File
@@ -17,7 +17,7 @@ func (i *Incrementor) Serve() {
for {
select {
case i.next <- i.current:
i.current += 1
i.current++
case <-i.stop:
// We sync here just to guarantee the output of "Stopping the service",
// so this passes the test reliably.
+20 -4
View File
@@ -478,6 +478,22 @@ func TestNilSupervisorAdd(t *testing.T) {
s.Add(s)
}
// https://github.com/thejerf/suture/issues/11
//
// The purpose of this test is to verify that it does not cause data races,
// so there are no obvious assertions.
func TestIssue11(t *testing.T) {
t.Parallel()
s := NewSimple("main")
s.ServeBackground()
subsuper := NewSimple("sub")
s.Add(subsuper)
subsuper.Add(NewService("may cause data race"))
}
// http://golangtutorials.blogspot.com/2011/10/gotest-unit-testing-and-benchmarking-go.html
// claims test function are run in the same order as the source file...
// I'm not sure if this is part of the contract, though. Especially in the
@@ -509,7 +525,7 @@ func (s *FailableService) Serve() {
everMultistarted = true
panic("Multi-started the same service! " + s.name)
}
s.existing += 1
s.existing++
s.started <- true
@@ -522,13 +538,13 @@ func (s *FailableService) Serve() {
case Happy:
// Do nothing on purpose. Life is good!
case Fail:
s.existing -= 1
s.existing--
if useStopChan {
s.stop <- true
}
return
case Panic:
s.existing -= 1
s.existing--
panic("Panic!")
case Hang:
// or more specifically, "hang until I release you"
@@ -537,7 +553,7 @@ func (s *FailableService) Serve() {
useStopChan = true
}
case <-s.shutdown:
s.existing -= 1
s.existing--
if useStopChan {
s.stop <- true
}
+3
View File
@@ -18,6 +18,7 @@ brbecker <brbecker@gmail.com>
brendanlong <self@brendanlong.com>
brgmnn <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
bsidhom <bsidhom@gmail.com>
burkemw3 <mburke@amplify.com>
calmh <jakob@nym.se>
canton7 <antony.male@gmail.com>
cdata <chris@scriptolo.gy>
@@ -41,6 +42,7 @@ kozec <kozec@kozec.com>
krozycki <rozycki.karol@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
marclaporte <marc@marclaporte.com>
mateon1 <matin1111@wp.pl>
mogwa1 <devriesb@gmail.com>
moshen <moshen.colin@gmail.com>
mvdan <mvdan@mvdan.cc>
@@ -56,6 +58,7 @@ rumpelsepp <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
sciurius <jvromans@squirrel.nl>
seehuhn <voss@seehuhn.de>
snnd <dw@risu.io>
simplypeachy <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
timabell <tim@timwise.co.uk>
tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
+20 -22
View File
@@ -1,5 +1,4 @@
Syncthing
=========
# Syncthing
[![Latest Build (Official)](https://img.shields.io/jenkins/s/http/build.syncthing.net/syncthing.svg?style=flat-square)](http://build.syncthing.net/job/syncthing/lastBuild/)
[![AppVeyor Build](https://img.shields.io/appveyor/ci/calmh/syncthing/master.svg?style=flat-square)](https://ci.appveyor.com/project/calmh/syncthing)
@@ -11,8 +10,7 @@ This is the Syncthing project which pursues the following goals:
1. Define a protocol for synchronization of a folder between a number of
collaborating devices. This protocol should be well defined, unambiguous,
easily understood, free to use, efficient, secure and language neutral.
This is called the [Block Exchange
Protocol](https://github.com/syncthing/specs/blob/master/BEPv1.md).
This is called the [Block Exchange Protocol][1].
2. Provide the reference implementation to demonstrate the usability of
said protocol. This is the `syncthing` utility. We hope that
@@ -22,38 +20,38 @@ The two are evolving together; the protocol is not to be considered
stable until Syncthing 1.0 is released, at which point it is locked down
for incompatible changes.
Getting Started
---------------
## Getting Started
Take a look at the [getting started
guide](http://docs.syncthing.net/intro/getting-started.html).
Take a look at the [getting started guide][2].
There are a few examples for keeping Syncthing running in the background
on your system in [the etc directory](https://github.com/syncthing/syncthing/blob/master/etc).
on your system in [the etc directory][3].
There is an IRC channel, `#syncthing` on Freenode, for talking directly
There is an IRC channel, `#syncthing` on [Freenode][4], for talking directly
to developers and users.
Building
--------
## Building
Building Syncthing from source is easy, and there's a
[guide](http://docs.syncthing.net/dev/building.html).
Building Syncthing from source is easy, and there's a [guide][5].
that describes it for both Unix and Windows systems.
Signed Releases
---------------
## Signed Releases
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
with the key D26E6ED000654A3E (see https://syncthing.net/security.html).
For release binaries, MD5 and SHA1 checksums are calculated and signed,
available in the md5sum.txt.asc and sha1sum.txt.asc files.
Documentation
=============
## Documentation
Please see the [Syncthing
documentation site](http://docs.syncthing.net/).
Please see the [Syncthing documentation site][6].
All code is licensed under the
[MPLv2 License](https://github.com/syncthing/syncthing/blob/master/LICENSE).
All code is licensed under the [MPLv2 License][7].
[1]: https://github.com/syncthing/specs/blob/master/BEPv1.md
[2]: http://docs.syncthing.net/intro/getting-started.html
[3]: https://github.com/syncthing/syncthing/blob/master/etc
[4]: https://webchat.freenode.net/
[5]: http://docs.syncthing.net/dev/building.html
[6]: http://docs.syncthing.net/
[7]: https://github.com/syncthing/syncthing/blob/master/LICENSE
+1 -1
View File
@@ -401,7 +401,7 @@ func assets() {
}
func xdr() {
runPrint("go", "generate", "./lib/discover", "./lib/db")
runPrint("go", "generate", "./lib/discover", "./lib/db", "./lib/protocol")
}
func translate() {
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"os"
"path/filepath"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
)
+1 -1
View File
@@ -15,9 +15,9 @@ import (
"os"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/protocol"
)
var timeout = 5 * time.Second
+1 -1
View File
@@ -13,8 +13,8 @@ import (
"log"
"os"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
)
+17 -3
View File
@@ -9,10 +9,24 @@ package main
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
debugHTTP = strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all"
debugSuture = strings.Contains(os.Getenv("STTRACE"), "suture") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger.NewFacility("main", "Main package")
httpl = logger.DefaultLogger.NewFacility("http", "REST API")
)
func init() {
l.SetDebug("main", strings.Contains(os.Getenv("STTRACE"), "main") || os.Getenv("STTRACE") == "all")
l.SetDebug("http", strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all")
}
func shouldDebugMain() bool {
return l.ShouldDebug("main")
}
func shouldDebugHTTP() bool {
return l.ShouldDebug("http")
}
+100 -50
View File
@@ -20,19 +20,20 @@ import (
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/calmh/logger"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/auto"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
@@ -41,21 +42,14 @@ import (
"golang.org/x/crypto/bcrypt"
)
type guiError struct {
Time time.Time `json:"time"`
Error string `json:"error"`
}
var (
configInSync = true
guiErrors = []guiError{}
guiErrorsMut = sync.NewMutex()
startTime = time.Now()
)
type apiSvc struct {
id protocol.DeviceID
cfg config.GUIConfiguration
cfg *config.Wrapper
assetDir string
model *model.Model
eventSub *events.BufferedSubscription
@@ -65,9 +59,12 @@ type apiSvc struct {
fss *folderSummarySvc
stop chan struct{}
systemConfigMut sync.Mutex
guiErrors *logger.Recorder
systemLog *logger.Recorder
}
func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc) (*apiSvc, error) {
func newAPISvc(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) (*apiSvc, error) {
svc := &apiSvc{
id: id,
cfg: cfg,
@@ -77,14 +74,21 @@ func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir strin
discoverer: discoverer,
relaySvc: relaySvc,
systemConfigMut: sync.NewMutex(),
guiErrors: errors,
systemLog: systemLog,
}
var err error
svc.listener, err = svc.getListener(cfg)
svc.listener, err = svc.getListener(cfg.GUI())
return svc, err
}
func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error) {
if guiAddress != "" {
// Override from the environment
cfg.Address = guiAddress
}
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
@@ -133,8 +137,6 @@ func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error)
func (s *apiSvc) Serve() {
s.stop = make(chan struct{})
l.AddHandler(logger.LevelWarn, s.showGuiError)
// The GET handlers
getRestMux := http.NewServeMux()
getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
@@ -159,6 +161,9 @@ func (s *apiSvc) Serve() {
getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
// The POST handlers
postRestMux := http.NewServeMux()
@@ -176,6 +181,7 @@ func (s *apiSvc) Serve() {
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
// Debug endpoints, not for general use
getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
@@ -195,33 +201,37 @@ func (s *apiSvc) Serve() {
assets: auto.Assets(),
})
guiCfg := s.cfg.GUI()
if guiAPIKey != "" {
// Override from the environment
guiCfg.APIKey = guiAPIKey
}
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler := csrfMiddleware(s.id.String()[:5], "/rest", s.cfg.APIKey, mux)
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey, mux)
// Add our version and ID as a header to responses
handler = withDetailsMiddleware(s.id, handler)
// Wrap everything in basic auth, if user/password is set.
if len(s.cfg.User) > 0 && len(s.cfg.Password) > 0 {
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], s.cfg, handler)
if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
}
// Redirect to HTTPS if we are supposed to
if s.cfg.UseTLS {
if guiCfg.UseTLS {
handler = redirectToHTTPSMiddleware(handler)
}
if debugHTTP {
handler = debugMiddleware(handler)
}
handler = debugMiddleware(handler)
srv := http.Server{
Handler: handler,
ReadTimeout: 10 * time.Second,
}
s.fss = newFolderSummarySvc(s.model)
s.fss = newFolderSummarySvc(s.cfg, s.model)
defer s.fss.Stop()
s.fss.ServeBackground()
@@ -273,7 +283,6 @@ func (s *apiSvc) CommitConfiguration(from, to config.Configuration) bool {
// method.
return false
}
s.cfg = to.GUI
close(s.stop)
@@ -369,6 +378,36 @@ func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
})
}
func (s *apiSvc) getSystemDebug(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
names := l.Facilities()
enabled := l.FacilityDebugging()
sort.Strings(enabled)
json.NewEncoder(w).Encode(map[string]interface{}{
"facilities": names,
"enabled": enabled,
})
}
func (s *apiSvc) postSystemDebug(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
q := r.URL.Query()
for _, f := range strings.Split(q.Get("enable"), ",") {
if f == "" {
continue
}
l.SetDebug(f, true)
l.Infof("Enabled debug data for %q", f)
}
for _, f := range strings.Split(q.Get("disable"), ",") {
if f == "" {
continue
}
l.SetDebug(f, false)
l.Infof("Disabled debug data for %q", f)
}
}
func (s *apiSvc) getDBBrowse(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
@@ -409,12 +448,12 @@ func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getDBStatus(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
res := folderSummary(s.model, folder)
res := folderSummary(s.cfg, s.model, folder)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
func folderSummary(m *model.Model, folder string) map[string]interface{} {
func folderSummary(cfg *config.Wrapper, m *model.Model, folder string) map[string]interface{} {
var res = make(map[string]interface{})
res["invalid"] = cfg.Folders()[folder].Invalid
@@ -524,7 +563,7 @@ func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getSystemConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(cfg.Raw())
json.NewEncoder(w).Encode(s.cfg.Raw())
}
func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
@@ -539,7 +578,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
return
}
if to.GUI.Password != cfg.GUI().Password {
if to.GUI.Password != s.cfg.GUI().Password {
if to.GUI.Password != "" {
hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
if err != nil {
@@ -554,7 +593,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
// Fixup usage reporting settings
if curAcc := cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
// UR was enabled
to.Options.URAccepted = usageReportVersion
to.Options.URUniqueID = randomString(8)
@@ -566,9 +605,9 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
// Activate and save
resp := cfg.Replace(to)
resp := s.cfg.Replace(to)
configInSync = !resp.RequiresRestart
cfg.Save()
s.cfg.Save()
}
func (s *apiSvc) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
@@ -586,7 +625,7 @@ func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
folder := qs.Get("folder")
if len(folder) > 0 {
if _, ok := cfg.Folders()[folder]; !ok {
if _, ok := s.cfg.Folders()[folder]; !ok {
http.Error(w, "Invalid folder ID", 500)
return
}
@@ -594,7 +633,7 @@ func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
if len(folder) == 0 {
// Reset all folders.
for folder := range cfg.Folders() {
for folder := range s.cfg.Folders() {
s.model.ResetFolder(folder)
}
s.flushResponse(`{"ok": "resetting database"}`, w)
@@ -632,7 +671,7 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["alloc"] = m.Alloc
res["sys"] = m.Sys - m.HeapReleased
res["tilde"] = tilde
if cfg.Options().LocalAnnEnabled || cfg.Options().GlobalAnnEnabled {
if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
res["discoveryEnabled"] = true
discoErrors := make(map[string]string)
discoMethods := 0
@@ -674,30 +713,41 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getSystemError(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
guiErrorsMut.Lock()
json.NewEncoder(w).Encode(map[string][]guiError{"errors": guiErrors})
guiErrorsMut.Unlock()
json.NewEncoder(w).Encode(map[string][]logger.Line{
"errors": s.guiErrors.Since(time.Time{}),
})
}
func (s *apiSvc) postSystemError(w http.ResponseWriter, r *http.Request) {
bs, _ := ioutil.ReadAll(r.Body)
r.Body.Close()
s.showGuiError(0, string(bs))
l.Warnln(string(bs))
}
func (s *apiSvc) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
guiErrorsMut.Lock()
guiErrors = []guiError{}
guiErrorsMut.Unlock()
s.guiErrors.Clear()
}
func (s *apiSvc) showGuiError(l logger.LogLevel, err string) {
guiErrorsMut.Lock()
guiErrors = append(guiErrors, guiError{time.Now(), err})
if len(guiErrors) > 5 {
guiErrors = guiErrors[len(guiErrors)-5:]
func (s *apiSvc) getSystemLog(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string][]logger.Line{
"messages": s.systemLog.Since(since),
})
}
func (s *apiSvc) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
for _, line := range s.systemLog.Since(since) {
fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
}
guiErrorsMut.Unlock()
}
func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
@@ -718,7 +768,7 @@ func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
func (s *apiSvc) getReport(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(reportData(s.model))
json.NewEncoder(w).Encode(reportData(s.cfg, s.model))
}
func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
@@ -787,7 +837,7 @@ func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return
}
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
if err != nil {
http.Error(w, err.Error(), 500)
return
@@ -830,7 +880,7 @@ func (s *apiSvc) getLang(w http.ResponseWriter, r *http.Request) {
}
func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
if err != nil {
l.Warnln("getting latest release:", err)
http.Error(w, err.Error(), 500)
@@ -928,7 +978,7 @@ func (s *apiSvc) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
tot := map[string]float64{}
count := map[string]float64{}
for _, folder := range cfg.Folders() {
for _, folder := range s.cfg.Folders() {
for _, device := range folder.DeviceIDs() {
deviceStr := device.String()
if s.model.ConnectedTo(device) {
+1 -4
View File
@@ -25,7 +25,6 @@ var (
)
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
next.ServeHTTP(w, r)
@@ -43,9 +42,7 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
}
}
if debugHTTP {
l.Debugln("Sessionless HTTP request with authentication; this is expensive.")
}
httpl.Debugln("Sessionless HTTP request with authentication; this is expensive.")
error := func() {
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
+176 -182
View File
@@ -7,6 +7,7 @@
package main
import (
"bytes"
"crypto/tls"
"flag"
"fmt"
@@ -21,19 +22,20 @@ import (
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"github.com/calmh/logger"
"github.com/juju/ratelimit"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/symlinks"
"github.com/syncthing/syncthing/lib/tlsutil"
@@ -43,7 +45,6 @@ import (
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/thejerf/suture"
"golang.org/x/crypto/bcrypt"
)
var (
@@ -72,9 +73,17 @@ const (
tlsDefaultCommonName = "syncthing"
tlsRSABits = 3072
pingEventInterval = time.Minute
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
)
var l = logger.DefaultLogger
// The discovery results are sorted by their source priority.
const (
ipv6LocalDiscoveryPriority = iota
ipv4LocalDiscoveryPriority
globalDiscoveryPriority
)
func init() {
if Version != "unknown-dev" {
@@ -107,15 +116,12 @@ func init() {
}
var (
cfg *config.Wrapper
myID protocol.DeviceID
confDir string
logFlags = log.Ltime
writeRateLimit *ratelimit.Bucket
readRateLimit *ratelimit.Bucket
stop = make(chan int)
cert tls.Certificate
lans []*net.IPNet
myID protocol.DeviceID
confDir string
logFlags = log.Ltime
stop = make(chan int)
cert tls.Certificate
lans []*net.IPNet
)
const (
@@ -145,25 +151,11 @@ Development Settings
The following environment variables modify syncthing's behavior in ways that
are mostly useful for developers. Use with care.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in
assets.
STTRACE A comma separated string of facilities to trace. The valid
facility strings are:
- "beacon" (the beacon package)
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "http" (the main package; HTTP requests)
- "locks" (the sync package; trace long held locks)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "suture" (the suture package; service management)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)
facility strings listed below.
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
@@ -186,33 +178,40 @@ are mostly useful for developers. Use with care.
GOGC Percentage of heap growth at which to trigger GC. Default is
100. Lower numbers keep peak memory usage down, at the price
of CPU usage (ie. performance).`
of CPU usage (ie. performance).
Debugging Facilities
--------------------
The following are valid values for the STTRACE variable:
%s`
)
// Command line and environment options
var (
reset bool
showVersion bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
noConsole bool
generateDir string
logFile string
auditEnabled bool
verbose bool
paused bool
noRestart = os.Getenv("STNORESTART") != ""
noUpgrade = os.Getenv("STNOUPGRADE") != ""
guiAddress = os.Getenv("STGUIADDRESS") // legacy
guiAuthentication = os.Getenv("STGUIAUTH") // legacy
guiAPIKey = os.Getenv("STGUIAPIKEY") // legacy
profiler = os.Getenv("STPROFILER")
guiAssets = os.Getenv("STGUIASSETS")
cpuProfile = os.Getenv("STCPUPROFILE") != ""
stRestarting = os.Getenv("STRESTART") != ""
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
reset bool
showVersion bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
noConsole bool
generateDir string
logFile string
auditEnabled bool
verbose bool
paused bool
noRestart = os.Getenv("STNORESTART") != ""
noUpgrade = os.Getenv("STNOUPGRADE") != ""
guiAddress = os.Getenv("STGUIADDRESS") // legacy
guiAPIKey = os.Getenv("STGUIAPIKEY") // legacy
profiler = os.Getenv("STPROFILER")
guiAssets = os.Getenv("STGUIASSETS")
cpuProfile = os.Getenv("STCPUPROFILE") != ""
stRestarting = os.Getenv("STRESTART") != ""
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
)
func main() {
@@ -229,7 +228,6 @@ func main() {
flag.StringVar(&generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&guiAddress, "gui-address", guiAddress, "Override GUI address")
flag.StringVar(&guiAuthentication, "gui-authentication", guiAuthentication, "Override GUI authentication; username:password")
flag.StringVar(&guiAPIKey, "gui-apikey", guiAPIKey, "Override GUI API key")
flag.StringVar(&confDir, "home", "", "Set configuration directory")
flag.IntVar(&logFlags, "logflags", logFlags, "Select information in log line prefix")
@@ -244,7 +242,8 @@ func main() {
flag.BoolVar(&verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&paused, "paused", false, "Start with all devices paused")
flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, baseDirs["config"]))
longUsage := fmt.Sprintf(extraUsage, baseDirs["config"], debugFacilities())
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
if noConsole {
@@ -345,7 +344,11 @@ func main() {
}
if doUpgrade || doUpgradeCheck {
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version)
releasesURL := "https://api.github.com/repos/syncthing/syncthing/releases?per_page=30"
if cfg, _, err := loadConfig(locations[locConfigFile]); err == nil {
releasesURL = cfg.Options().ReleasesURL
}
rel, err := upgrade.LatestRelease(releasesURL, Version)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
@@ -392,6 +395,28 @@ func main() {
}
}
func debugFacilities() string {
facilities := l.Facilities()
// Get a sorted list of names
var names []string
maxLen := 0
for name := range facilities {
names = append(names, name)
if len(name) > maxLen {
maxLen = len(name)
}
}
sort.Strings(names)
// Format the choices
b := new(bytes.Buffer)
for _, name := range names {
fmt.Fprintf(b, " %-*s - %s\n", maxLen, name, facilities[name])
}
return b.String()
}
func upgradeViaRest() error {
cfg, err := config.Load(locations[locConfigFile], protocol.LocalDeviceID)
if err != nil {
@@ -434,9 +459,7 @@ func syncthingMain() {
// We want any logging it does to go through our log system.
mainSvc := suture.New("main", suture.Spec{
Log: func(line string) {
if debugSuture {
l.Debugln(line)
}
l.Debugln(line)
},
})
mainSvc.ServeBackground()
@@ -453,6 +476,9 @@ func syncthingMain() {
mainSvc.Add(newVerboseSvc())
}
errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0)
systemLog := logger.NewRecorder(l, logger.LevelDebug, maxSystemLog, initialSystemLog)
// Event subscription for the API; must start early to catch the early events.
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000)
@@ -496,33 +522,21 @@ func syncthingMain() {
cfgFile := locations[locConfigFile]
var myName string
// Load the configuration file, if it exists.
// If it does not, create a template.
if info, err := os.Stat(cfgFile); err == nil {
if !info.Mode().IsRegular() {
l.Fatalln("Config file is not a file?")
}
cfg, err = config.Load(cfgFile, myID)
if err == nil {
myCfg := cfg.Devices()[myID]
if myCfg.Name == "" {
myName, _ = os.Hostname()
} else {
myName = myCfg.Name
}
cfg, myName, err := loadConfig(cfgFile)
if err != nil {
if os.IsNotExist(err) {
l.Infoln("No config file; starting with empty defaults")
myName, _ = os.Hostname()
newCfg := defaultConfig(myName)
cfg = config.Wrap(cfgFile, newCfg)
cfg.Save()
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
} else {
l.Fatalln("Configuration:", err)
l.Fatalln("Loading config:", err)
}
} else {
l.Infoln("No config file; starting with empty defaults")
myName, _ = os.Hostname()
newCfg := defaultConfig(myName)
cfg = config.Wrap(cfgFile, newCfg)
cfg.Save()
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
}
if cfg.Raw().OriginalVersion != config.CurrentVersion {
@@ -577,13 +591,6 @@ func syncthingMain() {
symlinks.Supported = false
}
if opts.MaxSendKbps > 0 {
writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxSendKbps), int64(5*1000*opts.MaxSendKbps))
}
if opts.MaxRecvKbps > 0 {
readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
}
if (opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0) && !opts.LimitBandwidthInLan {
lans, _ = osutil.GetLans()
networks := make([]string, 0, len(lans))
@@ -602,9 +609,10 @@ func syncthingMain() {
}
dbFile := locations[locDatabase]
ldb, err := leveldb.OpenFile(dbFile, dbOpts())
dbOpts := dbOpts(cfg)
ldb, err := leveldb.OpenFile(dbFile, dbOpts)
if leveldbIsCorrupted(err) {
ldb, err = leveldb.RecoverFile(dbFile, dbOpts())
ldb, err = leveldb.RecoverFile(dbFile, dbOpts)
}
if leveldbIsCorrupted(err) {
// The database is corrupted, and we've tried to recover it but it
@@ -614,7 +622,7 @@ func syncthingMain() {
if err := resetDB(); err != nil {
l.Fatalln("Remove database:", err)
}
ldb, err = leveldb.OpenFile(dbFile, dbOpts())
ldb, err = leveldb.OpenFile(dbFile, dbOpts)
}
if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
@@ -723,7 +731,7 @@ func syncthingMain() {
// Each global discovery server gets its results cached for five
// minutes, and is not asked again for a minute when it's returned
// unsuccessfully.
cachedDiscovery.Add(gd, 5*time.Minute, time.Minute)
cachedDiscovery.Add(gd, 5*time.Minute, time.Minute, globalDiscoveryPriority)
}
}
@@ -733,24 +741,24 @@ func syncthingMain() {
if err != nil {
l.Warnln("IPv4 local discovery:", err)
} else {
cachedDiscovery.Add(bcd, 0, 0)
cachedDiscovery.Add(bcd, 0, 0, ipv4LocalDiscoveryPriority)
}
// v6 multicasts
mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, addrList, relaySvc)
if err != nil {
l.Warnln("IPv6 local discovery:", err)
} else {
cachedDiscovery.Add(mcd, 0, 0)
cachedDiscovery.Add(mcd, 0, 0, ipv6LocalDiscoveryPriority)
}
}
// GUI
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc)
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc, errors, systemLog)
// Start connection management
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg, cachedDiscovery, relaySvc)
connectionSvc := connections.NewConnectionSvc(cfg, myID, m, tlsCfg, cachedDiscovery, relaySvc, bepProtocolName, tlsDefaultCommonName, lans)
mainSvc.Add(connectionSvc)
if cpuProfile {
@@ -786,7 +794,7 @@ func syncthingMain() {
// The usageReportingManager registers itself to listen to configuration
// changes, and there's nothing more we need to tell it from the outside.
// Hence we don't keep the returned pointer.
newUsageReportingManager(m, cfg)
newUsageReportingManager(cfg, m)
if opts.RestartOnWakeup {
go standbyMonitor()
@@ -796,7 +804,7 @@ func syncthingMain() {
if noUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
} else if IsRelease {
go autoUpgrade()
go autoUpgrade(cfg)
} else {
l.Infof("No automatic upgrades; %s is not a release version.", Version)
}
@@ -822,7 +830,30 @@ func syncthingMain() {
os.Exit(code)
}
func dbOpts() *opt.Options {
func loadConfig(cfgFile string) (*config.Wrapper, string, error) {
info, err := os.Stat(cfgFile)
if err != nil {
return nil, "", err
}
if !info.Mode().IsRegular() {
return nil, "", errors.New("configuration is not a file")
}
cfg, err := config.Load(cfgFile, myID)
if err != nil {
return nil, "", err
}
myCfg := cfg.Devices()[myID]
myName := myCfg.Name
if myName == "" {
myName, _ = os.Hostname()
}
return cfg, myName, nil
}
func dbOpts(cfg *config.Wrapper) *opt.Options {
// Calculate a suitable database block cache capacity.
// Default is 8 MiB.
@@ -873,48 +904,53 @@ func startAuditing(mainSvc *suture.Supervisor) {
l.Infoln("Audit log in", auditFile)
}
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc) {
opts := cfg.Options()
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) {
guiCfg := cfg.GUI()
if guiCfg.Enabled && guiCfg.Address != "" {
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
if !guiCfg.Enabled {
return
}
if guiCfg.Address == "" {
return
}
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
if err != nil {
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
} else {
var hostOpen, hostShow string
switch {
case addr.IP == nil:
hostOpen = "localhost"
hostShow = "0.0.0.0"
case addr.IP.IsUnspecified():
hostOpen = "localhost"
hostShow = addr.IP.String()
default:
hostOpen = addr.IP.String()
hostShow = hostOpen
}
var proto = "http"
if guiCfg.UseTLS {
proto = "https"
}
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow)
api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog)
if err != nil {
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
} else {
var hostOpen, hostShow string
switch {
case addr.IP == nil:
hostOpen = "localhost"
hostShow = "0.0.0.0"
case addr.IP.IsUnspecified():
hostOpen = "localhost"
hostShow = addr.IP.String()
default:
hostOpen = addr.IP.String()
hostShow = hostOpen
}
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
var proto = "http"
if guiCfg.UseTLS {
proto = "https"
}
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow)
api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub, discoverer, relaySvc)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
if opts.StartBrowser && !noBrowser && !stRestarting {
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(urlOpen)
}
if cfg.Options().StartBrowser && !noBrowser && !stRestarting {
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(urlOpen)
}
}
}
@@ -1010,48 +1046,6 @@ func getFreePort(host string, ports ...int) (int, error) {
return addr.Port, nil
}
func overrideGUIConfig(cfg config.GUIConfiguration, address, authentication, apikey string) config.GUIConfiguration {
if address != "" {
cfg.Enabled = true
if !strings.Contains(address, "//") {
// Assume just an IP was given. Don't touch he TLS setting.
cfg.Address = address
} else {
parsed, err := url.Parse(address)
if err != nil {
l.Fatalln(err)
}
cfg.Address = parsed.Host
switch parsed.Scheme {
case "http":
cfg.UseTLS = false
case "https":
cfg.UseTLS = true
default:
l.Fatalln("Unknown scheme:", parsed.Scheme)
}
}
}
if authentication != "" {
authenticationParts := strings.SplitN(authentication, ":", 2)
hash, err := bcrypt.GenerateFromPassword([]byte(authenticationParts[1]), 0)
if err != nil {
l.Fatalln("Invalid GUI password:", err)
}
cfg.User = authenticationParts[0]
cfg.Password = string(hash)
}
if apikey != "" {
cfg.APIKey = apikey
}
return cfg
}
func standbyMonitor() {
restartDelay := time.Duration(60 * time.Second)
now := time.Now()
@@ -1072,7 +1066,7 @@ func standbyMonitor() {
}
}
func autoUpgrade() {
func autoUpgrade(cfg *config.Wrapper) {
timer := time.NewTimer(0)
sub := events.Default.Subscribe(events.DeviceConnected)
for {
+1 -1
View File
@@ -10,10 +10,10 @@ import (
"os"
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"syscall"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
)
func init() {
+6 -3
View File
@@ -9,6 +9,7 @@ package main
import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/sync"
@@ -20,6 +21,7 @@ import (
type folderSummarySvc struct {
*suture.Supervisor
cfg *config.Wrapper
model *model.Model
stop chan struct{}
immediate chan string
@@ -33,9 +35,10 @@ type folderSummarySvc struct {
lastEventReqMut sync.Mutex
}
func newFolderSummarySvc(m *model.Model) *folderSummarySvc {
func newFolderSummarySvc(cfg *config.Wrapper, m *model.Model) *folderSummarySvc {
svc := &folderSummarySvc{
Supervisor: suture.NewSimple("folderSummarySvc"),
cfg: cfg,
model: m,
stop: make(chan struct{}),
immediate: make(chan string),
@@ -162,13 +165,13 @@ func (c *folderSummarySvc) foldersToHandle() []string {
func (c *folderSummarySvc) sendSummary(folder string) {
// The folder summary contains how many bytes, files etc
// are in the folder and how in sync we are.
data := folderSummary(c.model, folder)
data := folderSummary(c.cfg, c.model, folder)
events.Default.Log(events.FolderSummary, map[string]interface{}{
"folder": folder,
"summary": data,
})
for _, devCfg := range cfg.Folders()[folder].Devices {
for _, devCfg := range c.cfg.Folders()[folder].Devices {
if devCfg.DeviceID.Equals(myID) {
// We already know about ourselves.
continue
+1 -3
View File
@@ -98,9 +98,7 @@ func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
}
if debugNet {
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
}
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
return extPort
}
+19 -10
View File
@@ -19,9 +19,9 @@ import (
"sort"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/thejerf/suture"
)
@@ -32,12 +32,14 @@ import (
const usageReportVersion = 2
type usageReportingManager struct {
cfg *config.Wrapper
model *model.Model
sup *suture.Supervisor
}
func newUsageReportingManager(m *model.Model, cfg *config.Wrapper) *usageReportingManager {
func newUsageReportingManager(cfg *config.Wrapper, m *model.Model) *usageReportingManager {
mgr := &usageReportingManager{
cfg: cfg,
model: m,
}
@@ -58,9 +60,7 @@ func (m *usageReportingManager) VerifyConfiguration(from, to config.Configuratio
func (m *usageReportingManager) CommitConfiguration(from, to config.Configuration) bool {
if to.Options.URAccepted >= usageReportVersion && m.sup == nil {
// Usage reporting was turned on; lets start it.
svc := &usageReportingService{
model: m.model,
}
svc := newUsageReportingService(m.cfg, m.model)
m.sup = suture.NewSimple("usageReporting")
m.sup.Add(svc)
m.sup.ServeBackground()
@@ -79,7 +79,7 @@ func (m *usageReportingManager) String() string {
// reportData returns the data to be sent in a usage report. It's used in
// various places, so not part of the usageReportingSvc object.
func reportData(m *model.Model) map[string]interface{} {
func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
res := make(map[string]interface{})
res["urVersion"] = usageReportVersion
res["uniqueID"] = cfg.Options().URUniqueID
@@ -238,12 +238,21 @@ func stringIn(needle string, haystack []string) bool {
}
type usageReportingService struct {
cfg *config.Wrapper
model *model.Model
stop chan struct{}
}
func newUsageReportingService(cfg *config.Wrapper, model *model.Model) *usageReportingService {
return &usageReportingService{
cfg: cfg,
model: model,
stop: make(chan struct{}),
}
}
func (s *usageReportingService) sendUsageReport() error {
d := reportData(s.model)
d := reportData(s.cfg, s.model)
var b bytes.Buffer
json.NewEncoder(&b).Encode(d)
@@ -256,12 +265,12 @@ func (s *usageReportingService) sendUsageReport() error {
}
}
if cfg.Options().URPostInsecurely {
if s.cfg.Options().URPostInsecurely {
transp.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
_, err := client.Post(cfg.Options().URURL, "application/json", &b)
_, err := client.Post(s.cfg.Options().URURL, "application/json", &b)
return err
}
@@ -271,7 +280,7 @@ func (s *usageReportingService) Serve() {
l.Infoln("Starting usage reporting")
defer l.Infoln("Stopping usage reporting")
t := time.NewTimer(time.Duration(cfg.Options().URInitialDelayS) * time.Second) // time to initial report at start
t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second) // time to initial report at start
for {
select {
case <-s.stop:
@@ -2,6 +2,7 @@
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=http://docs.syncthing.net/
After=network.target
Wants=syncthing-inotify@.service
[Service]
User=%i
+1
View File
@@ -2,6 +2,7 @@
Description=Syncthing - Open Source Continuous File Synchronization
Documentation=http://docs.syncthing.net/
After=network.target
Wants=syncthing-inotify.service
[Service]
Environment=STNORESTART=yes
+5 -4
View File
@@ -21,6 +21,10 @@ ul+h5 {
margin-top: 1.5em;
}
#content {
margin-bottom: 50px;
}
.panel-progress {
background: #3498db;
height: 3px;
@@ -64,6 +68,7 @@ identicon {
.popover {
max-width: none;
min-width: 250px;
}
.panel-heading .fa, .modal-header .fa {
@@ -249,7 +254,3 @@ ul.three-columns li, ul.two-columns li {
position: static;
}
}
.popover {
min-width: 250px;
}
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когато добавяш нов идентификатор на папка помни, че той се използва за свързване на папките на различни устройства. Главни/малки букви са от значение и трябва да са еднакви на всички устройства.",
"Yes": "Да",
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
"days": "дни",
"full documentation": "пълна документация",
"items": "артикула",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папка \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quan s'afegeix una nova carpeta recorda que el ID d'aquesta s'utilitza per lligar repositoris entre els dispositius. Es distingeix entre majúscules i minúscules i ha de ser exactament iguals entre tots els dispositius.",
"Yes": "Si",
"You must keep at least one version.": "Has de mantenir com a mínim una versió.",
"days": "days",
"full documentation": "documentació sencera",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartir la carpeta \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quant s'afig una nova carpeta, hi ha que tindre en compte que l'ID de la carpeta s'utilitza per a juntar les carpetes entre dispositius. Són sensibles a les majúscules i deuen coincidir exactament entre tots els dispositius.",
"Yes": "Sí",
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
"days": "days",
"full documentation": "Documentació completa",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\"."
+2 -1
View File
@@ -157,7 +157,7 @@
"Source Code": "Zdrojový kód",
"Staggered File Versioning": "Postupné verzování souborů",
"Start Browser": "Otevřít prohlížeč",
"Statistics": "Statistika",
"Statistics": "Statistiky",
"Stopped": "Pozastaveno",
"Support": "Podpora",
"Sync Protocol Listen Addresses": "Adresa naslouchání synchronizačního protokolu",
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
"Yes": "Ano",
"You must keep at least one version.": "Je třeba ponechat alespoň jednu verzi.",
"days": "dní",
"full documentation": "plná dokumentace",
"items": "položky",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce sdílet adresář \"{{folder}}\"."
+30 -29
View File
@@ -23,7 +23,7 @@
"Bugs": "Fehler",
"CPU Utilization": "Prozessorauslastung",
"Changelog": "Änderungsprotokoll",
"Clean out after": "Aufräumen nach",
"Clean out after": "Löschen nach",
"Close": "Schließen",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
@@ -35,7 +35,7 @@
"Delete": "Löschen",
"Deleted": "Gelöscht",
"Device ID": "Geräte ID",
"Device Identification": "Gerät Identifikation",
"Device Identification": "Geräte Identifikation",
"Device Name": "Gerätename",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Gerät {{device}} ({{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Devices": "Geräte",
@@ -47,9 +47,9 @@
"Edit": "Bearbeiten",
"Edit Device": "Gerät bearbeiten",
"Edit Folder": "Verzeichnis bearbeiten",
"Editing": "Bearbeiten",
"Editing": "Bearbeitet",
"Enable UPnP": "UPnP aktivieren",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Geben Sie durch Komma getrennte Adressen an (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\", um die Adresse automatisch zu ermitteln.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Kommagetrennte Adressen (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\" eingeben, um die Adresse automatisch zu ermitteln.",
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
"Error": "Fehler",
"External File Versioning": "Externe Dateiversionierung",
@@ -57,9 +57,9 @@
"File Pull Order": "Dateiübertragungsreihenfolge",
"File Versioning": "Dateiversionierung",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis namens .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Wenn Dateien von Syncthing ersetzt oder gelöscht werden sollen, werden sie vorher in den .stversions Ordner verschoben.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, datiert in das Verzeichnis .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind auf diesem Gerät schreibgeschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Folder": "Verzeichnis",
"Folder ID": "Verzeichnis ID",
"Folder Master": "Master Verzeichnis - schreibgeschützt",
@@ -71,7 +71,7 @@
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
"Generate": "Generieren",
"Global Discovery": "Globale Gerätesuche",
"Global Discovery Server": "Globale(r) Indexserver",
"Global Discovery Server": "Globaler Gerätesuchserver",
"Global State": "Globaler Status",
"Help": "Hilfe",
"Home page": "Homepage",
@@ -111,18 +111,18 @@
"Out of Sync Items": "Nicht synchronisierte Objekte",
"Outgoing Rate Limit (KiB/s)": "Limit Datenrate (ausgehend) (KB/s)",
"Override Changes": "Änderungen überschreiben",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem die Versionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Gerät. Ordner werden erzeugt, wenn sie nicht existieren. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem alte Dateiversionen gespeichert werden sollen (ohne Angabe wird das Verzeichnis .stversions im Verzeichnis verwendet).",
"Pause": "Pause",
"Paused": "Pausiert",
"Please consult the release notes before performing a major upgrade.": "BItte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
"Please wait": "Bitte warten",
"Preview": "Vorschau",
"Preview Usage Report": "Vorschau des Nutzungsberichts",
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Suchstrukturen",
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"RAM Utilization": "RAM Auslastung",
"Random": "Zufall",
"Relayed via": "Weiterleitung über",
"Relayed via": "Weitergeleitet über",
"Relays": "Weiterleitungen",
"Release Notes": "Veröffentlichungsnotizen",
"Remove": "Entfernen",
@@ -135,7 +135,7 @@
"Resume": "Fortfahren",
"Reused": "Erneut benutzt",
"Save": "Speichern",
"Scanning": "Wird gescannt",
"Scanning": "Scannen",
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du dieses Verzeichnis teilen willst.",
"Select the folders to share with this device.": "Wähle die Verzeichnisse aus, die du mit diesem Gerät teilen möchtest",
"Settings": "Einstellungen",
@@ -147,8 +147,8 @@
"Shared With": "Geteilt mit",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kurze ID für das Verzeichnis. Muss auf allen Verbunds-Geräten gleich sein.",
"Show ID": "ID anzeigen",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients weitergegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt. Wird auf den Namen aktualisiert, den das Gerät angibt.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients im Cluster weitergegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt.Wird auf den Namen aktualisiert, den das Gerät angibt, wenn nichts eingetragen wird.",
"Shutdown": "Herunterfahren",
"Shutdown Complete": "Vollständig Heruntergefahren",
"Simple File Versioning": "Einfache Dateiversionierung",
@@ -163,13 +163,13 @@
"Sync Protocol Listen Addresses": "Adresse(n) für das Synchronisierungsprotokoll",
"Syncing": "Synchronisiere",
"Syncthing has been shut down.": "Syncthing wurde heruntergefahren.",
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile davon:",
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile von:",
"Syncthing is restarting.": "Syncthing wird neu gestartet",
"Syncthing is upgrading.": "Syncthing wird aktualisiert",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Es scheint als ob Syncthing ein Problem mit der Verarbeitung ihrer Eingabe hat. Bitte laden sie die Seite neu oder führen sie einen Neustart von Syncthing durch, falls das Problem weiterhin besteht.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing scheint ein Problem mit der Verarbeitung Deiner Eingabe zu haben. Bitte lade die Seite neu oder führe einen Neustart durch, falls das Problem weiterhin besteht.",
"The aggregated statistics are publicly available at {%url%}.": "Die gesammelten Statistiken sind öffentlich verfügbar unter {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu übernehmen.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber noch nicht aktiviert. Syncthing muss neugestartet werden, um die neue Konfiguration zu übernehmen.",
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird verwendet, um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
@@ -179,18 +179,18 @@
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Bezeichnung haben (64 Zeichen oder weniger). Sie darf nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
"The folder ID must be unique.": "Die Verzeichnis ID darf nur einmal existieren.",
"The folder path cannot be blank.": "Der Verzeichnispfad darf nicht leer sein.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es wird in folgenden Abständen versioniert: in der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag, danach wird bis zum Höchstalter eine Version pro Woche beibehalten.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Es wird in folgenden Abständen versioniert: In der ersten Stunde wird alle 30 Sekunden eine Version behalten, am ersten Tag eine jede Stunde, in den ersten 30 Tagen eine jeden Tag. Danach wird bis zum angegebenen Höchstalter eine Version pro Woche behalten.",
"The following items could not be synchronized.": "Die folgenden Objekte konnten nicht synchronisiert werden.",
"The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen) (0 um alte Versionen für immer zu behalten).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Der Prozentsatz des minimal freien Festplattenspeichers muss eine nichtnegative Zahl zwischen (inklusive) 0 und 100 sein. ",
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
"The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
"The number of days to keep files in the trash can. Zero means forever.": "Dauer in Tagen für welche die Dateien aufgehoben werden sollen. 0 bedeutet für immer.",
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei behalten werden sollen.",
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.",
"The path cannot be blank.": "Der Pfad darf nicht leer sein.",
"The rate limit must be a non-negative number (0: no limit)": "Das Daterate-Limit muss eine nicht negative Anzahl sein (0: kein Limit)",
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl von Sekunden sein.",
"The rate limit must be a non-negative number (0: no limit)": "Das Daterate-Limit muss eine nicht negative Anzahl sein (0 = kein Limit).",
"The rescan interval must be a non-negative number of seconds.": "Das Scanintervall muss eine nicht negative Anzahl (in Sekunden) sein.",
"They are retried automatically and will be synced when the error is resolved.": "Sie werden automatisch heruntergeladen und werden synchronisiert, wenn der Fehler behoben wurde.",
"This is a major version upgrade.": "Dies ist eine neue Hauptversion.",
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
@@ -207,11 +207,12 @@
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
"Version": "Version",
"Versions Path": "Versionierungspfad",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Versionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die Höchstzahl der Dateien je Zeitabschnitt überschritten wird.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf der Gegenseite hinzugefügt werden muss.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beim Hinzufügen eines neuen Verzeichnisses beachte, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Alte Dateiversionen werden automatisch gelöscht, wenn sie älter als das angegebene Höchstalter sind oder die angegebene Höchstzahl an Dateien erreicht ist.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf den anderen Geräten hinzugefügt werden muss.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beachte bitte beim Hinzufügen eines neuen Verzeichnisses, dass die Verzeichnis ID dazu verwendet wird, Verzeichnisse zwischen Geräten zu verbinden. Die ID muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
"Yes": "Ja",
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
"days": "Tage",
"full documentation": "Komplette Dokumentation",
"items": "Objekte",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
"Yes": "Ναι",
"You must keep at least one version.": "Πρέπει να τηρήσεις τουλάχιστον μια έκδοση.",
"days": "days",
"full documentation": "πλήρης τεκμηρίωση",
"items": "εγγραφές",
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}»."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"days": "days",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cuando añada una nueva carpeta, tenga en cuenta que su ID se usa para unir carpetas entre dispositivos. Son sensibles a las mayúsculas y deben coincidir exactamente entre todos los dispositivos.",
"Yes": "Si",
"You must keep at least one version.": "Debes mantener al menos una versión.",
"days": "days",
"full documentation": "Documentación completa",
"items": "Elementos",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Al agregar un nuevo repositorio, tenga en cuenta que la ID del repositorio se utiliza para conectar los repositorios entre dispositivos. Se distingue entre mayúsculas y minúsculas y debe ser exactamente igual en todos los dispositivos.",
"Yes": "Sí",
"You must keep at least one version.": "Debe mantener al menos una versión",
"days": "days",
"full documentation": "documentación completa",
"items": "ítems",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lisättäessä uutta kansiota, muista että kansion ID:tä käytetään solmimaan kansiot yhteen laitteiden välillä. Ne ovat riippuvaisia kirjankoosta ja niiden tulee täsmätä kaikkien laitteiden välillä.",
"Yes": "Kyllä",
"You must keep at least one version.": "Sinun tulee säilyttää ainakin yksi versio.",
"days": "days",
"full documentation": "täysi dokumentaatio",
"items": "kohteet",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que son ID est utilisé pour lier les répertoires à travers les appareils. Les ID sont sensibles à la casse et doivent être identiques à travers tous les nœuds.",
"Yes": "Oui",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que son ID est utilisé pour lier les répertoires à travers les appareils. Les ID sont sensibles à la casse et doivent être identiques à travers tous les nœuds.",
"Yes": "Oui",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."
+23 -22
View File
@@ -3,15 +3,15 @@
"A new major version may not be compatible with previous versions.": "Az új főverzió nem kompatibilis az előző főverzióval.",
"API Key": "API kulcs",
"About": "Névjegy",
"Actions": "Tevékenységek",
"Actions": "Műveletek",
"Add": "Hozzáadás",
"Add Device": "Eszköz hozzáadása",
"Add Folder": "Mappa hozzáadása",
"Add new folder?": " ",
"Add new folder?": " Új mappa hozzáadás?",
"Address": "Cím",
"Addresses": "Címek",
"Advanced": "Speciális",
"Advanced Configuration": "Speciális beállítások",
"Advanced": "Haladó",
"Advanced Configuration": "Haladó beállítások",
"All Data": "Minden adat",
"Allow Anonymous Usage Reporting?": "Engedélyezed a névtelen felhasználási adatok küldését?",
"Alphabetic": "ABC rendben",
@@ -37,7 +37,7 @@
"Device ID": "Eszköz azonosító",
"Device Identification": "Eszköz azonosító",
"Device Name": "Eszköz neve",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne.",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne. Hozzáadod az új eszközt?",
"Devices": "Eszközök",
"Disconnected": "Kapcsolat bontva",
"Documentation": "Dokumentáció",
@@ -56,7 +56,7 @@
"Failed Items": "Hibás elemek",
"File Pull Order": "Fájl küldési sorrend",
"File Versioning": "Fájl verziózás",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszereken használatakor.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Fájl jogosultságok figyelmen kívül hagyása változások keresésekor. FAT fájlrendszerek használatakor.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing áthelyezi vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing áthelyezi vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve, időbélyegzővel ellátva.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "A fájlok védve vannak a más eszközökön történt változásokkal szemben, de az ezen az eszközön történt változások érvényesek lesznek a többire.",
@@ -65,7 +65,7 @@
"Folder Master": "Központi mappa",
"Folder Path": "Mappa elérési útja",
"Folders": "Mappák",
"GUI": "Felület",
"GUI": "Grafikus felület",
"GUI Authentication Password": "Grafikus felület jelszava",
"GUI Authentication User": "Grafikus felület felhasználó neve ",
"GUI Listen Addresses": "Grafikus felület címe",
@@ -75,8 +75,8 @@
"Global State": "Globális állapot",
"Help": "Segítség",
"Home page": "Főoldal",
"Ignore": "Visszautasítás",
"Ignore Patterns": "Figyelmen kívül hagyás",
"Ignore": "Figyelmen kívül hagyás",
"Ignore Patterns": "Minták figyelmen kívül hagyása",
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
"Incoming Rate Limit (KiB/s)": "Bejövő sebesség korlát (KIB/mp)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Helytelen konfiguráció esetén károsodhat a mappák tartalma és működésképtelenné válhat a Syncthing.",
@@ -107,23 +107,23 @@
"Off": "Kikapcsolva",
"Oldest First": "Régebbi először",
"Options": "Opciók",
"Out of Sync": "Out of Sync",
"Out of Sync": "Nincs szinkronban",
"Out of Sync Items": "Nem szinkronizált elemek",
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
"Override Changes": "Változtatások felülbírálása",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "A mappa elérési útja az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Elérési út ahol a verziók tárolásra kerülnek (szabadon hagyva az alapértelmezett .stversions mappa lesz használva)",
"Pause": "Pause",
"Paused": "Paused",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Pause": "Szünet",
"Paused": "Szünetel",
"Please consult the release notes before performing a major upgrade.": "Nagyobb frissítés előtt ellenőrizni kell a kiadási megjegyzéseket.",
"Please wait": "Kérlek, várj",
"Preview": "Előnézet",
"Preview Usage Report": "Felhasználási adatok átnézése",
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
"RAM Utilization": "Memória használat",
"Random": "Véletlenszerű",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Közvetítve",
"Relays": "Közvetítések",
"Release Notes": "Kiadási megjegyzések",
"Remove": "Eltávolítás",
"Rescan": "Átnézés",
@@ -132,7 +132,7 @@
"Restart": "Újraindítás",
"Restart Needed": "Újraindítás szükséges",
"Restarting": "Újraindulás",
"Resume": "Resume",
"Resume": "Folytatás",
"Reused": "Újrafelhasználva",
"Save": "Mentés",
"Scanning": "Átnézés",
@@ -150,7 +150,7 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Az eszköz azonosító helyett jelenik meg. A többi eszközön alapértelmezett névként használható. ",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Az eszköz azonosító helyett jelenik meg. Üresen hagyva az eszköz saját neve lesz használva ",
"Shutdown": "Leállítás",
"Shutdown Complete": "Leállítás",
"Shutdown Complete": "Leállítás kész",
"Simple File Versioning": "Egyszerű fájl verziókövetés",
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
"Smallest First": "Kisebb előbb",
@@ -167,7 +167,7 @@
"Syncthing is restarting.": "Syncthing újraindul",
"Syncthing is upgrading.": "Syncthing frissül",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Úgy tűnik, hogy a Syncthing nem működik, vagy valami probléma van az hálózati kapcsolattal. Újra próbálom...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Úgy tűnik, hogy a Syncthing problémába ütközött a kérés feldolgozása során. Ha a probléma továbbra is fennáll, akkor frissíteni kell az oldalt, vagy újra kell indítani a Syncthinget.",
"The aggregated statistics are publicly available at {%url%}.": "Az összevont statisztikák nyilvánosan elérhetők a {{url}} címen.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A beállítások elmentésre kerültek, de nem lettek aktiválva. Indítsd újra a Syncthing-et, hogy aktiváld őket.",
"The device ID cannot be blank.": "Az eszköz azonosító nem lehet üres.",
@@ -189,9 +189,9 @@
"The number of old versions to keep, per file.": "A megtartott régi verziók száma, fájlonként.",
"The number of versions must be a number and cannot be blank.": "A megtartott verziók száma nem lehet üres",
"The path cannot be blank.": "Elérési út nem lehet üres.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Az arány limitnek pozitív számnak kell lennie (0: nincs limit)",
"The rescan interval must be a non-negative number of seconds.": "Az átnézési intervallum nullánál nagyobb másodperc érték kell legyen",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "A hiba javítása után automatikusan újra megpróbálja a szinkronizálást.",
"This is a major version upgrade.": "Ez egy főverzió frissítés.",
"Trash Can File Versioning": "Szemetes fájl verziózás",
"Unknown": "Ismeretlen",
@@ -204,7 +204,7 @@
"Upgrading": "Frissítés",
"Upload Rate": "Feltöltési sebesség",
"Uptime": "Üzemidő",
"Use HTTPS for GUI": "HTTPS használata a GUI-hoz",
"Use HTTPS for GUI": "HTTPS használata a felülethez",
"Version": "Verzió",
"Versions Path": "Verziók útvonala",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "A régi verziók automatikusan törlődnek, amennyiben öregebbek mint a maximum kor, vagy már több van belőlük mint az adott időszakban megtartható maximum.",
@@ -212,7 +212,8 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Amikor új mappát adsz hozzá, tartsd észben, hogy a mappa azonosító arra való hogy összekösd a mappákat az eszközeiden. Az azonosító kisbetű-nagybetű érzékeny és pontosan egyeznie kell az eszközökön.",
"Yes": "Igen",
"You must keep at least one version.": "Legalább egy verziót meg kell tartanod",
"days": "nap",
"full documentation": "teljes dokumentáció",
"items": "elemek",
"items": "elem",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} meg szeretné osztani a \"{{folder}}\" nevű mappát."
}
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando aggiungi una nuova cartella, ricordati che gli ID vengono utilizzati per collegare le cartelle nei dispositivi. Distinguono maiuscole e minuscole e devono corrispondere esattamente su tutti i dispositivi.",
"Yes": "Sì",
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
"days": "days",
"full documentation": "documentazione completa",
"items": "elementi",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vuole condividere la cartella \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "新しいフォルダーを追加する際、フォルダーIDはデバイス間でフォルダーの対応づけに使われることに注意してください。フォルダーIDは大文字と小文字が区別され、共有するすべてのデバイスの間で完全に一致しなくてはなりません。",
"Yes": "はい",
"You must keep at least one version.": "少なくとも一つのバージョンを保存してください。",
"days": "日",
"full documentation": "詳細なマニュアル",
"items": "項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。"
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
"Yes": "예",
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
"days": "days",
"full documentation": "전체 문서",
"items": "항목",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다."
+2 -1
View File
@@ -189,7 +189,7 @@
"The number of old versions to keep, per file.": "Kiek failo versijų saugoti.",
"The number of versions must be a number and cannot be blank.": "Versijų skaičius turi būti skaitmuo ir negali būti tuščias laukelis.",
"The path cannot be blank.": "Kelias negali būti tuščias.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Srauto maksimalus greitis privalo būti ne neigiamas skaičius (0: nėra apribojimo)",
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This is a major version upgrade.": "Tai yra stambus atnaujinimas.",
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Kai įvedate naują aplanką neužmirškite, kad jis bus naudojamas visuose įrenginiuose. Svarbu visur įvesti visiškai tokį pat aplanko vardą neužmirštant apie didžiąsias ir mažąsias raides.",
"Yes": "Taip",
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
"days": "days",
"full documentation": "pilna dokumentacija",
"items": "įrašai",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\""
+4 -3
View File
@@ -94,7 +94,7 @@
"Maximum Age": "Maksimal Levetid",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Nødvendig ledig diskplass",
"Move to top of queue": "Flytt til topp av kø",
"Move to top of queue": "Flytt fremst ien",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
"New Device": "Ny Enhet",
@@ -122,8 +122,8 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "RAM-utnyttelse",
"Random": "Tilfeldig",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Relé via",
"Relays": "Reléer",
"Release Notes": "Utgivelsesnotat",
"Remove": "Fjern",
"Rescan": "Skann på nytt",
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Når en ny mappe blir lagt til, husk at Mappe-ID blir brukt til å binde sammen mapper mellom enheter. Det er forskjell på store og små bokstaver, så IDene må være identiske på alle enhetene.",
"Yes": "Ja",
"You must keep at least one version.": "Du må beholde minst én versjon",
"days": "dager",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\"."
+2 -1
View File
@@ -49,7 +49,7 @@
"Edit Folder": "Bewerk map",
"Editing": "Bezig met bewerken",
"Enable UPnP": "UPnP gebruiken",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen of \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen in of voer \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
"Enter ignore patterns, one per line.": "Voer negeerpatronen in, één per regel.",
"Error": "Fout",
"External File Versioning": "Extern versiebeheer voor bestanden",
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Houd er bij het toevoegen van nieuwe mappen rekening mee dat het map-ID gebruikt wordt om mappen tussen apparaten te verbinden. Dit ID is hoofdlettergevoelig en moet identiek zijn op andere apparaten.",
"Yes": "Ja",
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
"days": "dagen",
"full documentation": "volledige documentatie",
"items": "objecten",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de map \"{{folder}}\" delen."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Hugs at når ei ny mappe vert lagt til, vert mappe-ID-en brukt til å binda saman mappene mellom einingane. Det er skilnad på store og små bokstavar, så ID-ane må vera identiske på alle einingane.",
"Yes": "Ja",
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"days": "days",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønskjer å dela mappa \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Przy dodawaniu nowego folderu, pamiętaj, że ID użyte jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ciągu ma znaczenie musi zgadzać się na wszystkich urządzeniach.",
"Yes": "Tak",
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
"days": "days",
"full documentation": "pełna dokumentacja",
"items": "pozycji",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\""
+12 -11
View File
@@ -37,7 +37,7 @@
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Documentation": "Documentação",
@@ -49,7 +49,7 @@
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Enable UPnP": "Habilitar UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática de endereço.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática do endereço.",
"Enter ignore patterns, one per line.": "Insira os padrões de exclusão, um por linha.",
"Error": "Erro",
"External File Versioning": "Versionamento externo de arquivo",
@@ -57,9 +57,9 @@
"File Pull Order": "Ordem de retirada do arquivo",
"File Versioning": "Versionamento de arquivos",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Os bits de permissão de um arquivo são ignorados durante as verificações. Use em sistemas de arquivo FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são motivos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando são substituídos ou removidos pelo Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são movidos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions após serem substituídos ou removidos pelo Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto dos dispositivos.",
"Folder": "Pasta",
"Folder ID": "ID da pasta",
"Folder Master": "Pasta mestre",
@@ -140,14 +140,14 @@
"Select the folders to share with this device.": "Selecione as pastas a serem compartilhadas com este dispositivo.",
"Settings": "Configurações",
"Share": "Compartilhar",
"Share Folder": "Compartilhar Pasta",
"Share Folder": "Compartilhar pasta",
"Share Folders With Device": "Compartilhar pastas com o dispositivo",
"Share With Devices": "Compartilhar com os dispositivos",
"Share this folder?": "Compartilhar esta pasta?",
"Shared With": "Compartilhada com",
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curto para a pasta. Deve ser igual em todos os dispositivos.",
"Show ID": "Mostrar ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome pré-definido opcional.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome opcional e pré-definido.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será atualizado para o nome que o dispositivo divulga, caso seja deixado em branco.",
"Shutdown": "Desligar",
"Shutdown Complete": "Desligamento completado",
@@ -172,11 +172,11 @@
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi salva mas ainda não foi ativada. O Syncthing precisa ser reiniciado para a ativação da nova configuração.",
"The device ID cannot be blank.": "O ID de dispositivo não pode ficar vazio.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a ser inserido aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífens são opcionais (ignorados).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório de uso é criptografado e enviado diariamente. É utilizado para rastrear plataformas, tamanhos de pastas e versões da aplicação. Caso o formato dos dados seja alterado, esta janela te avisará.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "O ID de dispositivo inserido não parece ser válido. Ele deve ter entre 52 e 56 caracteres e ser composto de letras e números, com espaços e hífens opcionais.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "O primeiro argumento da linha de comando é o caminho da pasta e o segundo é o caminho relativo à pasta.",
"The folder ID cannot be blank.": "O ID da pasta não pode ficar vazio.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos), composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos) e composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
"The folder ID must be unique.": "O ID da pasta deve ser único.",
"The folder path cannot be blank.": "O caminho da pasta não pode ficar vazio.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
@@ -184,7 +184,7 @@
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico. O campo não pode ficar vazio.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "O número máximo de dias em que uma versão é guardada. (Use 0 para manter para sempre).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "A porcentagem de espaço livre mínimo no disco deve ser um número positivo entre 0 e 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "O número de dias deve ser um número valido e não pode ficar em branco.",
"The number of days must be a number and cannot be blank.": "O número de dias deve ser um número válido e não pode ficar em branco.",
"The number of days to keep files in the trash can. Zero means forever.": "O número de dias em que são mantidos os arquivos da lixeira. Zero significa para sempre.",
"The number of old versions to keep, per file.": "O número de versões antigas a serem mantidas, por arquivo.",
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numérico. O campo não pode ficar vazio.",
@@ -209,9 +209,10 @@
"Versions Path": "Caminho das versões",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "As versões são automaticamente apagadas se elas são mais antigas do que a idade máxima ou excederem o número de arquivos permitido em um intervalo.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando estiver adicionando um dispositivo, lembre-se de que este dispositivo deve ser adicionado do outro lado também.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e devem ser iguais em todos os dispositivos.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e deve ser o mesmo em todos os dispositivos.",
"Yes": "Sim",
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
"days": "dias",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar as pastas entre dispositivos. É sensível às diferenças entre maiúsculas e minúsculas e tem que ter uma correspondência perfeita entre todos os dispositivos.",
"Yes": "Sim",
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
"days": "dias",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cînd adăugaţi un fişier nou, nu uitaţi că ID-ul fişierului va rămîne acelaşi pe toate dispozitivele. Iar literele mari sînt diferite de literele mici. ",
"Yes": "Da",
"You must keep at least one version.": "Trebuie să păstrezi cel puţin o versiune.",
"days": "days",
"full documentation": "toată documentaţia",
"items": "obiecte",
"{%device%} wants to share folder \"{%folder%}\".": "{{Dispozitivul}} vrea să transmită mapa {{Mapa}}"
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
"Yes": "Да",
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"days": "Дней",
"full documentation": "полная документация",
"items": "элементы",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny katalog, tänk på att katalog-ID:t knyter ihop katalogen mellan olika noder. De måste vara exakt desamma mellan noder och stora eller små bokstäver har betydelse.",
"Yes": "Ja",
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"days": "days",
"full documentation": "fullständig dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\"."
+13 -12
View File
@@ -49,7 +49,7 @@
"Edit Folder": "Редагувати директорію",
"Editing": "Редагування",
"Enable UPnP": "Увімкнути UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введіть розділені комою (\"tcp://ip:port\", \"tcp://host:port\") адреси або \"dynamic\" для автоматичного визначення адреси.",
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
"Error": "Помилка",
"External File Versioning": "Зовнішне керування версіями",
@@ -74,7 +74,7 @@
"Global Discovery Server": "Сервер для глобального виявлення",
"Global State": "Глобальний статус",
"Help": "Допомога",
"Home page": "Home page",
"Home page": "Домашня сторінка",
"Ignore": "Ігнорувати",
"Ignore Patterns": "Ігнорувати шаблони",
"Ignore Permissions": "Ігнорувати права доступу до файлів",
@@ -93,7 +93,7 @@
"Major Upgrade": "Мажорне оновлення",
"Maximum Age": "Максимальний вік",
"Metadata Only": "Тільки метадані",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Мінімальний вільний простір на диску",
"Move to top of queue": "Пересунути у початок черги",
"Multi level wildcard (matches multiple directory levels)": "Багаторівнева маска (пошук збігів в усіх піддиректоріях) ",
"Never": "Ніколи",
@@ -113,8 +113,8 @@
"Override Changes": "Перезаписати зміни",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Шлях до директорії на локальному комп’ютері. Буде створений, якщо такий не існує. Символ тильди (~) може бути використаний як ярлик для",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Пауза",
"Paused": "Призупинено",
"Please consult the release notes before performing a major upgrade.": "Будь ласка перегляньте примітки до випуску перед мажорним оновленням. ",
"Please wait": "Будь ласка, зачекайте",
"Preview": "Попередній перегляд",
@@ -122,17 +122,17 @@
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
"RAM Utilization": "Використання RAM",
"Random": "Випадково",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Ретранслювати через",
"Relays": "Ретрансляція",
"Release Notes": "Примітки до випуску",
"Remove": "Remove",
"Remove": "Видалити",
"Rescan": "Пересканувати",
"Rescan All": "Пересканувати усе",
"Rescan Interval": "Інтервал для повторного сканування",
"Restart": "Перезапуск",
"Restart Needed": "Необхідний перезапуск",
"Restarting": "Відбувається перезапуск",
"Resume": "Resume",
"Resume": "Продовжити",
"Reused": "Використано вдруге",
"Save": "Зберегти",
"Scanning": "Сканування",
@@ -157,7 +157,7 @@
"Source Code": "Сирцевий код",
"Staggered File Versioning": "Поступове версіонування",
"Start Browser": "Запустити браузер",
"Statistics": "Statistics",
"Statistics": "Статистика",
"Stopped": "Зупинено",
"Support": "Підтримка",
"Sync Protocol Listen Addresses": "Адреса панелі управління",
@@ -183,13 +183,13 @@
"The following items could not be synchronized.": "Наступні пункти не можуть бути синхронізовані.",
"The maximum age must be a number and cannot be blank.": "Максимальний термін повинен бути числом та не може бути пустим.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальний термін, щоб зберігати версію (у днях, вствновіть в 0, щоби зберігати версії назавжди).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Відсоток мінімального вільного простору на диску має бути додатнім числом від 0 до 100 включно.",
"The number of days must be a number and cannot be blank.": "Кількість днів має бути числом і не може бути порожнім.",
"The number of days to keep files in the trash can. Zero means forever.": "Кількість днів зберігання файлів у кошику. Нуль означає назавжди.",
"The number of old versions to keep, per file.": "Кількість старих версій, яку необхідно зберігати для кожного файлу.",
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
"The path cannot be blank.": "Шлях не може бути порожнім.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Швидкість має бути додатнім числом.",
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
"They are retried automatically and will be synced when the error is resolved.": "Вони будуть автоматично повторно синхронізовані, коли помилку буде усунено. ",
"This is a major version upgrade.": "Це оновлення мажорної версії",
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між вузлами. Назви є чутливими до регістра та повинні співпадати точно між усіма вузлами.",
"Yes": "Так",
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
"days": "days",
"full documentation": "повна документація",
"items": "елементи",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\"."
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "若你添加了新文件夹,记住文件夹标识是用以在不同设备间建立联系的。在不同设备间拥有相同标识的文件夹将会被同步。且文件夹标识大小写敏感。",
"Yes": "是",
"You must keep at least one version.": "您必须保留至少一个版本。",
"days": "days",
"full documentation": "完整文档",
"items": "条目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将 “{{folder}}” 文件夹共享给您"
+1
View File
@@ -212,6 +212,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "當新增一個資料夾時,請記住,資料夾識別碼是用來將裝置之間的資料夾綁定在一起的。它們有區分大小寫,且必須在所有裝置之間完全相同。",
"Yes": "是",
"You must keep at least one version.": "您必須保留至少一個版本。",
"days": "days",
"full documentation": "完整說明文件",
"items": "個項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\"。"
+3 -3
View File
@@ -70,7 +70,7 @@
</div>
</nav>
<div class="container">
<div class="container" id="content">
<!-- Panel: Restart Needed -->
@@ -171,7 +171,7 @@
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="fa fa-exclamation-circle"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.time | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
<p ng-repeat="err in errorList()"><small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.message)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="clearErrors()">
@@ -239,7 +239,7 @@
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
</tr>
<tr ng-if="model[folder.id].needFiles > 0">
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync</span></th>
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right">
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
</td>
+3
View File
@@ -13,6 +13,7 @@
<div class="col-md-12">
<ul class="list-unstyled three-columns" id="contributor-list">
<li class="auto-generated">Aaron Bieber</li>
<li class="auto-generated">Adam Piggott</li>
<li class="auto-generated">Alexander Graf</li>
<li class="auto-generated">Andrew Dunham</li>
<li class="auto-generated">Antony Male</li>
@@ -59,6 +60,8 @@
<li class="auto-generated">Marc Laporte</li>
<li class="auto-generated">Marc Pujol</li>
<li class="auto-generated">Marcin Dziadus</li>
<li class="auto-generated">Mateon1</li>
<li class="auto-generated">Matt Burke</li>
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Tilli</li>
<li class="auto-generated">Pascal Jungblut</li>
+5 -2
View File
@@ -1043,13 +1043,16 @@ angular.module('syncthing.core')
};
$scope.errorList = function () {
if (!$scope.errors) {
return [];
}
return $scope.errors.filter(function (e) {
return e.time > $scope.seenError;
return e.when > $scope.seenError;
});
};
$scope.clearErrors = function () {
$scope.seenError = $scope.errors[$scope.errors.length - 1].time;
$scope.seenError = $scope.errors[$scope.errors.length - 1].when;
$http.post(urlbase + '/system/error/clear');
};
@@ -15,7 +15,7 @@
<datalist id="discovery-list" ng-if="!editingExisting">
<option ng-repeat="(id, data) in discovery" value="{{id}}" />
</datalist>
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.deviceID}}</div>
<div ng-if="editingExisting" class="well well-sm text-monospace" select-on-click>{{currentDevice.deviceID}}</div>
<p class="help-block">
<span translate ng-if="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">The device ID to enter here can be found in the "Edit > Show ID" dialog on the other device. Spaces and dashes are optional (ignored).</span>
<span translate ng-show="!editingExisting && (deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine)">When adding a new device, keep in mind that this device must be added on the other side too.</span>
+1 -1
View File
@@ -1,4 +1,4 @@
<modal id="idqr" status="info" icon="qrcode" title="{{'Device Identification' | translate}} - {{deviceName(thisDevice())}}" large="yes" close="yes">
<div class="well well-sm text-monospace text-center">{{myID}}</div>
<div class="well well-sm text-monospace text-center" select-on-click>{{myID}}</div>
<img ng-if="myID" class="center-block img-thumbnail" ng-src="qr/?text={{myID}}"/>
</modal>
@@ -13,12 +13,11 @@
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder></input>
<p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
<span translate ng-if="folderEditor.folderID.$error.pattern && folderEditor.folderID.$dirty">The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
@@ -108,7 +108,7 @@
<div class="form-group">
<label translate>API Key</label>
<div class="well well-sm text-monospace">{{tmpGUI.apiKey || "-"}}</div>
<div class="well well-sm text-monospace" select-on-click>{{tmpGUI.apiKey || "-"}}</div>
<button type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(tmpGUI)">
<span class="fa fa-repeat"></span>&nbsp;<span translate>Generate</span>
</button>
+44 -40
View File
File diff suppressed because one or more lines are too long
+35 -51
View File
@@ -33,9 +33,7 @@ func NewBroadcast(port int) *Broadcast {
FailureBackoff: 60 * time.Second,
// Only log restarts in debug mode.
Log: func(line string) {
if debug {
l.Debugln(line)
}
l.Debugln(line)
},
}),
port: port,
@@ -81,17 +79,13 @@ type broadcastWriter struct {
}
func (w *broadcastWriter) Serve() {
if debug {
l.Debugln(w, "starting")
defer l.Debugln(w, "stopping")
}
l.Debugln(w, "starting")
defer l.Debugln(w, "stopping")
var err error
w.conn, err = net.ListenUDP("udp4", nil)
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
return
}
@@ -100,9 +94,7 @@ func (w *broadcastWriter) Serve() {
for bs := range w.inbox {
addrs, err := net.InterfaceAddrs()
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
continue
}
@@ -120,41 +112,43 @@ func (w *broadcastWriter) Serve() {
dsts = append(dsts, net.IP{0xff, 0xff, 0xff, 0xff})
}
if debug {
l.Debugln("addresses:", dsts)
}
l.Debugln("addresses:", dsts)
success := 0
for _, ip := range dsts {
dst := &net.UDPAddr{IP: ip, Port: w.port}
w.conn.SetWriteDeadline(time.Now().Add(time.Second))
_, err := w.conn.WriteTo(bs, dst)
w.conn.SetWriteDeadline(time.Time{})
if err, ok := err.(net.Error); ok && err.Timeout() {
// Write timeouts should not happen. We treat it as a fatal
// error on the socket.
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
return
} else if err, ok := err.(net.Error); ok && err.Temporary() {
// A transient error. Lets hope for better luck in the future.
if debug {
l.Debugln(err)
}
continue
} else if err != nil {
// Some other error that we don't expect. Bail and retry.
if debug {
l.Debugln(err)
}
w.setError(err)
return
} else if debug {
l.Debugf("sent %d bytes to %s", len(bs), dst)
w.setError(nil)
}
if err, ok := err.(net.Error); ok && err.Temporary() {
// A transient error. Lets hope for better luck in the future.
l.Debugln(err)
continue
}
if err != nil {
// Some other error that we don't expect. Bail and retry.
l.Debugln(err)
w.setError(err)
return
}
l.Debugf("sent %d bytes to %s", len(bs), dst)
success++
}
if success > 0 {
w.setError(nil)
}
}
}
@@ -175,17 +169,13 @@ type broadcastReader struct {
}
func (r *broadcastReader) Serve() {
if debug {
l.Debugln(r, "starting")
defer l.Debugln(r, "stopping")
}
l.Debugln(r, "starting")
defer l.Debugln(r, "stopping")
var err error
r.conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
r.setError(err)
return
}
@@ -195,27 +185,21 @@ func (r *broadcastReader) Serve() {
for {
n, addr, err := r.conn.ReadFrom(bs)
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
r.setError(err)
return
}
r.setError(nil)
if debug {
l.Debugf("recv %d bytes from %s", n, addr)
}
l.Debugf("recv %d bytes from %s", n, addr)
c := make([]byte, n)
copy(c, bs)
select {
case r.outbox <- recv{c, addr}:
default:
if debug {
l.Debugln("dropping message")
}
l.Debugln("dropping message")
}
}
+6 -3
View File
@@ -10,10 +10,13 @@ import (
"os"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "beacon") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
l = logger.DefaultLogger.NewFacility("beacon", "Multicast and broadcast discovery")
)
func init() {
l.SetDebug("beacon", strings.Contains(os.Getenv("STTRACE"), "beacon") || os.Getenv("STTRACE") == "all")
}
+29 -56
View File
@@ -35,9 +35,7 @@ func NewMulticast(addr string) *Multicast {
FailureBackoff: 60 * time.Second,
// Only log restarts in debug mode.
Log: func(line string) {
if debug {
l.Debugln(line)
}
l.Debugln(line)
},
}),
inbox: make(chan []byte),
@@ -85,25 +83,19 @@ type multicastWriter struct {
}
func (w *multicastWriter) Serve() {
if debug {
l.Debugln(w, "starting")
defer l.Debugln(w, "stopping")
}
l.Debugln(w, "starting")
defer l.Debugln(w, "stopping")
gaddr, err := net.ResolveUDPAddr("udp6", w.addr)
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
return
}
conn, err := net.ListenPacket("udp6", ":0")
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
return
}
@@ -117,34 +109,33 @@ func (w *multicastWriter) Serve() {
for bs := range w.inbox {
intfs, err := net.Interfaces()
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
return
}
var success int
success := 0
for _, intf := range intfs {
wcm.IfIndex = intf.Index
pconn.SetWriteDeadline(time.Now().Add(time.Second))
_, err = pconn.WriteTo(bs, wcm, gaddr)
pconn.SetWriteDeadline(time.Time{})
if err != nil && debug {
if err != nil {
l.Debugln(err, "on write to", gaddr, intf.Name)
} else if debug {
l.Debugf("sent %d bytes to %v on %s", len(bs), gaddr, intf.Name)
success++
w.setError(err)
continue
}
l.Debugf("sent %d bytes to %v on %s", len(bs), gaddr, intf.Name)
success++
}
if success > 0 {
w.setError(nil)
} else {
if debug {
l.Debugln(err)
}
l.Debugln(err)
w.setError(err)
}
}
@@ -166,34 +157,26 @@ type multicastReader struct {
}
func (r *multicastReader) Serve() {
if debug {
l.Debugln(r, "starting")
defer l.Debugln(r, "stopping")
}
l.Debugln(r, "starting")
defer l.Debugln(r, "stopping")
gaddr, err := net.ResolveUDPAddr("udp6", r.addr)
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
r.setError(err)
return
}
conn, err := net.ListenPacket("udp6", r.addr)
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
r.setError(err)
return
}
intfs, err := net.Interfaces()
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
r.setError(err)
return
}
@@ -202,20 +185,16 @@ func (r *multicastReader) Serve() {
joined := 0
for _, intf := range intfs {
err := pconn.JoinGroup(&intf, &net.UDPAddr{IP: gaddr.IP})
if debug {
if err != nil {
l.Debugln("IPv6 join", intf.Name, "failed:", err)
} else {
l.Debugln("IPv6 join", intf.Name, "success")
}
if err != nil {
l.Debugln("IPv6 join", intf.Name, "failed:", err)
} else {
l.Debugln("IPv6 join", intf.Name, "success")
}
joined++
}
if joined == 0 {
if debug {
l.Debugln("no multicast interfaces available")
}
l.Debugln("no multicast interfaces available")
r.setError(errors.New("no multicast interfaces available"))
return
}
@@ -224,24 +203,18 @@ func (r *multicastReader) Serve() {
for {
n, _, addr, err := pconn.ReadFrom(bs)
if err != nil {
if debug {
l.Debugln(err)
}
l.Debugln(err)
r.setError(err)
continue
}
if debug {
l.Debugf("recv %d bytes from %s", n, addr)
}
l.Debugf("recv %d bytes from %s", n, addr)
c := make([]byte, n)
copy(c, bs)
select {
case r.outbox <- recv{c, addr}:
default:
if debug {
l.Debugln("dropping message")
}
l.Debugln("dropping message")
}
}
}
+4 -2
View File
@@ -20,8 +20,8 @@ import (
"strconv"
"strings"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"golang.org/x/crypto/bcrypt"
)
@@ -108,7 +108,9 @@ type FolderConfiguration struct {
Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Order PullOrder `xml:"order" json:"order"`
IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
ScanProgressIntervalS int `xml:"scanProgressInterval" json:"scanProgressInterval"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
ScanProgressIntervalS int `xml:"scanProgressIntervalS" json:"scanProgressIntervalS"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
PullerSleepS int `xml:"pullerSleepS" json:"pullerSleepS"`
PullerPauseS int `xml:"pullerPauseS" json:"pullerPauseS"`
Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"strings"
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
)
var device1, device2, device3, device4 protocol.DeviceID
+6 -3
View File
@@ -10,10 +10,13 @@ import (
"os"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "config") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
l = logger.DefaultLogger.NewFacility("config", "Configuration loading and saving")
)
func init() {
l.SetDebug("config", strings.Contains(os.Getenv("STTRACE"), "config") || os.Getenv("STTRACE") == "all")
}
+5 -13
View File
@@ -9,9 +9,9 @@ package config
import (
"os"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -142,13 +142,9 @@ func (w *Wrapper) replaceLocked(to Configuration) CommitResponse {
from := w.cfg
for _, sub := range w.subs {
if debug {
l.Debugln(sub, "verifying configuration")
}
l.Debugln(sub, "verifying configuration")
if err := sub.VerifyConfiguration(from, to); err != nil {
if debug {
l.Debugln(sub, "rejected config:", err)
}
l.Debugln(sub, "rejected config:", err)
return CommitResponse{
ValidationError: err,
}
@@ -157,14 +153,10 @@ func (w *Wrapper) replaceLocked(to Configuration) CommitResponse {
allOk := true
for _, sub := range w.subs {
if debug {
l.Debugln(sub, "committing configuration")
}
l.Debugln(sub, "committing configuration")
ok := sub.CommitConfiguration(from, to)
if !ok {
if debug {
l.Debugln(sub, "requires restart")
}
l.Debugln(sub, "requires restart")
allOk = false
}
}
@@ -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 http://mozilla.org/MPL/2.0/.
package main
package connections
import (
"crypto/tls"
@@ -15,14 +15,15 @@ import (
"sync"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/relaysrv/client"
"github.com/juju/ratelimit"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/relay/client"
"github.com/thejerf/suture"
)
@@ -35,17 +36,29 @@ var (
listeners = make(map[string]ListenerFactory, 0)
)
type Model interface {
protocol.Model
AddConnection(conn model.Connection)
ConnectedTo(remoteID protocol.DeviceID) bool
IsPaused(remoteID protocol.DeviceID) bool
}
// The connection service listens on TLS and dials configured unconnected
// devices. Successful connections are handed to the model.
type connectionSvc struct {
*suture.Supervisor
cfg *config.Wrapper
myID protocol.DeviceID
model *model.Model
tlsCfg *tls.Config
discoverer discover.Finder
conns chan model.IntermediateConnection
relaySvc *relay.Svc
cfg *config.Wrapper
myID protocol.DeviceID
model Model
tlsCfg *tls.Config
discoverer discover.Finder
conns chan model.IntermediateConnection
relaySvc *relay.Svc
bepProtocolName string
tlsDefaultCommonName string
lans []*net.IPNet
writeRateLimit *ratelimit.Bucket
readRateLimit *ratelimit.Bucket
lastRelayCheck map[protocol.DeviceID]time.Time
@@ -54,16 +67,20 @@ type connectionSvc struct {
relaysEnabled bool
}
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Model, tlsCfg *tls.Config, discoverer discover.Finder, relaySvc *relay.Svc) *connectionSvc {
func NewConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, relaySvc *relay.Svc,
bepProtocolName string, tlsDefaultCommonName string, lans []*net.IPNet) suture.Service {
svc := &connectionSvc{
Supervisor: suture.NewSimple("connectionSvc"),
cfg: cfg,
myID: myID,
model: mdl,
tlsCfg: tlsCfg,
discoverer: discoverer,
relaySvc: relaySvc,
conns: make(chan model.IntermediateConnection),
Supervisor: suture.NewSimple("connectionSvc"),
cfg: cfg,
myID: myID,
model: mdl,
tlsCfg: tlsCfg,
discoverer: discoverer,
relaySvc: relaySvc,
conns: make(chan model.IntermediateConnection),
bepProtocolName: bepProtocolName,
tlsDefaultCommonName: tlsDefaultCommonName,
lans: lans,
connType: make(map[protocol.DeviceID]model.ConnectionType),
relaysEnabled: cfg.Options().RelaysEnabled,
@@ -71,6 +88,13 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
}
cfg.Subscribe(svc)
if svc.cfg.Options().MaxSendKbps > 0 {
svc.writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*svc.cfg.Options().MaxSendKbps), int64(5*1000*svc.cfg.Options().MaxSendKbps))
}
if svc.cfg.Options().MaxRecvKbps > 0 {
svc.readRateLimit = ratelimit.NewBucketWithRate(float64(1000*svc.cfg.Options().MaxRecvKbps), int64(5*1000*svc.cfg.Options().MaxRecvKbps))
}
// There are several moving parts here; one routine per listening address
// to handle incoming connections, one routine to periodically attempt
// outgoing connections, one routine to the the common handling
@@ -97,9 +121,7 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
continue
}
if debugNet {
l.Debugln("listening on", uri.String())
}
l.Debugln("listening on", uri)
svc.Add(serviceFunc(func() {
listener(uri, svc.tlsCfg, svc.conns)
@@ -123,7 +145,7 @@ next:
// of the TLS handshake. Unfortunately this can't be a hard error,
// because there are implementations out there that don't support
// protocol negotiation (iOS for one...).
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != bepProtocolName {
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != s.bepProtocolName {
l.Infof("Peer %s did not negotiate bep/1.0", c.Conn.RemoteAddr())
}
@@ -142,7 +164,7 @@ next:
// The device ID should not be that of ourselves. It can happen
// though, especially in the presence of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == myID {
if remoteID == s.myID {
l.Infof("Connected to myself (%s) - should not happen", remoteID)
c.Conn.Close()
continue
@@ -154,9 +176,7 @@ next:
ct, ok := s.connType[remoteID]
s.mut.RUnlock()
if ok && !ct.IsDirect() && c.Type.IsDirect() {
if debugNet {
l.Debugln("Switching connections", remoteID)
}
l.Debugln("Switching connections", remoteID)
s.model.Close(remoteID, fmt.Errorf("switching connections"))
} else if s.model.ConnectedTo(remoteID) {
// We should not already be connected to the other party. TODO: This
@@ -181,7 +201,7 @@ next:
// the certificate and used another name.
certName := deviceCfg.CertName
if certName == "" {
certName = tlsDefaultCommonName
certName = s.tlsDefaultCommonName
}
err := remoteCert.VerifyHostname(certName)
if err != nil {
@@ -199,22 +219,20 @@ next:
limit := s.shouldLimit(c.Conn.RemoteAddr())
wr := io.Writer(c.Conn)
if limit && writeRateLimit != nil {
wr = &limitedWriter{c.Conn, writeRateLimit}
if limit && s.writeRateLimit != nil {
wr = NewWriteLimiter(c.Conn, s.writeRateLimit)
}
rd := io.Reader(c.Conn)
if limit && readRateLimit != nil {
rd = &limitedReader{c.Conn, readRateLimit}
if limit && s.readRateLimit != nil {
rd = NewReadLimiter(c.Conn, s.readRateLimit)
}
name := fmt.Sprintf("%s-%s (%s)", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), c.Type)
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression)
l.Infof("Established secure connection to %s at %s", remoteID, name)
if debugNet {
l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit)
}
l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit)
s.model.AddConnection(model.Connection{
c.Conn,
@@ -245,7 +263,7 @@ func (s *connectionSvc) connect() {
for {
nextDevice:
for deviceID, deviceCfg := range s.cfg.Devices() {
if deviceID == myID {
if deviceID == s.myID {
continue
}
@@ -287,18 +305,14 @@ func (s *connectionSvc) connect() {
dialer, ok := dialers[uri.Scheme]
if !ok {
l.Infoln("Unknown address schema", uri.String())
l.Infoln("Unknown address schema", uri)
continue
}
if debugNet {
l.Debugln("dial", deviceCfg.DeviceID, uri.String())
}
l.Debugln("dial", deviceCfg.DeviceID, uri)
conn, err := dialer(uri, s.tlsCfg)
if err != nil {
if debugNet {
l.Debugln("dial failed", deviceCfg.DeviceID, uri.String(), err)
}
l.Debugln("dial failed", deviceCfg.DeviceID, uri, err)
continue
}
@@ -323,11 +337,9 @@ func (s *connectionSvc) connect() {
reconIntv := time.Duration(s.cfg.Options().RelayReconnectIntervalM) * time.Minute
if last, ok := s.lastRelayCheck[deviceID]; ok && time.Since(last) < reconIntv {
if debugNet {
l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
}
l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
continue nextDevice
} else if debugNet {
} else {
l.Debugln("Trying relay connections to", deviceID, relays)
}
@@ -342,21 +354,17 @@ func (s *connectionSvc) connect() {
inv, err := client.GetInvitationFromRelay(uri, deviceID, s.tlsCfg.Certificates)
if err != nil {
if debugNet {
l.Debugf("Failed to get invitation for %s from %s: %v", deviceID, uri, err)
}
l.Debugf("Failed to get invitation for %s from %s: %v", deviceID, uri, err)
continue
} else if debugNet {
} else {
l.Debugln("Succesfully retrieved relay invitation", inv, "from", uri)
}
conn, err := client.JoinSession(inv)
if err != nil {
if debugNet {
l.Debugf("Failed to join relay session %s: %v", inv, err)
}
l.Debugf("Failed to join relay session %s: %v", inv, err)
continue
} else if debugNet {
} else {
l.Debugln("Sucessfully joined relay session", inv)
}
@@ -412,7 +420,7 @@ func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
if !ok {
return true
}
for _, lan := range lans {
for _, lan := range s.lans {
if lan.Contains(tcpaddr.IP) {
return false
}
@@ -444,3 +452,10 @@ func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool
return true
}
// serviceFunc wraps a function to create a suture.Service without stop
// functionality.
type serviceFunc func()
func (f serviceFunc) Serve() { f() }
func (f serviceFunc) Stop() {}
@@ -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 http://mozilla.org/MPL/2.0/.
package main
package connections
import (
"crypto/tls"
@@ -33,17 +33,13 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
raddr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
if debugNet {
l.Debugln(err)
}
l.Debugln(err)
return nil, err
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
if debugNet {
l.Debugln(err)
}
l.Debugln(err)
return nil, err
}
@@ -81,9 +77,7 @@ func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- model.Intermedia
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
l.Debugln("connect from", conn.RemoteAddr())
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
+22
View File
@@ -0,0 +1,22 @@
// 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 http://mozilla.org/MPL/2.0/.
package connections
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("connections", "Connection handling")
)
func init() {
l.SetDebug("connections", strings.Contains(os.Getenv("STTRACE"), "connections") || os.Getenv("STTRACE") == "all")
}
@@ -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 http://mozilla.org/MPL/2.0/.
package main
package connections
import (
"io"
@@ -12,13 +12,20 @@ import (
"github.com/juju/ratelimit"
)
type limitedReader struct {
r io.Reader
type LimitedReader struct {
reader io.Reader
bucket *ratelimit.Bucket
}
func (r *limitedReader) Read(buf []byte) (int, error) {
n, err := r.r.Read(buf)
func NewReadLimiter(r io.Reader, b *ratelimit.Bucket) *LimitedReader {
return &LimitedReader{
reader: r,
bucket: b,
}
}
func (r *LimitedReader) Read(buf []byte) (int, error) {
n, err := r.reader.Read(buf)
if r.bucket != nil {
r.bucket.Wait(int64(n))
}
@@ -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 http://mozilla.org/MPL/2.0/.
package main
package connections
import (
"io"
@@ -12,14 +12,21 @@ import (
"github.com/juju/ratelimit"
)
type limitedWriter struct {
w io.Writer
type LimitedWriter struct {
writer io.Writer
bucket *ratelimit.Bucket
}
func (w *limitedWriter) Write(buf []byte) (int, error) {
func NewWriteLimiter(w io.Writer, b *ratelimit.Bucket) *LimitedWriter {
return &LimitedWriter{
writer: w,
bucket: b,
}
}
func (w *LimitedWriter) Write(buf []byte) (int, error) {
if w.bucket != nil {
w.bucket.Wait(int64(len(buf)))
}
return w.w.Write(buf)
return w.writer.Write(buf)
}
+1 -1
View File
@@ -17,8 +17,8 @@ import (
"encoding/binary"
"fmt"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
+1 -1
View File
@@ -9,7 +9,7 @@ package db
import (
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
+6 -4
View File
@@ -10,11 +10,13 @@ import (
"os"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "files") || os.Getenv("STTRACE") == "all"
debugDB = strings.Contains(os.Getenv("STTRACE"), "db") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
l = logger.DefaultLogger.NewFacility("db", "The database layer")
)
func init() {
l.SetDebug("db", strings.Contains(os.Getenv("STTRACE"), "db") || os.Getenv("STTRACE") == "all")
}
+53 -151
View File
@@ -15,7 +15,7 @@ import (
"runtime"
"sort"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/iterator"
@@ -178,20 +178,14 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
limit := deviceKey(folder, device, []byte{0xff, 0xff, 0xff, 0xff}) // after all folder/device files
batch := new(leveldb.Batch)
if debugDB {
l.Debugf("new batch %p", batch)
}
l.Debugf("new batch %p", batch)
snap, err := db.GetSnapshot()
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -220,15 +214,11 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
cmp := bytes.Compare(newName, oldName)
if debugDB {
l.Debugf("generic replace; folder=%q device=%v moreFs=%v moreDb=%v cmp=%d newName=%q oldName=%q", folder, protocol.DeviceIDFromBytes(device), moreFs, moreDb, cmp, newName, oldName)
}
l.Debugf("generic replace; folder=%q device=%v moreFs=%v moreDb=%v cmp=%d newName=%q oldName=%q", folder, protocol.DeviceIDFromBytes(device), moreFs, moreDb, cmp, newName, oldName)
switch {
case moreFs && (!moreDb || cmp == -1):
if debugDB {
l.Debugln("generic replace; missing - insert")
}
l.Debugln("generic replace; missing - insert")
// Database is missing this file. Insert it.
if lv := ldbInsert(batch, folder, device, fs[fsi]); lv > maxLocalVer {
maxLocalVer = lv
@@ -244,15 +234,11 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
// File exists on both sides - compare versions. We might get an
// update with the same version and different flags if a device has
// marked a file as invalid, so handle that too.
if debugDB {
l.Debugln("generic replace; exists - compare")
}
l.Debugln("generic replace; exists - compare")
var ef FileInfoTruncated
ef.UnmarshalXDR(dbi.Value())
if !fs[fsi].Version.Equal(ef.Version) || fs[fsi].Flags != ef.Flags {
if debugDB {
l.Debugln("generic replace; differs - insert")
}
l.Debugln("generic replace; differs - insert")
if lv := ldbInsert(batch, folder, device, fs[fsi]); lv > maxLocalVer {
maxLocalVer = lv
}
@@ -261,7 +247,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
} else {
ldbUpdateGlobal(snap, batch, folder, device, fs[fsi])
}
} else if debugDB {
} else {
l.Debugln("generic replace; equal - ignore")
}
@@ -269,9 +255,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
moreDb = dbi.Next()
case moreDb && (!moreFs || cmp == 1):
if debugDB {
l.Debugln("generic replace; exists - remove")
}
l.Debugln("generic replace; exists - remove")
if lv := deleteFn(snap, batch, folder, device, oldName, dbi); lv > maxLocalVer {
maxLocalVer = lv
}
@@ -281,9 +265,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
// Write out and reuse the batch every few records, to avoid the batch
// growing too large and thus allocating unnecessarily much memory.
if batch.Len() > batchFlushSize {
if debugDB {
l.Debugf("db.Write %p", batch)
}
l.Debugf("db.Write %p", batch)
err = db.Write(batch, nil)
if err != nil {
@@ -294,9 +276,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
}
}
if debugDB {
l.Debugf("db.Write %p", batch)
}
l.Debugf("db.Write %p", batch)
err = db.Write(batch, nil)
if err != nil {
panic(err)
@@ -309,13 +289,9 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) i
// TODO: Return the remaining maxLocalVer?
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
// Database has a file that we are missing. Remove it.
if debugDB {
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
}
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
ldbRemoveFromGlobal(db, batch, folder, device, name)
if debugDB {
l.Debugf("batch.Delete %p %x", batch, dbi.Key())
}
l.Debugf("batch.Delete %p %x", batch, dbi.Key())
batch.Delete(dbi.Key())
return 0
})
@@ -325,20 +301,14 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
runtime.GC()
batch := new(leveldb.Batch)
if debugDB {
l.Debugf("new batch %p", batch)
}
l.Debugf("new batch %p", batch)
snap, err := db.GetSnapshot()
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -347,9 +317,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
for _, f := range fs {
name := []byte(f.Name)
fk = deviceKeyInto(fk[:cap(fk)], folder, device, name)
if debugDB {
l.Debugf("snap.Get %p %x", snap, fk)
}
l.Debugf("snap.Get %p %x", snap, fk)
bs, err := snap.Get(fk, nil)
if err == leveldb.ErrNotFound {
if lv := ldbInsert(batch, folder, device, f); lv > maxLocalVer {
@@ -384,9 +352,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
// Write out and reuse the batch every few records, to avoid the batch
// growing too large and thus allocating unnecessarily much memory.
if batch.Len() > batchFlushSize {
if debugDB {
l.Debugf("db.Write %p", batch)
}
l.Debugf("db.Write %p", batch)
err = db.Write(batch, nil)
if err != nil {
@@ -397,9 +363,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
}
}
if debugDB {
l.Debugf("db.Write %p", batch)
}
l.Debugf("db.Write %p", batch)
err = db.Write(batch, nil)
if err != nil {
panic(err)
@@ -409,9 +373,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
}
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) int64 {
if debugDB {
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
}
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
if file.LocalVersion == 0 {
file.LocalVersion = clock(0)
@@ -419,9 +381,7 @@ func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) in
name := []byte(file.Name)
nk := deviceKey(folder, device, name)
if debugDB {
l.Debugf("batch.Put %p %x", batch, nk)
}
l.Debugf("batch.Put %p %x", batch, nk)
batch.Put(nk, file.MustMarshalXDR())
return file.LocalVersion
@@ -431,9 +391,7 @@ func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) in
// file. If the device is already present in the list, the version is updated.
// If the file does not have an entry in the global list, it is created.
func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device []byte, file protocol.FileInfo) bool {
if debugDB {
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
}
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
name := []byte(file.Name)
gk := globalKey(folder, name)
svl, err := db.Get(gk, nil)
@@ -499,10 +457,8 @@ func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device []byte, file pr
fl.versions = append(fl.versions, nv)
done:
if debugDB {
l.Debugf("batch.Put %p %x", batch, gk)
l.Debugf("new global after update: %v", fl)
}
l.Debugf("batch.Put %p %x", batch, gk)
l.Debugf("new global after update: %v", fl)
batch.Put(gk, fl.MustMarshalXDR())
return true
@@ -519,9 +475,7 @@ func insertVersion(vl []fileVersion, i int, v fileVersion) []fileVersion {
// given file. If the version list is empty after this, the file entry is
// removed entirely.
func ldbRemoveFromGlobal(db dbReader, batch dbWriter, folder, device, file []byte) {
if debugDB {
l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file)
}
l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file)
gk := globalKey(folder, file)
svl, err := db.Get(gk, nil)
@@ -545,15 +499,11 @@ func ldbRemoveFromGlobal(db dbReader, batch dbWriter, folder, device, file []byt
}
if len(fl.versions) == 0 {
if debugDB {
l.Debugf("batch.Delete %p %x", batch, gk)
}
l.Debugf("batch.Delete %p %x", batch, gk)
batch.Delete(gk)
} else {
if debugDB {
l.Debugf("batch.Put %p %x", batch, gk)
l.Debugf("new global after remove: %v", fl)
}
l.Debugf("batch.Put %p %x", batch, gk)
l.Debugf("new global after remove: %v", fl)
batch.Put(gk, fl.MustMarshalXDR())
}
}
@@ -565,13 +515,9 @@ func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterat
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -598,13 +544,9 @@ func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []b
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -659,19 +601,13 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf,
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
if debugDB {
l.Debugf("snap.Get %p %x", snap, k)
}
l.Debugf("snap.Get %p %x", snap, k)
bs, err := snap.Get(k, nil)
if err == leveldb.ErrNotFound {
return nil, false
@@ -691,9 +627,7 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf,
}
k = deviceKey(folder, vl.versions[0].device, file)
if debugDB {
l.Debugf("snap.Get %p %x", snap, k)
}
l.Debugf("snap.Get %p %x", snap, k)
bs, err = snap.Get(k, nil)
if err != nil {
panic(err)
@@ -713,13 +647,9 @@ func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iter
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -739,9 +669,7 @@ func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iter
}
name := globalKeyName(dbi.Key())
fk = deviceKeyInto(fk[:cap(fk)], folder, vl.versions[0].device, name)
if debugDB {
l.Debugf("snap.Get %p %x", snap, fk)
}
l.Debugf("snap.Get %p %x", snap, fk)
bs, err := snap.Get(fk, nil)
if err != nil {
l.Debugf("folder: %q (%x)", folder, folder)
@@ -802,13 +730,9 @@ func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterat
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -854,9 +778,7 @@ nextFile:
continue nextFile
}
fk = deviceKeyInto(fk[:cap(fk)], folder, vl.versions[i].device, name)
if debugDB {
l.Debugf("snap.Get %p %x", snap, fk)
}
l.Debugf("snap.Get %p %x", snap, fk)
bs, err := snap.Get(fk, nil)
if err != nil {
var id protocol.DeviceID
@@ -886,9 +808,7 @@ nextFile:
continue nextFile
}
if debugDB {
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v haveV=%d globalV=%d", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveVersion, vl.versions[0].version)
}
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v haveV=%d globalV=%d", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveVersion, vl.versions[0].version)
if cont := fn(gf); !cont {
return
@@ -908,13 +828,9 @@ func ldbListFolders(db *leveldb.DB) []string {
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -945,13 +861,9 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -995,13 +907,9 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
if err != nil {
panic(err)
}
if debugDB {
l.Debugf("created snapshot %p", snap)
}
l.Debugf("created snapshot %p", snap)
defer func() {
if debugDB {
l.Debugf("close snapshot %p", snap)
}
l.Debugf("close snapshot %p", snap)
snap.Release()
}()
@@ -1011,9 +919,7 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
defer dbi.Release()
batch := new(leveldb.Batch)
if debugDB {
l.Debugf("new batch %p", batch)
}
l.Debugf("new batch %p", batch)
var fk []byte
for dbi.Next() {
@@ -1033,9 +939,7 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
var newVL versionList
for _, version := range vl.versions {
fk = deviceKeyInto(fk[:cap(fk)], folder, version.device, name)
if debugDB {
l.Debugf("snap.Get %p %x", snap, fk)
}
l.Debugf("snap.Get %p %x", snap, fk)
_, err := snap.Get(fk, nil)
if err == leveldb.ErrNotFound {
continue
@@ -1051,8 +955,6 @@ func ldbCheckGlobals(db *leveldb.DB, folder []byte) {
batch.Put(dbi.Key(), newVL.MustMarshalXDR())
}
}
if debugDB {
l.Infoln("db check completed for %q", folder)
}
l.Debugf("db check completed for %q", folder)
db.Write(batch, nil)
}
+11 -31
View File
@@ -13,8 +13,8 @@
package db
import (
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syndtr/goleveldb/leveldb"
)
@@ -62,18 +62,14 @@ func NewFileSet(folder string, db *leveldb.DB) *FileSet {
}
return true
})
if debug {
l.Debugf("loaded localVersion for %q: %#v", folder, s.localVersion)
}
l.Debugf("loaded localVersion for %q: %#v", folder, s.localVersion)
clock(s.localVersion[protocol.LocalDeviceID])
return &s
}
func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
if debug {
l.Debugf("%s Replace(%v, [%d])", s.folder, device, len(fs))
}
l.Debugf("%s Replace(%v, [%d])", s.folder, device, len(fs))
normalizeFilenames(fs)
s.mutex.Lock()
defer s.mutex.Unlock()
@@ -89,9 +85,7 @@ func (s *FileSet) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
}
func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
if debug {
l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
}
l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
normalizeFilenames(fs)
s.mutex.Lock()
defer s.mutex.Unlock()
@@ -114,51 +108,37 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
}
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithNeed(%v)", s.folder, device)
}
l.Debugf("%s WithNeed(%v)", s.folder, device)
ldbWithNeed(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
}
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
}
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
ldbWithNeed(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
}
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithHave(%v)", s.folder, device)
}
l.Debugf("%s WithHave(%v)", s.folder, device)
ldbWithHave(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
}
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
if debug {
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
}
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
ldbWithHave(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
}
func (s *FileSet) WithGlobal(fn Iterator) {
if debug {
l.Debugf("%s WithGlobal()", s.folder)
}
l.Debugf("%s WithGlobal()", s.folder)
ldbWithGlobal(s.db, []byte(s.folder), nil, false, nativeFileIterator(fn))
}
func (s *FileSet) WithGlobalTruncated(fn Iterator) {
if debug {
l.Debugf("%s WithGlobalTruncated()", s.folder)
}
l.Debugf("%s WithGlobalTruncated()", s.folder)
ldbWithGlobal(s.db, []byte(s.folder), nil, true, nativeFileIterator(fn))
}
func (s *FileSet) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
if debug {
l.Debugf("%s WithPrefixedGlobalTruncated()", s.folder, prefix)
}
l.Debugf("%s WithPrefixedGlobalTruncated()", s.folder, prefix)
ldbWithGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
}
+1 -1
View File
@@ -13,8 +13,8 @@ import (
"sort"
"testing"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
)
+1 -1
View File
@@ -6,7 +6,7 @@
package db
import "github.com/syncthing/protocol"
import "github.com/syncthing/syncthing/lib/protocol"
type FileInfoTruncated struct {
protocol.FileInfo
+3 -9
View File
@@ -34,9 +34,7 @@ func NewVirtualMtimeRepo(ldb *leveldb.DB, folder string) *VirtualMtimeRepo {
}
func (r *VirtualMtimeRepo) UpdateMtime(path string, diskMtime, actualMtime time.Time) {
if debug {
l.Debugf("virtual mtime: storing values for path:%s disk:%v actual:%v", path, diskMtime, actualMtime)
}
l.Debugf("virtual mtime: storing values for path:%s disk:%v actual:%v", path, diskMtime, actualMtime)
diskBytes, _ := diskMtime.MarshalBinary()
actualBytes, _ := actualMtime.MarshalBinary()
@@ -63,15 +61,11 @@ func (r *VirtualMtimeRepo) GetMtime(path string, diskMtime time.Time) time.Time
panic(fmt.Sprintf("Can't unmarshal stored mtime at path %s: %v", path, err))
}
if debug {
l.Debugf("virtual mtime: return %v instead of %v for path: %s", mtime, diskMtime, path)
}
l.Debugf("virtual mtime: return %v instead of %v for path: %s", mtime, diskMtime, path)
return mtime
}
if debug {
l.Debugf("virtual mtime: record exists, but mismatch inDisk: %v dbDisk: %v for path: %s", diskMtime, mtime, path)
}
l.Debugf("virtual mtime: record exists, but mismatch inDisk: %v dbDisk: %v for path: %s", diskMtime, mtime, path)
return diskMtime
}
+91 -20
View File
@@ -7,10 +7,11 @@
package discover
import (
"sort"
stdsync "sync"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
)
@@ -33,6 +34,14 @@ type cachedFinder struct {
Finder
cacheTime time.Duration
negCacheTime time.Duration
priority int
}
// A prioritizedAddress is what we use to sort addresses returned from
// different sources with different priorities.
type prioritizedAddress struct {
priority int
addr string
}
func NewCachingMux() *CachingMux {
@@ -43,9 +52,9 @@ func NewCachingMux() *CachingMux {
}
// Add registers a new Finder, with associated cache timeouts.
func (m *CachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration) {
func (m *CachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration, priority int) {
m.mut.Lock()
m.finders = append(m.finders, cachedFinder{finder, cacheTime, negCacheTime})
m.finders = append(m.finders, cachedFinder{finder, cacheTime, negCacheTime, priority})
m.caches = append(m.caches, newCache())
m.mut.Unlock()
@@ -57,6 +66,8 @@ func (m *CachingMux) Add(finder Finder, cacheTime, negCacheTime time.Duration) {
// Lookup attempts to resolve the device ID using any of the added Finders,
// while obeying the cache settings.
func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays []Relay, err error) {
var pdirect []prioritizedAddress
m.mut.Lock()
for i, finder := range m.finders {
if cacheEntry, ok := m.caches[i].Get(deviceID); ok {
@@ -64,11 +75,11 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
if cacheEntry.found && time.Since(cacheEntry.when) < finder.cacheTime {
// It's a positive, valid entry. Use it.
if debug {
l.Debugln("cached discovery entry for", deviceID, "at", finder.String())
l.Debugln(" ", cacheEntry)
l.Debugln("cached discovery entry for", deviceID, "at", finder)
l.Debugln(" cache:", cacheEntry)
for _, addr := range cacheEntry.Direct {
pdirect = append(pdirect, prioritizedAddress{finder.priority, addr})
}
direct = append(direct, cacheEntry.Direct...)
relays = append(relays, cacheEntry.Relays...)
continue
}
@@ -76,9 +87,7 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
if !cacheEntry.found && time.Since(cacheEntry.when) < finder.negCacheTime {
// It's a negative, valid entry. We should not make another
// attempt right now.
if debug {
l.Debugln("negative cache entry for", deviceID, "at", finder.String())
}
l.Debugln("negative cache entry for", deviceID, "at", finder)
continue
}
@@ -87,12 +96,12 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
// Perform the actual lookup and cache the result.
if td, tr, err := finder.Lookup(deviceID); err == nil {
if debug {
l.Debugln("lookup for", deviceID, "at", finder.String())
l.Debugln(" ", td)
l.Debugln(" ", tr)
l.Debugln("lookup for", deviceID, "at", finder)
l.Debugln(" direct:", td)
l.Debugln(" relays:", tr)
for _, addr := range td {
pdirect = append(pdirect, prioritizedAddress{finder.priority, addr})
}
direct = append(direct, td...)
relays = append(relays, tr...)
m.caches[i].Set(deviceID, CacheEntry{
Direct: td,
@@ -104,11 +113,11 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
}
m.mut.Unlock()
if debug {
l.Debugln("lookup results for", deviceID)
l.Debugln(" ", direct)
l.Debugln(" ", relays)
}
direct = uniqueSortedAddrs(pdirect)
relays = uniqueSortedRelays(relays)
l.Debugln("lookup results for", deviceID)
l.Debugln(" direct: ", direct)
l.Debugln(" relays: ", relays)
return direct, relays, nil
}
@@ -196,3 +205,65 @@ func (c *cache) Cache() map[protocol.DeviceID]CacheEntry {
c.mut.Unlock()
return m
}
func uniqueSortedAddrs(ss []prioritizedAddress) []string {
// We sort the addresses by priority, then filter them based on seen
// (first time seen is the on kept, so we retain priority).
sort.Sort(prioritizedAddressList(ss))
filtered := make([]string, 0, len(ss))
seen := make(map[string]struct{}, len(ss))
for _, s := range ss {
if _, ok := seen[s.addr]; !ok {
filtered = append(filtered, s.addr)
seen[s.addr] = struct{}{}
}
}
return filtered
}
func uniqueSortedRelays(rs []Relay) []Relay {
m := make(map[string]Relay, len(rs))
for _, r := range rs {
m[r.URL] = r
}
var ur = make([]Relay, 0, len(m))
for _, r := range m {
ur = append(ur, r)
}
sort.Sort(relayList(ur))
return ur
}
type relayList []Relay
func (l relayList) Len() int {
return len(l)
}
func (l relayList) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}
func (l relayList) Less(a, b int) bool {
return l[a].URL < l[b].URL
}
type prioritizedAddressList []prioritizedAddress
func (l prioritizedAddressList) Len() int {
return len(l)
}
func (l prioritizedAddressList) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}
func (l prioritizedAddressList) Less(a, b int) bool {
if l[a].priority != l[b].priority {
return l[a].priority < l[b].priority
}
return l[a].addr < l[b].addr
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package discover
import (
"reflect"
"testing"
"time"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestCacheUnique(t *testing.T) {
direct0 := []string{"tcp://192.0.2.44:22000", "tcp://192.0.2.42:22000"} // prio 0
direct1 := []string{"tcp://192.0.2.43:22000", "tcp://192.0.2.42:22000"} // prio 1
// what we expect from just direct0
direct0Sorted := []string{"tcp://192.0.2.42:22000", "tcp://192.0.2.44:22000"}
// what we expect from direct0+direct1
totalSorted := []string{
// first prio 0, sorted
"tcp://192.0.2.42:22000", "tcp://192.0.2.44:22000",
// then prio 1
"tcp://192.0.2.43:22000",
// no duplicate .42
}
relays := []Relay{{URL: "relay://192.0.2.44:443"}, {URL: "tcp://192.0.2.45:443"}}
c := NewCachingMux()
c.ServeBackground()
defer c.Stop()
// Add a fake discovery service and verify we get it's answers through the
// cache.
f1 := &fakeDiscovery{direct0, relays}
c.Add(f1, time.Minute, 0, 0)
dir, rel, err := c.Lookup(protocol.LocalDeviceID)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(dir, direct0Sorted) {
t.Errorf("Incorrect direct; %+v != %+v", dir, direct0Sorted)
}
if !reflect.DeepEqual(rel, relays) {
t.Errorf("Incorrect relays; %+v != %+v", rel, relays)
}
// Add one more that answers in the same way and check that we don't
// duplicate or otherwise mess up the responses now.
f2 := &fakeDiscovery{direct1, relays}
c.Add(f2, time.Minute, 0, 1)
dir, rel, err = c.Lookup(protocol.LocalDeviceID)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(dir, totalSorted) {
t.Errorf("Incorrect direct; %+v != %+v", dir, totalSorted)
}
if !reflect.DeepEqual(rel, relays) {
t.Errorf("Incorrect relays; %+v != %+v", rel, relays)
}
}
type fakeDiscovery struct {
direct []string
relays []Relay
}
func (f *fakeDiscovery) Lookup(deviceID protocol.DeviceID) (direct []string, relays []Relay, err error) {
return f.direct, f.relays, nil
}
func (f *fakeDiscovery) Error() error {
return nil
}
func (f *fakeDiscovery) String() string {
return "fake"
}
func (f *fakeDiscovery) Cache() map[protocol.DeviceID]CacheEntry {
return nil
}
+6 -3
View File
@@ -10,10 +10,13 @@ import (
"os"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "discover") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
l = logger.DefaultLogger.NewFacility("discover", "Remote device discovery")
)
func init() {
l.SetDebug("discover", strings.Contains(os.Getenv("STTRACE"), "discover") || os.Getenv("STTRACE") == "all")
}
+1 -1
View File
@@ -9,7 +9,7 @@ package discover
import (
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/thejerf/suture"
)
+10 -28
View File
@@ -18,8 +18,8 @@ import (
stdsync "sync"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
)
type globalClient struct {
@@ -124,16 +124,12 @@ func (c *globalClient) Lookup(device protocol.DeviceID) (direct []string, relays
resp, err := c.queryClient.Get(qURL.String())
if err != nil {
if debug {
l.Debugln("globalClient.Lookup", qURL.String(), err)
}
l.Debugln("globalClient.Lookup", qURL, err)
return nil, nil, err
}
if resp.StatusCode != 200 {
resp.Body.Close()
if debug {
l.Debugln("globalClient.Lookup", qURL.String(), resp.Status)
}
l.Debugln("globalClient.Lookup", qURL, resp.Status)
return nil, nil, errors.New(resp.Status)
}
@@ -198,9 +194,7 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
if len(ann.Direct)+len(ann.Relays) == 0 {
c.setError(errors.New("nothing to announce"))
if debug {
l.Debugln("Nothing to announce")
}
l.Debugln("Nothing to announce")
timer.Reset(announceErrorRetryInterval)
return
}
@@ -208,37 +202,27 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
// The marshal doesn't fail, I promise.
postData, _ := json.Marshal(ann)
if debug {
l.Debugf("Announcement: %s", postData)
}
l.Debugf("Announcement: %s", postData)
resp, err := c.announceClient.Post(c.server, "application/json", bytes.NewReader(postData))
if err != nil {
if debug {
l.Debugln("announce POST:", err)
}
l.Debugln("announce POST:", err)
c.setError(err)
timer.Reset(announceErrorRetryInterval)
return
}
if debug {
l.Debugln("announce POST:", resp.Status)
}
l.Debugln("announce POST:", resp.Status)
resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
if debug {
l.Debugln("announce POST:", resp.Status)
}
l.Debugln("announce POST:", resp.Status)
c.setError(errors.New(resp.Status))
if h := resp.Header.Get("Retry-After"); h != "" {
// The server has a recommendation on when we should
// retry. Follow it.
if secs, err := strconv.Atoi(h); err == nil && secs > 0 {
if debug {
l.Debugln("announce Retry-After:", secs, err)
}
l.Debugln("announce Retry-After:", secs, err)
timer.Reset(time.Duration(secs) * time.Second)
return
}
@@ -254,9 +238,7 @@ func (c *globalClient) sendAnnouncement(timer *time.Timer) {
// The server has a recommendation on when we should
// reannounce. Follow it.
if secs, err := strconv.Atoi(h); err == nil && secs > 0 {
if debug {
l.Debugln("announce Reannounce-After:", secs, err)
}
l.Debugln("announce Reannounce-After:", secs, err)
timer.Reset(time.Duration(secs) * time.Second)
return
}
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"testing"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/tlsutil"
)
+14 -13
View File
@@ -17,9 +17,9 @@ import (
"strconv"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/beacon"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/thejerf/suture"
)
@@ -117,7 +117,12 @@ func (c *localClient) Error() error {
}
func (c *localClient) announcementPkt() Announce {
addrs := c.addrList.AllAddresses()
var addrs []Address
for _, addr := range c.addrList.AllAddresses() {
addrs = append(addrs, Address{
URL: addr,
})
}
var relays []Relay
for _, relay := range c.relayStat.Relays() {
@@ -161,15 +166,11 @@ func (c *localClient) recvAnnouncements(b beacon.Interface) {
var pkt Announce
err := pkt.UnmarshalXDR(buf)
if err != nil && err != io.EOF {
if debug {
l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
}
l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
continue
}
if debug {
l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.This.ID))
}
l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.This.ID))
var newDevice bool
if bytes.Compare(pkt.This.ID, c.myID[:]) != 0 {
@@ -198,7 +199,7 @@ func (c *localClient) registerDevice(src net.Addr, device Device) bool {
var validAddresses []string
for _, addr := range device.Addresses {
u, err := url.Parse(addr)
u, err := url.Parse(addr.URL)
if err != nil {
continue
}
@@ -213,10 +214,10 @@ func (c *localClient) registerDevice(src net.Addr, device Device) bool {
if err != nil {
continue
}
u.Host = fmt.Sprintf("%s:%d", host, tcpAddr.Port)
u.Host = net.JoinHostPort(host, strconv.Itoa(tcpAddr.Port))
validAddresses = append(validAddresses, u.String())
} else {
validAddresses = append(validAddresses, addr)
validAddresses = append(validAddresses, addr.URL)
}
}
@@ -242,9 +243,9 @@ func addrToAddr(addr *net.TCPAddr) string {
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
return fmt.Sprintf(":%c", addr.Port)
} else if bs := addr.IP.To4(); bs != nil {
return fmt.Sprintf("%s:%c", bs.String(), addr.Port)
return net.JoinHostPort(bs.String(), strconv.Itoa(addr.Port))
} else if bs := addr.IP.To16(); bs != nil {
return fmt.Sprintf("[%s]:%c", bs.String(), addr.Port)
return net.JoinHostPort(bs.String(), strconv.Itoa(addr.Port))
}
return ""
}
+10 -12
View File
@@ -11,27 +11,25 @@ package discover
const (
AnnouncementMagic = 0x9D79BC40
QueryMagic = 0x2CA856F6
)
type Query struct {
Magic uint32
DeviceID []byte // max:32
}
type Announce struct {
Magic uint32
This Device
Extra []Device // max:16
}
type Device struct {
ID []byte // max:32
Addresses []Address // max:16
Relays []Relay // max:16
}
type Address struct {
URL string // max:2083
}
type Relay struct {
URL string `json:"url"` // max:2083
Latency int32 `json:"latency"`
}
type Device struct {
ID []byte // max:32
Addresses []string // max:16
Relays []Relay // max:16
}
+151 -155
View File
@@ -13,80 +13,6 @@ import (
/*
Query Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Device ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Device ID (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Query {
unsigned int Magic;
opaque DeviceID<32>;
}
*/
func (o Query) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Query) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Query) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Query) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Query) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteUint32(o.Magic)
if l := len(o.DeviceID); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("DeviceID", l, 32)
}
xw.WriteBytes(o.DeviceID)
return xw.Tot(), xw.Error()
}
func (o *Query) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Query) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Query) DecodeXDRFrom(xr *xdr.Reader) error {
o.Magic = xr.ReadUint32()
o.DeviceID = xr.ReadBytesMax(32)
return xr.Error()
}
/*
Announce Structure:
0 1 2 3
@@ -187,80 +113,6 @@ func (o *Announce) DecodeXDRFrom(xr *xdr.Reader) error {
/*
Relay Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of URL |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ URL (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Latency |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Relay {
string URL<256>;
int Latency;
}
*/
func (o Relay) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Relay) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Relay) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Relay) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Relay) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.URL); l > 256 {
return xw.Tot(), xdr.ElementSizeExceeded("URL", l, 256)
}
xw.WriteString(o.URL)
xw.WriteUint32(uint32(o.Latency))
return xw.Tot(), xw.Error()
}
func (o *Relay) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
o.URL = xr.ReadStringMax(256)
o.Latency = int32(xr.ReadUint32())
return xr.Error()
}
/*
Device Structure:
0 1 2 3
@@ -274,10 +126,8 @@ Device Structure:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Addresses (variable length) \
\ Zero or more Address Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Relays |
@@ -290,7 +140,7 @@ Device Structure:
struct Device {
opaque ID<32>;
string Addresses<16>;
Address Addresses<16>;
Relay Relays<16>;
}
@@ -330,7 +180,10 @@ func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
}
xw.WriteUint32(uint32(len(o.Addresses)))
for i := range o.Addresses {
xw.WriteString(o.Addresses[i])
_, err := o.Addresses[i].EncodeXDRInto(xw)
if err != nil {
return xw.Tot(), err
}
}
if l := len(o.Relays); l > 16 {
return xw.Tot(), xdr.ElementSizeExceeded("Relays", l, 16)
@@ -365,9 +218,9 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
if _AddressesSize > 16 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
}
o.Addresses = make([]string, _AddressesSize)
o.Addresses = make([]Address, _AddressesSize)
for i := range o.Addresses {
o.Addresses[i] = xr.ReadString()
(&o.Addresses[i]).DecodeXDRFrom(xr)
}
_RelaysSize := int(xr.ReadUint32())
if _RelaysSize < 0 {
@@ -382,3 +235,146 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
}
return xr.Error()
}
/*
Address Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of URL |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ URL (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Address {
string URL<2083>;
}
*/
func (o Address) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Address) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Address) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Address) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Address) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.URL); l > 2083 {
return xw.Tot(), xdr.ElementSizeExceeded("URL", l, 2083)
}
xw.WriteString(o.URL)
return xw.Tot(), xw.Error()
}
func (o *Address) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Address) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Address) DecodeXDRFrom(xr *xdr.Reader) error {
o.URL = xr.ReadStringMax(2083)
return xr.Error()
}
/*
Relay Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of URL |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ URL (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Latency |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Relay {
string URL<2083>;
int Latency;
}
*/
func (o Relay) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Relay) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Relay) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Relay) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Relay) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.URL); l > 2083 {
return xw.Tot(), xdr.ElementSizeExceeded("URL", l, 2083)
}
xw.WriteString(o.URL)
xw.WriteUint32(uint32(o.Latency))
return xw.Tot(), xw.Error()
}
func (o *Relay) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
o.URL = xr.ReadStringMax(2083)
o.Latency = int32(xr.ReadUint32())
return xr.Error()
}
+6 -3
View File
@@ -10,10 +10,13 @@ import (
"os"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/syncthing/lib/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "events") || os.Getenv("STTRACE") == "all"
dl = logger.DefaultLogger
dl = logger.DefaultLogger.NewFacility("events", "Event generation and logging")
)
func init() {
dl.SetDebug("events", strings.Contains(os.Getenv("STTRACE"), "events") || os.Getenv("STTRACE") == "all")
}
+15 -19
View File
@@ -105,7 +105,7 @@ func (t EventType) MarshalText() ([]byte, error) {
const BufferSize = 64
type Logger struct {
subs map[int]*Subscription
subs []*Subscription
nextID int
mutex sync.Mutex
}
@@ -119,7 +119,6 @@ type Event struct {
type Subscription struct {
mask EventType
id int
events chan Event
timeout *time.Timer
}
@@ -133,23 +132,20 @@ var (
func NewLogger() *Logger {
return &Logger{
subs: make(map[int]*Subscription),
mutex: sync.NewMutex(),
}
}
func (l *Logger) Log(t EventType, data interface{}) {
l.mutex.Lock()
if debug {
dl.Debugln("log", l.nextID, t.String(), data)
}
dl.Debugln("log", l.nextID, t, data)
l.nextID++
e := Event{
ID: l.nextID,
Time: time.Now(),
Type: t,
Data: data,
}
l.nextID++
for _, s := range l.subs {
if s.mask&t != 0 {
select {
@@ -164,27 +160,29 @@ func (l *Logger) Log(t EventType, data interface{}) {
func (l *Logger) Subscribe(mask EventType) *Subscription {
l.mutex.Lock()
if debug {
dl.Debugln("subscribe", mask)
}
dl.Debugln("subscribe", mask)
s := &Subscription{
mask: mask,
id: l.nextID,
events: make(chan Event, BufferSize),
timeout: time.NewTimer(0),
}
l.nextID++
l.subs[s.id] = s
l.subs = append(l.subs, s)
l.mutex.Unlock()
return s
}
func (l *Logger) Unsubscribe(s *Subscription) {
l.mutex.Lock()
if debug {
dl.Debugln("unsubscribe")
dl.Debugln("unsubscribe")
for i, ss := range l.subs {
if s == ss {
last := len(l.subs) - 1
l.subs[i] = l.subs[last]
l.subs[last] = nil
l.subs = l.subs[:last]
break
}
}
delete(l.subs, s.id)
close(s.events)
l.mutex.Unlock()
}
@@ -193,9 +191,7 @@ func (l *Logger) Unsubscribe(s *Subscription) {
// out of the event channel is closed. Poll should not be called concurrently
// from multiple goroutines for a single subscription.
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
if debug {
dl.Debugln("poll", timeout)
}
dl.Debugln("poll", timeout)
if !s.timeout.Reset(timeout) {
select {
+3 -2
View File
@@ -134,6 +134,7 @@ func TestIDs(t *testing.T) {
s := l.Subscribe(events.AllEvents)
defer l.Unsubscribe(s)
l.Log(events.DeviceConnected, "foo")
_ = l.Subscribe(events.AllEvents)
l.Log(events.DeviceConnected, "bar")
ev, err := s.Poll(timeout)
@@ -152,8 +153,8 @@ func TestIDs(t *testing.T) {
if ev.Data.(string) != "bar" {
t.Fatal("Incorrect event:", ev)
}
if !(ev.ID > id) {
t.Fatalf("ID not incremented (%d !> %d)", ev.ID, id)
if ev.ID != id+1 {
t.Fatalf("ID not incremented (%d != %d)", ev.ID, id+1)
}
}
+8 -9
View File
@@ -12,7 +12,6 @@ import (
"crypto/md5"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
@@ -217,28 +216,28 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
// Pattern is rooted in the current dir only
exp, err := fnmatch.Convert(line[1:], flags)
if err != nil {
return fmt.Errorf("Invalid pattern %q in ignore file", line)
return fmt.Errorf("invalid pattern %q in ignore file", line)
}
patterns = append(patterns, Pattern{exp, include})
} else if strings.HasPrefix(line, "**/") {
// Add the pattern as is, and without **/ so it matches in current dir
exp, err := fnmatch.Convert(line, flags)
if err != nil {
return fmt.Errorf("Invalid pattern %q in ignore file", line)
return fmt.Errorf("invalid pattern %q in ignore file", line)
}
patterns = append(patterns, Pattern{exp, include})
exp, err = fnmatch.Convert(line[3:], flags)
if err != nil {
return fmt.Errorf("Invalid pattern %q in ignore file", line)
return fmt.Errorf("invalid pattern %q in ignore file", line)
}
patterns = append(patterns, Pattern{exp, include})
} else if strings.HasPrefix(line, "#include ") {
includeFile := filepath.Join(filepath.Dir(currentFile), line[len("#include "):])
includeRel := line[len("#include "):]
includeFile := filepath.Join(filepath.Dir(currentFile), includeRel)
includes, err := loadIgnoreFile(includeFile, seen)
if err != nil {
log.Println(err)
return err
return fmt.Errorf("include of %q: %v", includeRel, err)
}
patterns = append(patterns, includes...)
} else {
@@ -246,13 +245,13 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
// current directory and subdirs.
exp, err := fnmatch.Convert(line, flags)
if err != nil {
return fmt.Errorf("Invalid pattern %q in ignore file", line)
return fmt.Errorf("invalid pattern %q in ignore file", line)
}
patterns = append(patterns, Pattern{exp, include})
exp, err = fnmatch.Convert("**/"+line, flags)
if err != nil {
return fmt.Errorf("Invalid pattern %q in ignore file", line)
return fmt.Errorf("invalid pattern %q in ignore file", line)
}
patterns = append(patterns, Pattern{exp, include})
}

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