Compare commits

..

40 Commits

Author SHA1 Message Date
Simon Frei e4ab9d3312 lib/model: Pass correct file info to deleteItemOnDisk (fixes #5616) (#5617) 2019-03-25 12:43:21 +01:00
Jakob Borg 24f41e169a gui: Add missing quote char 2019-03-25 12:37:10 +01:00
Simon Frei 50d8c43e7c lib/config: Set UseLargeBlocks to true by default (fixes #5599) (#5600) 2019-03-12 12:59:26 +00:00
Evgeny Kuznetsov 90d9b2de2b cmd/syncthing: Add a check for particular far-future version of config (fixes #1101) 2019-03-12 08:20:47 +00:00
Evgeny Kuznetsov 04f05f102d cmd: Add check for newer config file and an option to override it (fixes #4921) (#5597)
* Add check for newer config file and override option

* Expanded error message

* Polish previous commits

* Make it newER
2019-03-12 07:12:08 +00:00
Simon Frei 289a02e994 lib/model: Integrate stat refs in folder (#5596) 2019-03-11 16:57:21 +00:00
georgespatton 84fe285659 etc: Systemd unit should declare after=multiuser.target (fixes #5346) (#5593) 2019-03-11 15:50:34 +01:00
Simon Frei 445637ebec lib/model: Pass fset & ignores on folder creation (#5592) 2019-03-11 07:28:54 +01:00
Simon Frei 3f3d2c814b lib/model: Remove unused code (#5591) 2019-03-10 17:05:39 +01:00
Simon Frei 189e44488e lib/model: Introduce must test utility (#5586)
* lib/model: Introduce must test utility

* nice
2019-03-09 18:45:36 +00:00
Simon Frei 27ff20faa3 lib/model: Introduce waitForState test utility (#5585)
* lib/model: Introduce waitForState test utility

* folder id as param to waitForState
2019-03-09 10:36:55 +00:00
Simon Frei b1564e53e4 lib/model: Improve test utilities (#5584) 2019-03-08 20:29:09 +00:00
Simon Frei 3a75b63776 lib/model: Use temp dir from osutils in tests (#5581) 2019-03-07 16:34:41 +01:00
Simon Frei 8e238c8e48 lib/model: Check before replacing existing file on pull (fixes #5571) (#5567) 2019-03-07 15:15:14 +01:00
Jakob Borg 3d5af675db gui, man, authors: Update docs, translations, and contributors 2019-03-06 07:45:23 +01:00
Jakob Borg 9da3273eb8 lib/model: Clarify fileInfoBatch.flushIfFull criteria 2019-03-05 21:34:04 +01:00
Simon Frei bd37f6da17 lib/model: Optimize dbUpdaterRoutine (#5576) 2019-03-05 21:32:37 +01:00
Simon Frei 6940d79f5b lib/model: Use errors.Wrap for pull errors (#5563) 2019-03-04 13:01:52 +00:00
Simon Frei 0f80318ef6 lib/scanner: Consistenlty use CreateFileInfo and remove outdated comment (#5574) 2019-03-04 13:27:33 +01:00
Simon Frei d6622b1f68 lib/model: setUp -> setup (#5573) 2019-03-04 12:27:10 +00:00
Simon Frei 43bcb3d5a5 lib/model: Refactor conflict name handling (#5572) 2019-03-04 12:20:40 +00:00
Evgeny Kuznetsov e2e8f6e940 gui: Update copyright notices (fixes #5569) (#5570) 2019-03-03 13:15:17 +01:00
Jakob Borg 88b0ce892d gui, man, authors: Update docs, translations, and contributors 2019-02-27 07:45:23 +01:00
otbutz 55cd4b3d9b gui: Use handshake icon for "Introduced by" (fixes #5560) (#5561) 2019-02-26 14:18:35 +01:00
Jakob Borg f24676ba5a lib/tlsutil: Enable TLS 1.3 when available, on test builds (fixes #5065) (#5558)
* lib/tlsutil: Enable TLS 1.3 when available, on test builds (fixes #5065)

This enables TLS 1.3 negotiation on Go 1.12 by setting the GODEBUG
variable. For now, this just gets enabled on test versions (those with a
dash in the version number).

Users wishing to enable this on production builds can set GODEBUG
manually.

The string representation of connections now includes the TLS version
and cipher suite. This becomes part of the log output on connections.
That is, when talking to an old client:

    Established secure connection .../TLS1.2-TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256

and now potentially:

    Established secure connection .../TLS1.3-TLS_AES_128_GCM_SHA256

(The cipher suite was there previously in the log output, but not the
TLS version.)

I also added this info as a new Crypto() method on the connection, and
propagate this out to the API and GUI, where it can be seen in the
connection address hover (although with bad word wrapping sometimes).

* wip

* wip
2019-02-26 11:49:02 +01:00
Simon Frei 722b3fce6a all: Hide implementations behind interfaces for mocked testing (#5548)
* lib/model: Hide implementations behind interfaces for mocked testing

* review
2019-02-26 08:09:25 +00:00
Jonas Thelemann 8a05492622 docs: Correct Docker README (#5480) (#5545)
Plus some formatting.
2019-02-26 00:37:59 +04:00
Jakob Borg 63c4e7f6d0 Merge branch 'release'
* release:
  lib/scanner: Use standard adler32 when we don't need rolling (#5556)
2019-02-25 19:36:41 +01:00
Audrius Butkevicius fafd30f804 lib/scanner: Use standard adler32 when we don't need rolling (#5556)
* lib/scanner: Use standard adler32 when we don't need rolling

Seems the rolling adler32 implementation is super slow when executed on large blocks, even tho I can't explain why.

BenchmarkFind1MFile-16    				     100	  18991667 ns/op	  55.21 MB/s	  398844 B/op	      20 allocs/op
BenchmarkBlock/adler32-131072/#00-16     		     200	   9726519 ns/op	1078.06 MB/s	 2654936 B/op	     163 allocs/op
BenchmarkBlock/bozo32-131072/#00-16      		      20	  73435540 ns/op	 142.79 MB/s	 2654928 B/op	     163 allocs/op
BenchmarkBlock/buzhash32-131072/#00-16   		      20	  61482005 ns/op	 170.55 MB/s	 2654928 B/op	     163 allocs/op
BenchmarkBlock/buzhash64-131072/#00-16   		      20	  61673660 ns/op	 170.02 MB/s	 2654928 B/op	     163 allocs/op
BenchmarkBlock/vanilla-adler32-131072/#00-16         	     300	   4377307 ns/op	2395.48 MB/s	 2654935 B/op	     163 allocs/op
BenchmarkBlock/adler32-16777216/#00-16               	       2	 544010100 ns/op	  19.27 MB/s	   65624 B/op	       5 allocs/op
BenchmarkBlock/bozo32-16777216/#00-16                	       1	4678108500 ns/op	   2.24 MB/s	51970144 B/op	      24 allocs/op
BenchmarkBlock/buzhash32-16777216/#00-16             	       1	3880370700 ns/op	   2.70 MB/s	51970144 B/op	      24 allocs/op
BenchmarkBlock/buzhash64-16777216/#00-16             	       1	3875911700 ns/op	   2.71 MB/s	51970144 B/op	      24 allocs/op
BenchmarkBlock/vanilla-adler32-16777216/#00-16       	     300	   4010279 ns/op	2614.72 MB/s	   65624 B/op	       5 allocs/op
BenchmarkRoll/adler32-131072/#00-16                  	    2000	    974279 ns/op	 134.53 MB/s	     270 B/op	       0 allocs/op
BenchmarkRoll/bozo32-131072/#00-16                   	    2000	    791770 ns/op	 165.54 MB/s	     270 B/op	       0 allocs/op
BenchmarkRoll/buzhash32-131072/#00-16                	    2000	    917409 ns/op	 142.87 MB/s	     270 B/op	       0 allocs/op
BenchmarkRoll/buzhash64-131072/#00-16                	    2000	    881125 ns/op	 148.76 MB/s	     270 B/op	       0 allocs/op
BenchmarkRoll/adler32-16777216/#00-16                	      10	 124000400 ns/op	 135.30 MB/s	 7548937 B/op	       0 allocs/op
BenchmarkRoll/bozo32-16777216/#00-16                 	      10	 118008080 ns/op	 142.17 MB/s	 7548928 B/op	       0 allocs/op
BenchmarkRoll/buzhash32-16777216/#00-16              	      10	 126794440 ns/op	 132.32 MB/s	 7548928 B/op	       0 allocs/op
BenchmarkRoll/buzhash64-16777216/#00-16              	      10	 126631960 ns/op	 132.49 MB/s	 7548928 B/op	       0 allocs/op

* Update benchmark_test.go

* gofmt

* fixup benchmark
2019-02-25 13:29:31 +04:00
Simon Frei ad5a046843 lib/fs: Rename fsFile* to basicFile* (#5546) 2019-02-24 18:02:02 +01:00
Jakob Borg 4b8853bfde gui, man, authors: Update docs, translations, and contributors 2019-02-20 07:45:22 +01:00
Evgeny Kuznetsov 648fcf2c45 etc: Remove unnecessary quotes in .desktop (#5540) 2019-02-15 07:35:19 +00:00
Simon Frei ca3ae64bbf lib/db: Flush batch based on size and refactor (fixes #5531) (#5536)
Flush the batch when exceeding a certain size, instead of when reaching a number
of batched operations.
Move batch to lowlevel to be able to use it in NamespacedKV.
Increase the leveldb memory buffer from 4 to 16 MiB.
2019-02-14 23:15:13 +00:00
Simon Frei e2204d0071 build: Add option to get test coverage (#5539) 2019-02-14 22:38:47 +00:00
Simon Frei d5ff2c41dc all: Get rid of fatal logging (#5537)
* cleanup Fatal in lib/config/config.go

* cleanup Fatal in lib/config/folderconfiguration.go

* cleanup Fatal in lib/model/model.go

* cleanup Fatal in cmd/syncthing/monitor.go

* cleanup Fatal in cmd/syncthing/main.go

* cleanup Fatal in lib/api

* remove Fatal methods from logger

* lowercase in errors.Wrap

* one less channel
2019-02-14 20:29:14 +00:00
Simon Frei 7a40c42e8b cmd/syncthing: Introduce exiter to handle termination (#5532) 2019-02-13 23:07:27 +00:00
Simon Frei 905c3594b0 lib/model: Various model test fixes and polish (#5528)
* lib/model: Various model test fixes and polish

Missing calls to m.Stop()
Don't fail test if temporary test dir cleanup fails

* drop lazyness
2019-02-13 18:54:04 +00:00
Jakob Borg 5fd333e4f7 gui, man, authors: Update docs, translations, and contributors 2019-02-13 07:45:23 +01:00
Simon Frei 225c0dda80 lib/model: Scan conflicts after creation (#5511)
Also unflakes and improve TestRequestRemoteRenameChanged.
2019-02-12 16:05:20 +01:00
Simon Frei 5fd2cab102 lib/model: Run more tests in tmp dir (#5527) 2019-02-12 16:04:04 +01:00
104 changed files with 2071 additions and 1802 deletions
+5
View File
@@ -0,0 +1,5 @@
coverage:
range: "40...100"
ignore:
- "**.pb.go"
+5 -1
View File
@@ -70,6 +70,7 @@ Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
Elliot Huffman <thelich2@gmail.com>
Emil Hessman (ceh) <emil@hessman.se>
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
Evgeny Kuznetsov <evgeny@kuznetsov.md>
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
Felix Ableitner (Nutomic) <me@nutomic.com>
Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
@@ -83,7 +84,7 @@ Heiko Zuerker (Smiley73) <heiko@zuerker.org>
Hugo Locurcio <hugo.locurcio@hugo.pro>
Iain Barnett <iainspeed@gmail.com>
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
Iskander (Alex) Sharipov <quasilyte@gmail.com>
Iskander Sharipov (Alex) <quasilyte@gmail.com>
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
Jake Peterson (acogdev) <jake@acogdev.com>
@@ -99,6 +100,7 @@ Jochen Voss (seehuhn) <voss@seehuhn.de>
Johan Andersson <j@i19.se>
Johan Vromans (sciurius) <jvromans@squirrel.nl>
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
Jonas Thelemann <e-mail@jonas-thelemann.de>
Jonathan Cross <jcross@gmail.com>
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
Jörg Thalheim <Mic92@users.noreply.github.com>
@@ -126,6 +128,7 @@ Mark Pulford (mpx) <mark@kyne.com.au>
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
Matic Potočnik <hairyfotr@gmail.com>
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
Matt Robenolt <matt@ydekproductions.com>
Matteo Ruina <matteo.ruina@gmail.com>
Maurizio Tomasi <ziotom78@gmail.com>
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
@@ -143,6 +146,7 @@ Nicolas Braud-Santoni <nicolas@braud-santoni.eu>
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
Nils Jakobi (thunderstorm99) <jakobi.nils@gmail.com>
NoLooseEnds <jon.koslung@gmail.com>
otbutz <tbutz@optitool.de>
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
+5 -14
View File
@@ -1,39 +1,30 @@
# Docker Container for Syncthing
Use the Dockerfile in this repo, or pull the `syncthing/syncthing` image
from Docker Hub. Use volumes to have the synchronized files available on the
host.
from Docker Hub.
The exposed volumes are by default:
/var/syncthing/config - the configuration and index directory into the Container
/var/syncthing - the default sync folder into the Container
You can add more folders and map them as you prefer.
Use the `/var/syncthing` volume to have the synchronized files available on the
host. You can add more folders and map them as you prefer.
Note that Syncthing runs as UID 1000 and GID 1000 by default. These may be
altered with the ``PUID`` and ``PGID`` environment variables.
Example usage:
## Example Usage
```
$ docker pull syncthing/syncthing
$ docker run -p 8384:8384 -p 22000:22000 \
-v /wherever/st-cfg:/var/syncthing/config \
-v /wherever/st-sync:/var/syncthing \
syncthing/syncthing:latest
```
Note that local device discovery will not work with the above command resulting
in poor local transfer rates if local device addresses are not manually
configured.
Note that local device discovery will not work with the above command, resulting in poor local transfer rates if local device addresses are not manually configured.
To allow local discovery, the docker host network can be used instead:
```
$ docker pull syncthing/syncthing
$ docker run --network=host \
-v /wherever/st-cfg:/var/syncthing/config \
-v /wherever/st-sync:/var/syncthing \
syncthing/syncthing:latest
```
+13 -9
View File
@@ -48,6 +48,7 @@ var (
pkgdir string
cc string
debugBinary bool
coverage bool
timeout = "120s"
gogoProtoVersion = "v1.2.0"
)
@@ -330,24 +331,27 @@ func parseFlags() {
flag.StringVar(&pkgdir, "pkgdir", "", "Set -pkgdir parameter for `go build`")
flag.StringVar(&cc, "cc", os.Getenv("CC"), "Set CC environment variable for `go build`")
flag.BoolVar(&debugBinary, "debug-binary", debugBinary, "Create unoptimized binary to use with delve, set -gcflags='-N -l' and omit -ldflags")
flag.BoolVar(&coverage, "coverage", coverage, "Write coverage profile of tests to coverage.txt")
flag.Parse()
}
func test(pkgs ...string) {
lazyRebuildAssets()
useRace := runtime.GOARCH == "amd64"
switch runtime.GOOS {
case "darwin", "linux", "freebsd": // , "windows": # See https://github.com/golang/go/issues/27089
default:
useRace = false
args := []string{"test", "-short", "-timeout", timeout, "-tags", "purego"}
if runtime.GOARCH == "amd64" {
switch runtime.GOOS {
case "darwin", "linux", "freebsd": // , "windows": # See https://github.com/golang/go/issues/27089
args = append(args, "-race")
}
}
if useRace {
runPrint(goCmd, append([]string{"test", "-short", "-race", "-timeout", timeout, "-tags", "purego"}, pkgs...)...)
} else {
runPrint(goCmd, append([]string{"test", "-short", "-timeout", timeout, "-tags", "purego"}, pkgs...)...)
if coverage {
args = append(args, "-covermode", "atomic", "-coverprofile", "coverage.txt")
}
runPrint(goCmd, append(args, pkgs...)...)
}
func bench(pkgs ...string) {
+32 -73
View File
@@ -40,11 +40,9 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/versioner"
"github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt"
)
@@ -64,90 +62,33 @@ const (
type apiService struct {
id protocol.DeviceID
cfg configIntf
cfg config.Wrapper
httpsCertFile string
httpsKeyFile string
statics *staticsServer
model modelIntf
model model.Model
eventSubs map[events.EventType]events.BufferedSubscription
eventSubsMut sync.Mutex
discoverer discover.CachingMux
connectionsService connectionsIntf
connectionsService connections.Service
fss *folderSummaryService
systemConfigMut sync.Mutex // serializes posts to /rest/system/config
stop chan struct{} // signals intentional stop
configChanged chan struct{} // signals intentional listener close due to config change
started chan string // signals startup complete by sending the listener address, for testing only
startedOnce chan struct{} // the service has started successfully at least once
startedOnce chan struct{} // the service has started at least once
startupErr error
cpu rater
guiErrors logger.Recorder
systemLog logger.Recorder
}
type modelIntf interface {
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
Completion(device protocol.DeviceID, folder string) model.FolderCompletion
Override(folder string)
Revert(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated
NeedSize(folder string) db.Counts
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
ResetFolder(folder string)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []model.Availability
GetIgnores(folder string) ([]string, []string, error)
GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
SetIgnores(folder string, content []string) error
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
ScanFolderSubdirs(folder string, subs []string) error
BringToFront(folder, file string)
Connection(deviceID protocol.DeviceID) (connections.Connection, bool)
GlobalSize(folder string) db.Counts
LocalSize(folder string) db.Counts
ReceiveOnlyChangedSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool)
State(folder string) (string, time.Time, error)
UsageReportingStats(version int, preview bool) map[string]interface{}
FolderErrors(folder string) ([]model.FileError, error)
WatchError(folder string) error
}
type configIntf interface {
GUI() config.GUIConfiguration
LDAP() config.LDAPConfiguration
RawCopy() config.Configuration
Options() config.OptionsConfiguration
Replace(cfg config.Configuration) (config.Waiter, error)
Subscribe(c config.Committer)
Folders() map[string]config.FolderConfiguration
Devices() map[protocol.DeviceID]config.DeviceConfiguration
SetDevice(config.DeviceConfiguration) (config.Waiter, error)
SetDevices([]config.DeviceConfiguration) (config.Waiter, error)
Save() error
ListenAddresses() []string
RequiresRestart() bool
}
type connectionsIntf interface {
Status() map[string]interface{}
NATType() string
}
type rater interface {
Rate() float64
}
func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder, cpu rater) *apiService {
func newAPIService(id protocol.DeviceID, cfg config.Wrapper, httpsCertFile, httpsKeyFile, assetDir string, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, errors, systemLog logger.Recorder, cpu rater) *apiService {
service := &apiService{
id: id,
cfg: cfg,
@@ -174,6 +115,11 @@ func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKey
return service
}
func (s *apiService) WaitForStart() error {
<-s.startedOnce
return s.startupErr
}
func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
cert, err := tls.LoadX509KeyPair(s.httpsCertFile, s.httpsKeyFile)
if err != nil {
@@ -236,14 +182,15 @@ func (s *apiService) Serve() {
// We let this be a loud user-visible warning as it may be the only
// indication they get that the GUI won't be available.
l.Warnln("Starting API/GUI:", err)
return
default:
// This is during initialization. A failure here should be fatal
// as there will be no way for the user to communicate with us
// otherwise anyway.
l.Fatalln("Starting API/GUI:", err)
s.startupErr = err
close(s.startedOnce)
}
return
}
if listener == nil {
@@ -410,6 +357,19 @@ func (s *apiService) Serve() {
}
}
// Complete implements suture.IsCompletable, which signifies to the supervisor
// whether to stop restarting the service.
func (s *apiService) Complete() bool {
select {
case <-s.startedOnce:
return s.startupErr != nil
case <-s.stop:
return true
default:
}
return false
}
func (s *apiService) Stop() {
close(s.stop)
}
@@ -699,7 +659,7 @@ func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
}
}
func folderSummary(cfg configIntf, m modelIntf, folder string) (map[string]interface{}, error) {
func folderSummary(cfg config.Wrapper, m model.Model, folder string) (map[string]interface{}, error) {
var res = make(map[string]interface{})
errors, err := m.FolderErrors(folder)
@@ -932,7 +892,7 @@ func (s *apiService) getSystemConfigInsync(w http.ResponseWriter, r *http.Reques
func (s *apiService) postSystemRestart(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "restarting"}`, w)
go restart()
go exit.Restart()
}
func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
@@ -958,12 +918,12 @@ func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
}
go restart()
go exit.Restart()
}
func (s *apiService) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "shutting down"}`, w)
go shutdown()
go exit.Shutdown()
}
func (s *apiService) flushResponse(resp string, w http.ResponseWriter) {
@@ -1383,8 +1343,7 @@ func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
}
s.flushResponse(`{"ok": "restarting"}`, w)
l.Infoln("Upgrading")
stop <- exitUpgrading
exit.ExitUpgrading()
}
}
+180 -103
View File
@@ -9,7 +9,6 @@ package main
import (
"bytes"
"crypto/tls"
"errors"
"flag"
"fmt"
"io"
@@ -48,6 +47,7 @@ import (
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/pkg/errors"
"github.com/thejerf/suture"
)
@@ -68,10 +68,7 @@ const (
maxSystemLog = 250
)
var (
myID protocol.DeviceID
stop = make(chan int)
)
var myID protocol.DeviceID
const (
usage = "syncthing [options]"
@@ -166,34 +163,35 @@ var (
)
type RuntimeOptions struct {
confDir string
resetDatabase bool
resetDeltaIdxs bool
showVersion bool
showPaths bool
showDeviceId bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
browserOnly bool
hideConsole bool
logFile string
auditEnabled bool
auditFile string
verbose bool
paused bool
unpaused bool
guiAddress string
guiAPIKey string
generateDir string
noRestart bool
profiler string
assetDir string
cpuProfile bool
stRestarting bool
logFlags int
showHelp bool
confDir string
resetDatabase bool
resetDeltaIdxs bool
showVersion bool
showPaths bool
showDeviceId bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
browserOnly bool
hideConsole bool
logFile string
auditEnabled bool
auditFile string
verbose bool
paused bool
unpaused bool
guiAddress string
guiAPIKey string
generateDir string
noRestart bool
profiler string
assetDir string
cpuProfile bool
stRestarting bool
logFlags int
showHelp bool
allowNewerConfig bool
}
func defaultRuntimeOptions() RuntimeOptions {
@@ -247,6 +245,7 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout). Cannot be used together with -no-restart/STNORESTART environment variable.")
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
flag.BoolVar(&options.allowNewerConfig, "allow-newer-config", false, "Allow loading newer than current config version")
if runtime.GOOS == "windows" {
// Allow user to hide the console window
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
@@ -264,6 +263,32 @@ func parseCommandLineOptions() RuntimeOptions {
return options
}
type exiter struct {
stop chan int
}
func (e *exiter) Restart() {
l.Infoln("Restarting")
e.stop <- exitRestarting
}
func (e *exiter) Shutdown() {
l.Infoln("Shutting down")
e.stop <- exitSuccess
}
func (e *exiter) ExitUpgrading() {
l.Infoln("Shutting down after upgrade")
e.stop <- exitUpgrading
}
// waitForExit must be called synchronously.
func (e *exiter) waitForExit() int {
return <-e.stop
}
var exit = exiter{make(chan int)}
func main() {
options := parseCommandLineOptions()
l.SetFlags(options.logFlags)
@@ -282,7 +307,8 @@ func main() {
// to complain if they set -logfile explicitly, not if it's set to its
// default location
if options.noRestart && (options.logFile != "" && options.logFile != "-") {
l.Fatalln("-logfile may not be used with -no-restart or STNORESTART")
l.Warnln("-logfile may not be used with -no-restart or STNORESTART")
os.Exit(exitError)
}
if options.hideConsole {
@@ -295,11 +321,13 @@ func main() {
var err error
options.confDir, err = filepath.Abs(options.confDir)
if err != nil {
l.Fatalln(err)
l.Warnln("Failed to make options path absolute:", err)
os.Exit(exitError)
}
}
if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil {
l.Fatalln(err)
l.Warnln(err)
os.Exit(exitError)
}
}
@@ -336,7 +364,8 @@ func main() {
locations.Get(locations.KeyFile),
)
if err != nil {
l.Fatalln("Error reading device ID:", err)
l.Warnln("Error reading device ID:", err)
os.Exit(exitError)
}
myID = protocol.NewDeviceID(cert.Certificate[0])
@@ -345,22 +374,32 @@ func main() {
}
if options.browserOnly {
openGUI()
if err := openGUI(); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(exitError)
}
return
}
if options.generateDir != "" {
generate(options.generateDir)
if err := generate(options.generateDir); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(exitError)
}
return
}
// Ensure that our home directory exists.
ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700)
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failure on home directory:", err)
os.Exit(exitError)
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
l.Warnln("Error while Upgrading:", err)
os.Exit(exitError)
}
l.Infoln("Upgraded from", options.upgradeTo)
return
@@ -379,7 +418,8 @@ func main() {
if options.resetDatabase {
if err := resetDB(); err != nil {
l.Fatalln("Resetting database:", err)
l.Warnln("Resetting database:", err)
os.Exit(exitError)
}
return
}
@@ -391,23 +431,30 @@ func main() {
}
}
func openGUI() {
cfg, _ := loadOrDefaultConfig()
func openGUI() error {
cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
if cfg.GUI().Enabled {
if err := openURL(cfg.GUI().URL()); err != nil {
l.Fatalln("Open URL:", err)
return err
}
} else {
l.Warnln("Browser: GUI is currently disabled")
}
return nil
}
func generate(generateDir string) {
func generate(generateDir string) error {
dir, err := fs.ExpandTilde(generateDir)
if err != nil {
l.Fatalln("generate:", err)
return err
}
if err := ensureDir(dir, 0700); err != nil {
return err
}
ensureDir(dir, 0700)
certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
@@ -417,11 +464,11 @@ func generate(generateDir string) {
} else {
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName)
if err != nil {
l.Fatalln("Create certificate:", err)
return errors.Wrap(err, "create certificate")
}
myID = protocol.NewDeviceID(cert.Certificate[0])
if err != nil {
l.Fatalln("Load certificate:", err)
return errors.Wrap(err, "load certificate")
}
if err == nil {
l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0]))
@@ -431,13 +478,17 @@ func generate(generateDir string) {
cfgFile := filepath.Join(dir, "config.xml")
if _, err := os.Stat(cfgFile); err == nil {
l.Warnln("Config exists; will not overwrite.")
return
return nil
}
cfg, err := defaultConfig(cfgFile)
if err != nil {
return err
}
var cfg = defaultConfig(cfgFile)
err = cfg.Save()
if err != nil {
l.Warnln("Failed to save config", err)
return errors.Wrap(err, "save config")
}
return nil
}
func debugFacilities() string {
@@ -467,7 +518,8 @@ func checkUpgrade() upgrade.Release {
opts := cfg.Options()
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
if err != nil {
l.Fatalln("Upgrade:", err)
l.Warnln("Upgrade:", err)
os.Exit(exitError)
}
if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
@@ -486,14 +538,16 @@ func performUpgrade(release upgrade.Release) {
if err == nil {
err = upgrade.To(release)
if err != nil {
l.Fatalln("Upgrade:", err)
l.Warnln("Upgrade:", err)
os.Exit(exitError)
}
l.Infof("Upgraded to %q", release.Tag)
} else {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
if err != nil {
l.Fatalln("Upgrade:", err)
l.Warnln("Upgrade:", err)
os.Exit(exitError)
}
l.Infoln("Syncthing upgrading")
os.Exit(exitUpgrading)
@@ -592,7 +646,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
tlsDefaultCommonName,
)
if err != nil {
l.Fatalln(err)
l.Infoln("Failed to generate certificate:", err)
os.Exit(exitError)
}
}
@@ -614,10 +669,15 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
"myID": myID.String(),
})
cfg := loadConfigAtStartup()
cfg, err := loadConfigAtStartup(runtimeOptions.allowNewerConfig)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(exitError)
}
if err := checkShortIDs(cfg); err != nil {
l.Fatalln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one of the following:\n ", err)
l.Warnln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one of the following:\n ", err)
os.Exit(exitError)
}
if len(runtimeOptions.profiler) > 0 {
@@ -626,7 +686,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
runtime.SetBlockProfileRate(1)
err := http.ListenAndServe(runtimeOptions.profiler, nil)
if err != nil {
l.Fatalln(err)
l.Warnln(err)
os.Exit(exitError)
}
}()
}
@@ -637,10 +698,12 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
dbFile := locations.Get(locations.Database)
ldb, err := db.Open(dbFile)
if err != nil {
l.Fatalln("Error opening database:", err)
l.Warnln("Error opening database:", err)
os.Exit(exitError)
}
if err := db.UpdateSchema(ldb); err != nil {
l.Fatalln("Database schema:", err)
l.Warnln("Database schema:", err)
os.Exit(exitError)
}
if runtimeOptions.resetDeltaIdxs {
@@ -776,10 +839,12 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
if runtimeOptions.cpuProfile {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Fatalln("Creating profile:", err)
l.Warnln("Creating profile:", err)
os.Exit(exitError)
}
if err := pprof.StartCPUProfile(f); err != nil {
l.Fatalln("Starting profile:", err)
l.Warnln("Starting profile:", err)
os.Exit(exitError)
}
}
@@ -858,7 +923,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
code := <-stop
code := exit.waitForExit()
mainService.Stop()
@@ -879,7 +944,7 @@ func setupSignalHandling() {
signal.Notify(restartSign, sigHup)
go func() {
<-restartSign
stop <- exitRestarting
exit.Restart()
}()
// Exit with "success" code (no restart) on INT/TERM
@@ -889,45 +954,57 @@ func setupSignalHandling() {
signal.Notify(stopSign, os.Interrupt, sigTerm)
go func() {
<-stopSign
stop <- exitSuccess
exit.Shutdown()
}()
}
func loadOrDefaultConfig() (*config.Wrapper, error) {
func loadOrDefaultConfig() (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile)
cfg, err := config.Load(cfgFile, myID)
if err != nil {
cfg = defaultConfig(cfgFile)
cfg, err = defaultConfig(cfgFile)
}
return cfg, err
}
func loadConfigAtStartup() *config.Wrapper {
func loadConfigAtStartup(allowNewerConfig bool) (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile)
cfg, err := config.Load(cfgFile, myID)
if os.IsNotExist(err) {
cfg = defaultConfig(cfgFile)
cfg.Save()
l.Infof("Default config saved. Edit %s to taste or use the GUI\n", cfg.ConfigPath())
cfg, err = defaultConfig(cfgFile)
if err != nil {
return nil, errors.Wrap(err, "failed to generate default config")
}
err = cfg.Save()
if err != nil {
return nil, errors.Wrap(err, "failed to save default config")
}
l.Infof("Default config saved. Edit %s to taste (with Syncthing stopped) or use the GUI", cfg.ConfigPath())
} else if err == io.EOF {
l.Fatalln("Failed to load config: unexpected end of file. Truncated or empty configuration?")
return nil, errors.New("Failed to load config: unexpected end of file. Truncated or empty configuration?")
} else if err != nil {
l.Fatalln("Failed to load config:", err)
return nil, errors.Wrap(err, "failed to load config")
}
if cfg.RawCopy().OriginalVersion != config.CurrentVersion {
if cfg.RawCopy().OriginalVersion == config.CurrentVersion+1101 {
l.Infof("Now, THAT's what we call a config from the future! Don't worry. As long as you hit that wire with the connecting hook at precisely eighty-eight miles per hour the instant the lightning strikes the tower... everything will be fine.")
}
if cfg.RawCopy().OriginalVersion > config.CurrentVersion && !allowNewerConfig {
return nil, fmt.Errorf("Config file version (%d) is newer than supported version (%d). If this is expected, use -allow-newer-config to override.", cfg.RawCopy().OriginalVersion, config.CurrentVersion)
}
err = archiveAndSaveConfig(cfg)
if err != nil {
l.Fatalln("Config archive:", err)
return nil, errors.Wrap(err, "config archive")
}
}
return cfg
return cfg, nil
}
func archiveAndSaveConfig(cfg *config.Wrapper) error {
func archiveAndSaveConfig(cfg config.Wrapper) error {
// Copy the existing config to an archive copy
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion)
l.Infoln("Archiving a copy of old config file format at:", archivePath)
@@ -976,7 +1053,8 @@ func startAuditing(mainService *suture.Supervisor, auditFile string) {
}
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
if err != nil {
l.Fatalln("Audit:", err)
l.Warnln("Audit:", err)
os.Exit(exitError)
}
auditDest = auditFile
}
@@ -991,7 +1069,7 @@ func startAuditing(mainService *suture.Supervisor, auditFile string) {
l.Infoln("Audit log in", auditDest)
}
func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) {
func setupGUI(mainService *suture.Supervisor, cfg config.Wrapper, m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) {
guiCfg := cfg.GUI()
if !guiCfg.Enabled {
@@ -1009,46 +1087,43 @@ func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Mode
cfg.Subscribe(api)
mainService.Add(api)
if err := api.WaitForStart(); err != nil {
l.Warnln("Failed starting API:", err)
os.Exit(exitError)
}
if cfg.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in its own routine.
<-api.startedOnce
go func() { _ = openURL(guiCfg.URL()) }()
}
}
func defaultConfig(cfgFile string) *config.Wrapper {
newCfg := config.NewWithFreePorts(myID)
func defaultConfig(cfgFile string) (config.Wrapper, error) {
newCfg, err := config.NewWithFreePorts(myID)
if err != nil {
return nil, err
}
if noDefaultFolder {
l.Infoln("We will skip creation of a default folder on first start since the proper envvar is set")
return config.Wrap(cfgFile, newCfg)
return config.Wrap(cfgFile, newCfg), nil
}
newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
l.Infoln("Default folder created and/or linked to new config")
return config.Wrap(cfgFile, newCfg)
return config.Wrap(cfgFile, newCfg), nil
}
func resetDB() error {
return os.RemoveAll(locations.Get(locations.Database))
}
func restart() {
l.Infoln("Restarting")
stop <- exitRestarting
}
func shutdown() {
l.Infoln("Shutting down")
stop <- exitSuccess
}
func ensureDir(dir string, mode fs.FileMode) {
func ensureDir(dir string, mode fs.FileMode) error {
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
err := fs.MkdirAll(".", mode)
if err != nil {
l.Fatalln(err)
return err
}
if fi, err := fs.Stat("."); err == nil {
@@ -1064,6 +1139,7 @@ func ensureDir(dir string, mode fs.FileMode) {
}
}
}
return nil
}
func standbyMonitor() {
@@ -1079,14 +1155,14 @@ func standbyMonitor() {
// things a moment to stabilize.
time.Sleep(restartDelay)
restart()
exit.Restart()
return
}
now = time.Now()
}
}
func autoUpgrade(cfg *config.Wrapper) {
func autoUpgrade(cfg config.Wrapper) {
timer := time.NewTimer(0)
sub := events.Default.Subscribe(events.DeviceConnected)
for {
@@ -1137,7 +1213,7 @@ func autoUpgrade(cfg *config.Wrapper) {
events.Default.Unsubscribe(sub)
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
time.Sleep(time.Minute)
stop <- exitUpgrading
exit.ExitUpgrading()
return
}
}
@@ -1188,7 +1264,7 @@ func cleanConfigDirectory() {
// checkShortIDs verifies that the configuration won't result in duplicate
// short ID:s; that is, that the devices in the cluster all have unique
// initial 64 bits.
func checkShortIDs(cfg *config.Wrapper) error {
func checkShortIDs(cfg config.Wrapper) error {
exists := make(map[protocol.ShortID]protocol.DeviceID)
for deviceID := range cfg.Devices() {
shortID := deviceID.Short()
@@ -1210,7 +1286,7 @@ func showPaths(options RuntimeOptions) {
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
}
func setPauseState(cfg *config.Wrapper, paused bool) {
func setPauseState(cfg config.Wrapper, paused bool) {
raw := cfg.RawCopy()
for i := range raw.Devices {
raw.Devices[i].Paused = paused
@@ -1219,6 +1295,7 @@ func setPauseState(cfg *config.Wrapper, paused bool) {
raw.Folders[i].Paused = paused
}
if _, err := cfg.Replace(raw); err != nil {
l.Fatalln("Cannot adjust paused state:", err)
l.Warnln("Cannot adjust paused state:", err)
os.Exit(exitError)
}
}
+54
View File
@@ -44,6 +44,8 @@ func (c *mockedConfig) Replace(cfg config.Configuration) (config.Waiter, error)
func (c *mockedConfig) Subscribe(cm config.Committer) {}
func (c *mockedConfig) Unsubscribe(cm config.Committer) {}
func (c *mockedConfig) Folders() map[string]config.FolderConfiguration {
return nil
}
@@ -68,6 +70,58 @@ func (c *mockedConfig) RequiresRestart() bool {
return false
}
func (c *mockedConfig) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {}
func (c *mockedConfig) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {}
func (m *mockedConfig) MyName() string {
return ""
}
func (m *mockedConfig) ConfigPath() string {
return ""
}
func (m *mockedConfig) SetGUI(gui config.GUIConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) SetOptions(opts config.OptionsConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
return config.FolderConfiguration{}, false
}
func (m *mockedConfig) FolderList() []config.FolderConfiguration {
return nil
}
func (m *mockedConfig) SetFolder(fld config.FolderConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
return config.DeviceConfiguration{}, false
}
func (m *mockedConfig) RemoveDevice(id protocol.DeviceID) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (m *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
return false
}
func (m *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
return false
}
func (m *mockedConfig) GlobalDiscoveryServers() []string {
return nil
}
type noopWaiter struct{}
func (noopWaiter) Wait() {}
+4
View File
@@ -15,3 +15,7 @@ func (m *mockedConnections) Status() map[string]interface{} {
func (m *mockedConnections) NATType() string {
return ""
}
func (m *mockedConnections) Serve() {}
func (m *mockedConnections) Stop() {}
+37
View File
@@ -7,8 +7,10 @@
package main
import (
"net"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
@@ -150,3 +152,38 @@ func (m *mockedModel) WatchError(folder string) error {
func (m *mockedModel) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated {
return nil
}
func (m *mockedModel) Serve() {}
func (m *mockedModel) Stop() {}
func (m *mockedModel) Index(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {}
func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {
}
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
return nil, nil
}
func (m *mockedModel) ClusterConfig(deviceID protocol.DeviceID, config protocol.ClusterConfig) {}
func (m *mockedModel) Closed(conn protocol.Connection, err error) {}
func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
}
func (m *mockedModel) AddConnection(conn connections.Connection, hello protocol.HelloResult) {}
func (m *mockedModel) OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error {
return nil
}
func (m *mockedModel) GetHello(protocol.DeviceID) protocol.HelloIntf {
return nil
}
func (m *mockedModel) AddFolder(cfg config.FolderConfiguration) {}
func (m *mockedModel) RestartFolder(from, to config.FolderConfiguration) {}
func (m *mockedModel) StartFolder(folder string) {}
func (m *mockedModel) StartDeadlockDetector(timeout time.Duration) {}
+3 -3
View File
@@ -85,18 +85,18 @@ func monitorMain(runtimeOptions RuntimeOptions) {
stderr, err := cmd.StderrPipe()
if err != nil {
l.Fatalln("stderr:", err)
panic(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
l.Fatalln("stdout:", err)
panic(err)
}
l.Infoln("Starting syncthing")
err = cmd.Start()
if err != nil {
l.Fatalln(err)
panic(err)
}
stdoutMut.Lock()
+5 -3
View File
@@ -9,7 +9,9 @@ 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/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
@@ -20,8 +22,8 @@ import (
type folderSummaryService struct {
*suture.Supervisor
cfg configIntf
model modelIntf
cfg config.Wrapper
model model.Model
stop chan struct{}
immediate chan string
@@ -34,7 +36,7 @@ type folderSummaryService struct {
lastEventReqMut sync.Mutex
}
func newFolderSummaryService(cfg configIntf, m modelIntf) *folderSummaryService {
func newFolderSummaryService(cfg config.Wrapper, m model.Model) *folderSummaryService {
service := &folderSummaryService{
Supervisor: suture.New("folderSummaryService", suture.Spec{
PassThroughPanics: true,
+5 -5
View File
@@ -37,7 +37,7 @@ const usageReportVersion = 3
// reportData returns the data to be sent in a usage report. It's used in
// various places, so not part of the usageReportingManager object.
func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf, version int, preview bool) map[string]interface{} {
func reportData(cfg config.Wrapper, m model.Model, connectionsService connections.Service, version int, preview bool) map[string]interface{} {
opts := cfg.Options()
res := make(map[string]interface{})
res["urVersion"] = version
@@ -323,16 +323,16 @@ func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf,
}
type usageReportingService struct {
cfg *config.Wrapper
model *model.Model
connectionsService *connections.Service
cfg config.Wrapper
model model.Model
connectionsService connections.Service
forceRun chan struct{}
stop chan struct{}
stopped chan struct{}
stopMut sync.RWMutex
}
func newUsageReportingService(cfg *config.Wrapper, model *model.Model, connectionsService *connections.Service) *usageReportingService {
func newUsageReportingService(cfg config.Wrapper, model model.Model, connectionsService connections.Service) *usageReportingService {
svc := &usageReportingService{
cfg: cfg,
model: model,
+1 -1
View File
@@ -1,7 +1,7 @@
[Desktop Entry]
Name=Syncthing Web UI
GenericName=File synchronization UI
Comment="Opens Syncthing's Web UI in the default browser (Syncthing must already be started)."
Comment=Opens Syncthing's Web UI in the default browser (Syncthing must already be started).
Exec=/usr/bin/syncthing -browser-only
Icon=syncthing
Terminal=false
+1 -1
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=man:syncthing(1)
After=network.target
After=multi-user.target network.target
[Service]
User=%i
+6 -2
View File
@@ -228,7 +228,7 @@
"Reduced by ignore patterns": "Намалено посредством шаблон за игнориране",
"Release Notes": "Бележки по обновяването",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Кандидат версиите съдържат най-новата функционалност и поправки. Те са близки до традиционните дву-седмични Synchthing обновления.",
"Remote Devices": "Чужди устройства",
"Remote Devices": "Устройства",
"Remove": "Премахни",
"Remove Device": "Премахване на устройство",
"Remove Folder": "Премахване на папка",
@@ -300,7 +300,7 @@
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing има проблем при обработването на заявката. Моля, презаредете браузъра или рестартирайте Syncthing ако проблемът продължи.",
"Take me back": "Take me back",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Администраторския панел на Syncthing е разрешава дистанционен достъп без да изисква парола.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Администраторският панел на Syncthing разрешава дистанционен достъп без да изисква парола.",
"The aggregated statistics are publicly available at the URL below.": "Обобщение на събраните статистически ще намерите на долния URL адрес.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурацията е запазена, но не е активирана. Syncthing трябва да рестартира, за да се активира новата конфигурация.",
"The device ID cannot be blank.": "Полето идентификатор на устройство не може да бъде празно.",
@@ -349,6 +349,9 @@
"Uptime": "Работи от",
"Usage reporting is always enabled for candidate releases.": "Докладът за ползването е винаги включен за кандидат нови версии.",
"Use HTTPS for GUI": "Използвай HTTPS за потребителския интерфейс",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Версия",
"Versions": "Версии",
"Versions Path": "Път до версиите",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Предупреждение: Ако използвате външна програма за наблюдение като {{syncthingInotify}}, трябва да я деактивирате.",
"Watch for Changes": "Мониторинг за промени",
"Watching for Changes": "Мониторинг за промени",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Когато добавяте ново устройство имайте предвид, че това устройство също трябва да бъде добавено от другата страна.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когато добавяте нов идентификатор на папка имайте предвид, че той се използва за свързване на папките между отделните устройства. Идентификатора разграничава главни/малки букви.",
"Yes": "Да",
@@ -349,6 +349,9 @@
"Uptime": "Temps de funcionament",
"Usage reporting is always enabled for candidate releases.": "Els informes d'ús sempre estan activats per a les versions candidates.",
"Use HTTPS for GUI": "Utilitzar HTTPS per a l'Interfície Gràfica d'Usuari (GUI)",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Versió",
"Versions": "Versions",
"Versions Path": "Ruta de les versions",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "AVÍS: Si estàs utilitzant un observador extern com {{syncthingInotify}}, deus assegurar-te de que està desactivat.",
"Watch for Changes": "Vigilar els Canvis",
"Watching for Changes": "Vigilant els Canvis",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quant s'afig un nou dispositiu, hi ha que tindre en compte que aquest dispositiu deu ser afegit també en l'altre costat.",
"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í",
+5 -1
View File
@@ -51,7 +51,7 @@
"Connection Error": "Chyba připojení",
"Connection Type": "Typ připojení",
"Connections": "Připojení",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Syncthing nyní umožňuje nepřetržité sledování změn. To zachytí změny na disku na spustí sken pouze pro změněné cesty. Výhody jsou rychlejší propagace změn a méně plných skenů.",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Syncthing nyní umožňuje nepřetržité sledování změn. To zachytí změny na disku a spustí sken pouze pro změněné cesty. Výhody jsou rychlejší propagace změn a méně plných skenů.",
"Copied from elsewhere": "Zkopírováno odjinud",
"Copied from original": "Zkopírováno z originálu",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 následující přispěvatelé:",
@@ -349,6 +349,9 @@
"Uptime": "Celkový čas běhu",
"Usage reporting is always enabled for candidate releases.": "Hlášení o používání je pro kandidáty na vydání vždy zapnuto.",
"Use HTTPS for GUI": "Použít HTTPS pro grafické rozhraní",
"Use notifications from the filesystem to detect changed items.": "Použít oznamování soubor. systému pro nalezení změněných položek.",
"Variable Size Blocks": "Bloky variabilní velikosti",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Bloky variabilní velikosti (také \"velké bloky\") jsou účinnější pro velké soubory.",
"Version": "Verze",
"Versions": "Verze",
"Versions Path": "Cesta k verzím",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Pozor: Pokud používáte externí sledování změn jako {{syncthingInotify}}, měly byste se ujistit, že je toto sledování vypnuto.",
"Watch for Changes": "Sledovat změny",
"Watching for Changes": "Sledování změn",
"Watching for changes discovers most changes without periodic scanning.": "Sledování změn odhalí většinu změn bez periodického skenování.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového zařízení mějte na paměti, že je ho třeba také zadat na druhé straně.",
"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říč zařízeními. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi zařízeními.",
"Yes": "Ano",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Oppetid",
"Usage reporting is always enabled for candidate releases.": "Forbrugsraportering er altid aktiveret for udgivelseskandidater.",
"Use HTTPS for GUI": "Anvend HTTPS til GUI-adgang",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Version",
"Versions": "Versioner",
"Versions Path": "Versionssti",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advarsel: Hvis du bruger en ekstern overvågning så som {{syncthingInotify}}, bør du være sikker på, at den er deaktiveret.",
"Watch for Changes": "Overvåg ændringer",
"Watching for Changes": "Overvågning af ændringer",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når der tilføjes en ny enhed, vær da opmærksom på, at denne enhed også skal tilføjes i den anden ende.",
"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 der tilføjes en ny enhed, vær da opmærksom på at samme mappe-ID bruges til at forbinde mapper på de forskellige enheder. Der er forskel på store og små bogstaver, og ID skal være fuldstændig identisk på alle enheder.",
"Yes": "Ja",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Betriebszeit",
"Usage reporting is always enabled for candidate releases.": "Nutzungsbericht ist für Veröffentlichungskandidaten immer aktiviert.",
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche verwenden",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Version",
"Versions": "Versionen",
"Versions Path": "Versionierungspfad",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Achtung: Wenn Sie einen externen Beobachter wie {{syncthingInotify}} benutzen, sollten sie sicher sein das dieser deaktiviert ist.",
"Watch for Changes": "Auf Änderungen achten",
"Watching for Changes": "Auf Änderungen achten",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "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 Ordners, dass die Ordnerkennung dazu verwendet wird, Ordner zwischen Geräten zu verbinden. Die Kennung muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
"Yes": "Ja",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Χρόνος απρόσκοπτης λειτουργίας",
"Usage reporting is always enabled for candidate releases.": "Η αποστολή αναφορών χρήσης είναι πάντα ενεργοποιημένη στις υποψήφιες εκδόσεις.",
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Έκδοση",
"Versions": "Εκδόσεις",
"Versions Path": "Φάκελος τήρησης εκδόσεων",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Προσοχή: αν χρησιμοποιείτε ένα εξωτερικό εργαλείο επιτήρησης, όπως το {{syncthingInotify}}, σιγουρευτείτε ότι έχει απενεργοποιηθεί.",
"Watch for Changes": "Επιτήρηση αλλαγών",
"Watching for Changes": "Εκτελείται η επιτήρηση αλλαγών",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Θυμήσου πως όταν προσθέτεις μια νέα συσκευή, ετούτη η συσκευή θα πρέπει να προστεθεί και στην άλλη πλευρά.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
"Yes": "Ναι",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Uptime",
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Version",
"Versions": "Versions",
"Versions Path": "Versions Path",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "Watch for Changes",
"Watching for Changes": "Watching for Changes",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "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",
+1
View File
@@ -56,6 +56,7 @@
"Copied from original": "Copied from original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Danger!": "Danger!",
"Debugging Facilities": "Debugging Facilities",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Tiempo de funcionamiento",
"Usage reporting is always enabled for candidate releases.": "El informe de uso está siempre habilitado en las versiones candidatas.",
"Use HTTPS for GUI": "Usar HTTPS para la Interfaz Gráfica de Usuario (GUI)",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Versión",
"Versions": "Versiones",
"Versions Path": "Ruta de las versiones",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advertencia: Si estás usando un vigilante externo como {{syncthingInotify}}, deberías asegurarte de que está desactivado.",
"Watch for Changes": "Vigilar los cambios",
"Watching for Changes": "Vigilando los cambios",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
"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",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Tiempo de funcionamiento",
"Usage reporting is always enabled for candidate releases.": "El informe de uso está siempre habilitado en las versiones candidatas.",
"Use HTTPS for GUI": "Usar HTTPS para la Interfaz Gráfica de Usuario (GUI)",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Versión",
"Versions": "Versiones",
"Versions Path": "Ruta de las versiones",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advertencia: Si estás utilizando un observador externo como {{syncthingInotify}}, debes asegurarte de que está desactivado.",
"Watch for Changes": "Vigila los cambios",
"Watching for Changes": "Vigilando los cambios",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
"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",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Päälläoloaika",
"Usage reporting is always enabled for candidate releases.": "Käytön raportointi on aina käytössä testiversioissa.",
"Use HTTPS for GUI": "Käytä HTTPS:ää GUI:n kanssa",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Versio",
"Versions": "Versiot",
"Versions Path": "Versioiden polku",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Varoitus: jos käytät ulkopuolista tiedostojärjestelmän muutosten valvojaa, kuten {{syncthingInotify}} varmista, että se ei ole aktiivinen.",
"Watch for Changes": "Seuraa muutoksia",
"Watching for Changes": "Seuraa muutoksia",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lisättäessä laitetta, muista että tämä laite tulee myös lisätä toiseen laitteeseen.",
"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ä",
+7 -3
View File
@@ -138,7 +138,7 @@
"Global Discovery": "Découverte globale",
"Global Discovery Servers": "Serveurs de découverte globale",
"Global State": "État global",
"Help": "Aide",
"Help": "Aide (anglais)",
"Home page": "Page d'accueil",
"Ignore": "Refuser",
"Ignore Patterns": "Exclusions...",
@@ -298,7 +298,7 @@
"Syncthing is upgrading.": "Syncthing se met à jour.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être arrêté, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble avoir un problème pour traiter votre demande. Rafraîchissez la page (F5 sur PC) ou redémarrez Syncthing si le problème persiste.",
"Take me back": "Reprends moi",
"Take me back": "Vérifier ...",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "L'adresse de l'interface graphique est remplacée par une ou des options de lancement. Les modifications apportées ici ne seront pas effectives tant que ces options seront utilisées.",
"The Syncthing admin interface is configured to allow remote access without a password.": "L'interface d'administration de Syncthing est paramétrée pour autoriser les accès à distance sans mot de passe !!!",
"The aggregated statistics are publicly available at the URL below.": "Les statistiques agrégées sont disponibles publiquement à l'adresse ci-dessous.",
@@ -349,6 +349,9 @@
"Uptime": "Durée de fonctionnement",
"Usage reporting is always enabled for candidate releases.": "L'envoi des statistiques d'utilisation est obligatoirement actif pour les versions préliminaires.",
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
"Use notifications from the filesystem to detect changed items.": "Utiliser les notifications du système de fichiers pour détecter les éléments modifiés.",
"Variable Size Blocks": "Blocs de taille variable",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Les blocs de taille variable (dont les \"gros blocs\") sont plus efficaces pour les gros fichiers.",
"Version": "Version",
"Versions": "Restauration...",
"Versions Path": "Emplacement des versions",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Attention : si vous utilisez un système externe de surveillance tel que {{syncthingInotify}}, vous devez vous assurer qu'il est désactivé.",
"Watch for Changes": "Écouter les changements",
"Watching for Changes": "Surveillance des changements",
"Watching for changes discovers most changes without periodic scanning.": "La surveillance des changements découvre la plupart des changements sans réanalyses périodiques.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lorsque vous ajoutez un appareil, gardez à l'esprit que le votre doit aussi être ajouté de l'autre coté.",
"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 partage est ajouté, gardez à l'esprit que son ID est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur tous les appareils participant à ce partage.",
"Yes": "Oui",
@@ -369,7 +373,7 @@
"You can read more about the two release channels at the link below.": "Vous pouvez en savoir plus sur les deux canaux de distribution via le lien ci-dessous.",
"You have no ignored devices.": "Aucun appareil refusé.",
"You have no ignored folders.": "Aucun partage refusé.",
"You have unsaved changes. Do you really want to discard them?": "Vous avez des réglages non enregistrés. Voulez-vous vraiment les ignorer ?",
"You have unsaved changes. Do you really want to discard them?": "Vous avez des réglages non enregistrés. Voulez-vous vraiment les rejeter ?",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"directories": "répertoires",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Rintiid",
"Usage reporting is always enabled for candidate releases.": "Brûkersrapportaazje stiet altyd oan foar ferzje kandidaten.",
"Use HTTPS for GUI": "Brûk HTTPS foar GUI",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Ferzje",
"Versions": "Ferzjes",
"Versions Path": "Ferzjes-paad",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warskôging: As jo in eksterne sjogger lykas {{syncthingInotify}} brûke, bin der dan wiis fan dat dizze út stiet.",
"Watch for Changes": "Sjoch foar Feroarings",
"Watching for Changes": "Sjocht foar Feroarings",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Hâld by it taheakjen fan in nij apparaat yn de holle dat it apparaat oan de oare kant ek taheakke wurde moat. ",
"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.": "Hâld by it taheakjen fan in nije map yn de holle dat de map-ID brûkt wurd om de mappen tusken apparaten mei-inoar te ferbinen. Se binne haadlettergefoelich en moatte oer alle apparaten eksakt oerienkomme.",
"Yes": "Ja",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Üzemidő",
"Usage reporting is always enabled for candidate releases.": "Az előzetes kiadásokban a használati jelentés mindig engedélyezett.",
"Use HTTPS for GUI": "HTTPS használata a grafikus felülethez",
"Use notifications from the filesystem to detect changed items.": "A fájlrendszer által szolgáltatott értesítések alkalmazása a megváltozott elemek keresésére.",
"Variable Size Blocks": "Változó méretű blokkok",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "A változó mérető blokkok (ún. „nagy blokkok”) sokkal hatékonyabbak nagy fájloknál.",
"Version": "Verzió",
"Versions": "Verziók",
"Versions Path": "Verziók útvonala",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Figyelem: Ha eddig külső megfigyelő volt erre a célra alkalmazva, mint pl. a(z) {{syncthingInotify}}, akkor előbb meg kell győződni arról, hogy az ki lett kapcsolva.",
"Watch for Changes": "Változásfigyelés",
"Watching for Changes": "Változások figyelése",
"Watching for changes discovers most changes without periodic scanning.": "A változások keresése felfedezi a legtöbb változást periodikus átnézés nélkül.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Új eszköz hozzáadásakor nem szabad elfeledkezni arról, hogy a másik oldalon ezt az eszközt is hozzá kell adni.",
"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.": "Új eszköz hozzáadásakor észben kell tartani, hogy a mappaazonosító arra való, hogy összekösse a mappákat az eszközökön. Az azonosító kisbetű-nagybetű érzékeny és pontosan egyeznie kell az eszközökön.",
"Yes": "Igen",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Tempo di Funzionamento",
"Usage reporting is always enabled for candidate releases.": "Le segnalazioni di utilizzo sono sempre abilitate le versioni candidate al rilascio.",
"Use HTTPS for GUI": "Utilizza HTTPS per l'interfaccia grafica",
"Use notifications from the filesystem to detect changed items.": "Usa le notifiche dal filesystem per rilevare gli elementi modificati.",
"Variable Size Blocks": "Blocchi di dimensioni variabile",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "I blocchi di dimensioni variabile (anche \"blocchi di grandi dimensioni\") sono più efficienti per file di grandi dimensioni.",
"Version": "Versione",
"Versions": "Versioni",
"Versions Path": "Percorso Cartella Versioni",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Attenzione: se stai usando uno strumento esterno di monitoraggio come {{syncthingInotify}}, dovresti assicurarti che sia disattivato.",
"Watch for Changes": "Monitorare i cambiamenti",
"Watching for Changes": "Monitoraggio dei cambiamenti",
"Watching for changes discovers most changes without periodic scanning.": "Guardando le modifiche si scopre la maggior parte delle modifiche senza scansione periodica.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando si aggiunge un nuovo dispositivo, tenere presente che il dispositivo deve essere aggiunto anche dall'altra parte.",
"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ì",
+7 -3
View File
@@ -58,8 +58,8 @@
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "無視パターンを作成中。既存のファイルが {{path}} にある場合は上書きされます。",
"Danger!": "危険!",
"Debugging Facilities": "Debugging Facilities",
"Default Folder Path": "Default Folder Path",
"Debugging Facilities": "デバッグ機能",
"Default Folder Path": "デフォルトのフォルダーパス",
"Deleted": "削除",
"Deselect All": "すべて選択解除",
"Device": "デバイス",
@@ -202,7 +202,7 @@
"Override Changes": "他のデバイスの変更を上書きする",
"Path": "パス",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "ローカルコンピュータ上のフォルダーパス。フォルダーが存在しない場合は作成されます。チルダ (~) で以下のフォルダーを短縮入力できます:",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "新しい自動承諾フォルダーが作成されるパス、およびUIを使用して新しいフォルダーを追加するときにデフォルトでサジェストされるパスです。チルダ文字(~)の場合は {{tilde}} に展開されます。",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "古いバージョンを保存するパス (空欄の場合、デフォルトで共有フォルダー内の .stversions ディレクトリ)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "古いバージョンを保存するパス (空欄の場合、デフォルトでフォルダー内の .stversions フォルダー)",
"Pause": "一時停止",
@@ -349,6 +349,9 @@
"Uptime": "稼働時間",
"Usage reporting is always enabled for candidate releases.": "リリース候補版では常に使用状況レポートが送信されます。",
"Use HTTPS for GUI": "GUIにHTTPSを使用する",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "バージョン",
"Versions": "Versions",
"Versions Path": "古いバージョンを保存するパス",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "変更の監視",
"Watching for Changes": "変更の監視",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "新しいデバイスを追加する際は、相手側デバイスにもこのデバイスを追加してください。",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "新しいフォルダーを追加する際、フォルダーIDはデバイス間でフォルダーの対応づけに使われることに注意してください。フォルダーIDは大文字と小文字が区別され、共有するすべてのデバイスの間で完全に一致しなくてはなりません。",
"Yes": "はい",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "가동 시간",
"Usage reporting is always enabled for candidate releases.": "출시 후보 버전에서는 사용 보고서가 항상 활성화 됩니다.",
"Use HTTPS for GUI": "GUI에서 HTTPS 프로토콜 사용",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "버전",
"Versions": "버전",
"Versions Path": "버전 저장 경로",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "경고 : {{syncthingInotify}} 와 같은 외부 감시 도구를 사용하는 경우 비활성화되어 있는지 확인해야 합니다.",
"Watch for Changes": "변경 사항 감시",
"Watching for Changes": "변경 사항 감시",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "새 장치를 추가할 시 추가한 기기 쪽에서도 이 장치를 추가해야 합니다.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
"Yes": "예",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Veiksnumo laikas",
"Usage reporting is always enabled for candidate releases.": "Naudojimo ataskaita kandidatinėms versijoms visada yra įjungta.",
"Use HTTPS for GUI": "Valdymo skydeliui naudoti saugų ryšį ",
"Use notifications from the filesystem to detect changed items.": "Naudoti pranešimus iš failų sistemos, norint aptikti pakeistus elementus.",
"Variable Size Blocks": "Kintamųjų dydžių blokai",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Kintamųjų dydžių blokai (taip pat \"dideli blokai\") yra efektyvesni dideliems failams.",
"Version": "Versija",
"Versions": "Versijos",
"Versions Path": "Kelias iki versijos",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Įspėjimas: Jeigu naudojate išorinį stebėtoją, tokį kaip {{syncthingInotify}}, tuomet turite įsitikinti, kad jis yra išjungtas.",
"Watch for Changes": "Stebėti pakeitimus",
"Watching for Changes": "Stebimi pakeitimai",
"Watching for changes discovers most changes without periodic scanning.": "Pakeitimų stebėjimas aptinka daugumą pakeitimų be periodinio nuskaitymo.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Pridėdami įrenginį, turėkite omeny, kad šis įrenginys taip pat turi būti pridėtas kitoje pusėje.",
"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",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Oppetid",
"Usage reporting is always enabled for candidate releases.": "Bruksrapportering er alltid påslått for utgivelseskandidater",
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Versjon",
"Versions": "Versjoner",
"Versions Path": "Plassering av versjoner",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advarsel: Hvis du bruker en ekstern viser, som {{syncthingInotify}}, burde du forsikre deg om at den avslått.",
"Watch for Changes": "Hold oppsyn med endringer",
"Watching for Changes": "Holder oppsyn med endringer",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når du legger til en ny enhet, husk at enheten må legges til på andre siden også.",
"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",
+6 -2
View File
@@ -52,8 +52,8 @@
"Connection Type": "Soort verbinding",
"Connections": "Verbindingen",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Continu opvolgen van wijzigingen is nu beschikbaar in Syncthing. Dit zal wijzigingen op een schijf detecteren en alleen een scan uitvoeren op de gewijzigde paden. De voordelen zijn dat wijzigingen sneller doorgevoerd worden en dat minder volledige scans nodig zijn.",
"Copied from elsewhere": "Gekopieerd van ergens anders",
"Copied from original": "Gekopieerd van het origineel",
"Copied from elsewhere": "Gekopieerd van elders",
"Copied from original": "Gekopieerd van origineel",
"Copyright © 2014-2016 the following Contributors:": "Auteursrecht © 2014-2016 voor de volgende bijdragers:",
"Copyright © 2014-2017 the following Contributors:": "Auteursrecht © 2014-2017 voor de volgende bijdragers:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Negeerpatronen worden aangemaakt, bestaand bestand wordt overschreven op {{path}}.",
@@ -349,6 +349,9 @@
"Uptime": "Bedrijfstijd",
"Usage reporting is always enabled for candidate releases.": "Gebruiksrapportering is altijd ingeschakeld voor de kandidaat-releases.",
"Use HTTPS for GUI": "HTTPS gebruiken voor GUI",
"Use notifications from the filesystem to detect changed items.": "Meldingen van het bestandssysteem gebruiken om gewijzigde items te detecteren.",
"Variable Size Blocks": "Blokken met variabele grootte",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Blokken met variabele grootte (ook \"grote blokken\") zijn efficiënter voor grote bestanden.",
"Version": "Versie",
"Versions": "Versies",
"Versions Path": "Pad voor versies",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Waarschuwing: als u een externe opvolger gebruikt zoals {{syncthingInotify}}, moet u zich ervan verzekeren dat die uitgeschakeld is.",
"Watch for Changes": "Wijzigingen opvolgen",
"Watching for Changes": "Wijzigingen opvolgen",
"Watching for changes discovers most changes without periodic scanning.": "Opvolgen van wijzigingen ontdekt de meeste wijzigingen zonder periodiek scannen.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Houd er bij het toevoegen van een nieuw apparaat rekening mee dat dit apparaat ook aan de andere kant moet toegevoegd worden.",
"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 een nieuwe map rekening mee dat de map-ID gebruikt wordt om mappen aan elkaar te koppelen tussen apparaten. Ze zijn hoofdlettergevoelig en moeten exact overeenkomen op alle apparaten.",
"Yes": "Ja",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Czas działania",
"Usage reporting is always enabled for candidate releases.": "Raportowanie użycia dla wydań kandydujących jest zawsze włączone.",
"Use HTTPS for GUI": "Używaj HTTPS",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Wersja",
"Versions": "Wersje",
"Versions Path": "Ścieżka wersji",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeśli korzystasz z zewnętrznego obserwatora takiego jak {{syncthingInotify}}, upewnij się, że jest on dezaktywowany.",
"Watch for Changes": "Obserwuj zmiany",
"Watching for Changes": "Obserwowanie zmian",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Gdy dodajesz nowe urządzenie, pamiętaj że urządzenie musi zostać dodane także po drugiej stronie.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Przy dodawaniu nowego folderu, pamiętaj, że ID użyte jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ciągu ma znaczenie musi zgadzać się na wszystkich urządzeniach.",
"Yes": "Tak",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Tempo ligado",
"Usage reporting is always enabled for candidate releases.": "O relatório de uso está sempre habilitado em versões candidatas ao lançamento",
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Versão",
"Versions": "Versões",
"Versions Path": "Caminho do versionamento",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "Observar alterações",
"Watching for Changes": "Observando alterações",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "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 deve ser o mesmo em todos os dispositivos.",
"Yes": "Sim",
+5 -1
View File
@@ -249,7 +249,7 @@
"Running": "Em execução",
"Save": "Gravar",
"Scan Time Remaining": "Tempo restante da verificação",
"Scanning": "Verificando",
"Scanning": "Verificação de alterações",
"See external versioner help for supported templated command line parameters.": "Veja a ajuda do gestor de versões externo para saber que parâmetros da linha de comandos são suportados.",
"See external versioning help for supported templated command line parameters.": "Veja a ajuda externa sobre gestão de versões para ver os modelos suportados de parâmetros para a linha de comandos.",
"Select All": "Seleccionar tudo",
@@ -349,6 +349,9 @@
"Uptime": "Tempo em actividade",
"Usage reporting is always enabled for candidate releases.": "O relatório de utilização está sempre activado nas versões candidatas a lançamento.",
"Use HTTPS for GUI": "Utilizar HTTPS na interface gráfica",
"Use notifications from the filesystem to detect changed items.": "Use notificações do sistema de ficheiros para detectar itens alterados.",
"Variable Size Blocks": "Blocos de tamanho variável",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Blocos de tamanho variável (também designados por \"blocos grandes\") são mais eficientes para ficheiros grandes.",
"Version": "Versão",
"Versions": "Versões",
"Versions Path": "Caminho das versões",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Aviso: Se estiver a usar um verificador externo, tal como o {{syncthingInotify}}, deve certificar-se que está desactivado.",
"Watch for Changes": "Vigiar alterações",
"Watching for Changes": "Vigilância de alterações",
"Watching for changes discovers most changes without periodic scanning.": "A vigilância de alterações encontra a maior parte das alterações sem a verificação periódica.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando adicionar um novo dispositivo, lembre-se que este dispositivo tem que 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 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",
+37 -33
View File
@@ -10,7 +10,7 @@
"Add Device": "Добавить устройство",
"Add Folder": "Добавить папку",
"Add Remote Device": "Добавить удалённое устройство",
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
"Add devices from the introducer to our device list, for mutually shared folders.": "Добавлять устройства, известные рекомендателю, в список устройств, если есть общие с ними папки.",
"Add new folder?": "Добавить новую папку?",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Также будет увеличен интервал полного сканирования (в 60 раз, т.е. новое значение по умолчанию 1 час). Вы можете вручную настроить интервал для каждой папки, выбрав \"Нет\".",
"Address": "Адрес",
@@ -23,7 +23,7 @@
"Allowed Networks": "Разрешённые сети",
"Alphabetic": "По алфавиту",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Для версионирования используется внешняя программа. Ей нужно удалить файл из общей папки.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Для версионирования используется внешняя программа. Ей нужно удалить файл из общей папки. Если путь к приложению содержит пробелы, его нужно взять в кавычки.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Внешний процесс управляет версиями файлов. Процесс удалит файл из синхронизируемой папки.",
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимных отчётов изменился. Вы хотите переключиться на новый формат?",
@@ -34,8 +34,8 @@
"Auto Accept": "Автопринятие",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматическое обновление теперь предлагает выбор между стабильными выпусками и кандидатами в релизы.",
"Automatic upgrades": "Автообновление",
"Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Automatic upgrades are always enabled for candidate releases.": "Автоматическое обновление всегда включено для кандидатов в релизы.",
"Automatically create or share folders that this device advertises at the default path.": "Автоматически создавать, используя путь по умолчанию, или делиться папками, о которых сообщает это устройство.",
"Available debug logging facilities:": "Доступные средства отладочного журнала:",
"Be careful!": "Будьте осторожны!",
"Bugs": "Ошибки",
@@ -56,12 +56,12 @@
"Copied from original": "Скопировано с оригинала",
"Copyright © 2014-2016 the following Contributors:": "Авторские права © 2014–2016 принадлежат:",
"Copyright © 2014-2017 the following Contributors:": "Авторские права © 2014—2017 следующие участники:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Создание шаблонов игнорирования, существующий файл {{path}} будет перезаписан.",
"Danger!": "Опасно!",
"Debugging Facilities": "Средства отладки",
"Default Folder Path": "Путь для папок",
"Deleted": "Удалено",
"Deselect All": "Deselect All",
"Deselect All": "Снять выделение",
"Device": "Устройство",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство «{{name}}» ({{device}} на {{address}}) хочет подключиться. Добавить новое устройство?",
"Device ID": "ID устройства",
@@ -73,8 +73,8 @@
"Disabled": "Отключено",
"Disabled periodic scanning and disabled watching for changes": "Периодическое сканирование и отслеживание изменений отключено",
"Disabled periodic scanning and enabled watching for changes": "Периодическое сканирование отключено, отслеживание изменений включено",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Discard": "Discard",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодическое сканирование отключено, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
"Discard": "Отменить",
"Disconnected": "Нет соединения",
"Discovered": "Обнаружено",
"Discovery": "Обнаружение",
@@ -97,7 +97,7 @@
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Введите положительное значение (например, \"2.35\") и выберите единицу измерения, либо процент от общей ёмкости диска.",
"Enter a non-privileged port number (1024 - 65535).": "Введите непривилегированный порт (1024—65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введите через запятую («tcp://ip:port», «tcp://host:port») адреса, либо «dynamic», чтобы выполнить автоматическое обнаружение адреса.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "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": "Внешний контроль версий файлов",
@@ -115,7 +115,7 @@
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файлы защищены от изменений сделанных на других устройствах, но изменения сделанные на этом устройстве будут отправлены всему кластеру.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Файлы синхронизируются из группы, но изменения, сделанные на этом устройстве, не будут отправлены на другие устройства группы.",
"Filesystem Notifications": "Уведомления файловой системы",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filesystem Watcher Errors": "Ошибки отслеживания файловой системы",
"Filter by date": "Отфильтровать по дате",
"Filter by name": "Отфильтровать по имени",
"Folder": "Папка",
@@ -124,7 +124,7 @@
"Folder Path": "Путь к папке",
"Folder Type": "Тип папки",
"Folders": "Папки",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Для следующих папок произошла ошибка при запуске отслеживания изменений. Попытки будут повторяться раз в минуту, и ошибки скоро могут быть устранены. Если этого не произойдёт, попробуйте разобраться в причинах и попросите поддержки, если у вас не получится.",
"Full Rescan Interval (s)": "Интервал полного сканирования (в секундах)",
"GUI": "Интерфейс",
"GUI Authentication Password": "Пароль для доступа к панели управления",
@@ -159,14 +159,14 @@
"Later": "Позже",
"Latest Change": "Последнее изменение",
"Learn more": "Узнать больше",
"Limit": "Limit",
"Limit": "Ограничение",
"Listeners": "Прослушиватель",
"Loading data...": "Загрузка данных...",
"Loading...": "Загрузка...",
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
"Local State (Total)": "Локальное состояние (всего)",
"Locally Changed Items": "Locally Changed Items",
"Locally Changed Items": "Объекты, изменённые на этом компьютере",
"Log": "Журнал",
"Log tailing paused. Click here to continue.": "Вывод журнала приостановлен. Чтобы продолжить, нажмите здесь.",
"Log tailing paused. Scroll to bottom continue.": "Вывод журнала приостановлен. Чтобы продолжить, прокрутите журнал до конца.",
@@ -177,8 +177,8 @@
"Maximum Age": "Максимальный срок",
"Metadata Only": "Только метаданные",
"Minimum Free Disk Space": "Минимальное свободное место на диске",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Mod. Device": "Изм. устройство",
"Mod. Time": "Посл. изм.",
"Move to top of queue": "Поместить в начало очереди",
"Multi level wildcard (matches multiple directory levels)": "Многоуровневая маска (поиск совпадений во всех подпапках)",
"Never": "Никогда",
@@ -208,16 +208,16 @@
"Pause": "Пауза",
"Pause All": "Приостановить все",
"Paused": "Приостановлено",
"Pending changes": "Pending changes",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Pending changes": "Несохранённые изменения",
"Periodic scanning at given interval and disabled watching for changes": "Периодическое сканирование с заданным интервалом, отслеживание изменений отключено",
"Periodic scanning at given interval and enabled watching for changes": "Периодическое сканирование с заданным интервалом и включено отслеживание изменений",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Периодическое сканирование с заданным интервалом, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
"Permissions": "Разрешения",
"Please consult the release notes before performing a major upgrade.": "Перед проведением обновления основной версии ознакомтесь, пожалуйста, с Замечаниями к версии",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Установите имя пользователя и пароль для интерфейса в настройках",
"Please wait": "Пожалуйста, подождите",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
"Prefix indicating that the file can be deleted if preventing directory removal": "Префикс указывает, что файл может быть удалён, если он мешает удалить папку",
"Prefix indicating that the pattern should be matched without case sensitivity": "Префикс указывает, что регистр букв (заглавные/строчные) игнорируется",
"Preview": "Предварительный просмотр",
"Preview Usage Report": "Посмотреть отчёт об использовании",
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
@@ -245,14 +245,14 @@
"Resume": "Возобновить",
"Resume All": "Возобновить все",
"Reused": "Повторно использовано",
"Revert Local Changes": "Revert Local Changes",
"Revert Local Changes": "Отменить изменения на этом компьютере",
"Running": "Запущено",
"Save": "Сохранить",
"Scan Time Remaining": "Оставшееся время сканирования",
"Scanning": "Сканирование",
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select All": "Select All",
"See external versioner help for supported templated command line parameters.": "Поддерживаемые шаблонные параметры командной строки см. в документации сторонней программы контроля версий",
"See external versioning help for supported templated command line parameters.": "Поддерживаемые шаблонные параметры командной строки см. в документации сторонней программы контроля версий",
"Select All": "Выбрать все",
"Select a version": "Выберите версию",
"Select latest version": "Выбрать последнюю версию",
"Select oldest version": "Выбрать самую старую версию",
@@ -267,7 +267,7 @@
"Share With Devices": "Предоставить доступ устройствам",
"Share this folder?": "Предоставить доступ к этой папке?",
"Shared With": "Доступ предоставлен",
"Sharing": "Sharing",
"Sharing": "Предоставление доступа",
"Show ID": "Показать ID",
"Show QR": "Показать QR-код",
"Show diff with previous version": "Показать различия с предыдущей версией",
@@ -289,7 +289,7 @@
"Statistics": "Статистика",
"Stopped": "Остановлено",
"Support": "Поддержка",
"Support Bundle": "Support Bundle",
"Support Bundle": "Данные для поддержки",
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
"Syncing": "Синхронизация",
"Syncthing has been shut down.": "Syncthing был выключен.",
@@ -298,7 +298,7 @@
"Syncthing is upgrading.": "Обновление Syncthing.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Кажется, Syncthing не запущен или есть проблемы с подключением к Интернету. Переподключаюсь...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing столкнулся с проблемой при обработке Вашего запроса. Пожалуйста, обновите страницу или перезапустите Syncthing если проблема повторится.",
"Take me back": "Take me back",
"Take me back": "Вернуться к редактированию",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Эти изменения не вступят в силу, пока адрес панели управления переопределён в настройках запуска.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Административный интерфейс Syncthing настроен для предоставления удаленного доступа без пароля.",
"The aggregated statistics are publicly available at the URL below.": "Агрегированные статистические данные общедоступны по ссылке ниже.",
@@ -313,7 +313,7 @@
"The folder path cannot be blank.": "Путь к папке не должен быть пустым.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Используются следующие интервалы: в первый час версия меняется каждые 30 секунд, в первый день - каждый час, первые 30 дней - каждый день, после, до максимального срока - каждую неделю.",
"The following items could not be synchronized.": "Невозможно синхронизировать следующие объекты",
"The following items were changed locally.": "The following items were changed locally.",
"The following items were changed locally.": "Следующие объекты были изменены локально",
"The maximum age must be a number and cannot be blank.": "Максимальный срок должен быть числом и не может быть пустым.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальный срок хранения версии (в днях, 0 значит вечное хранение).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Минимальное свободное место на диске должно быть в процентах между 0 и 100 включительно.",
@@ -334,8 +334,8 @@
"Trash Can File Versioning": "Использовать версионность для файлов в Корзине",
"Type": "Тип",
"Unavailable": "Недоступно",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unavailable/Disabled by administrator or maintainer": "Недоступно или отключено администратором",
"Undecided (will prompt)": "Запрос каждый раз",
"Unignore": "Не игнорировать",
"Unknown": "Неизвестно",
"Unshared": "Необщедоступно",
@@ -349,18 +349,22 @@
"Uptime": "Время работы",
"Usage reporting is always enabled for candidate releases.": "Отправка отчётов об использовании всегда включена для кандидатов в релизы.",
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
"Use notifications from the filesystem to detect changed items.": "Использовать уведомления от файловой системы для обнаружения изменённых объектов.",
"Variable Size Blocks": "Блоки переменного размера",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Блоки переменного размера (или \"большие блоки\") более эффективны для файлов большого размера.",
"Version": "Версия",
"Versions": "Версии",
"Versions Path": "Путь к версиям",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Версии удаляются автоматически, если они существуют дольше максимального срока или превышают разрешённое количество файлов за интервал.",
"Waiting to scan": "Waiting to scan",
"Waiting to scan": "Ожидание сканирования",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolder}}».",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolderLabel}}» ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Осторожно, этот путь является подкаталогом существующей папки «{{otherFolder}}».",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Внимание! Этот путь — поддиректория уже существующей папки «{{otherFolderLabel}}» ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Внимание! Если вы используете внешнюю программу для отслеживания изменений, например, {{syncthingInotify}}, убедитесь, что она отключена.",
"Watch for Changes": "Следить за изменениями",
"Watching for Changes": "Слежение за изменениями",
"Watching for changes discovers most changes without periodic scanning.": "Отслеживание изменений обнаруживает большинство изменений без периодического сканирования.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Когда добавляете устройство, помните о том, что это же устройство должно быть добавлено и другой стороной.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
"Yes": "Да",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Doba prevádzky",
"Usage reporting is always enabled for candidate releases.": "Hlásenia o používaní sú pri kandidátoch na vydanie vždy povolené.",
"Use HTTPS for GUI": "Použiť HTTPS pre grafické rozhranie",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Verzia",
"Versions": "Verzie",
"Versions Path": "Cesta k verziám",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "Watch for Changes",
"Watching for Changes": "Watching for Changes",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "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": "Áno",
+5 -1
View File
@@ -211,7 +211,7 @@
"Pending changes": "Väntar på ändringar",
"Periodic scanning at given interval and disabled watching for changes": "Periodisk uppdatering i givet intervall och inaktiverad spaning efter ändringar",
"Periodic scanning at given interval and enabled watching for changes": "Periodisk uppdatering i givet intervall och aktiverad spaning efter ändringar",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk uppdatering i givet intervall och misslyckades med att ställa in spaning efter ändringar, försök igen var 1m:",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk uppdatering i givet intervall och misslyckades med att ställa in utkik efter ändringar, försök igen var 1m:",
"Permissions": "Behörigheter",
"Please consult the release notes before performing a major upgrade.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ställ in ett grafiska gränssnittets användarautentisering och lösenord i inställningsdialogrutan.",
@@ -349,6 +349,9 @@
"Uptime": "Drifttid",
"Usage reporting is always enabled for candidate releases.": "Användningsrapportering är alltid aktiverad för kandidatutgåvor.",
"Use HTTPS for GUI": "Använd HTTPS för gränssnittet",
"Use notifications from the filesystem to detect changed items.": "Använd aviseringar från filsystemet för att upptäcka ändrade objekt.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Version",
"Versions": "Versioner",
"Versions Path": "Sökväg för versioner",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Varning: Om du använder en extern bevakare som {{syncthingInotify}}, bör du se till att den är inaktiverad.",
"Watch for Changes": "Håll utkik efter ändringar",
"Watching for Changes": "Håller utkik efter ändringar",
"Watching for changes discovers most changes without periodic scanning.": "Hålla utkik efter ändringar upptäcker de flesta förändringar utan periodisk uppdatering.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet, kom ihåg att den här enheten måste läggas till på den andra enheten också.",
"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 mapp, tänk på att mapp-ID knyter ihop mappar mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
"Yes": "Ja",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "Тривалість роботи",
"Usage reporting is always enabled for candidate releases.": "Звіти про користування завжди увімкнені для реліз-кандидатів.",
"Use HTTPS for GUI": "Використовувати HTTPS для доступу до панелі управління",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "Версія",
"Versions": "Версії",
"Versions Path": "Шлях до версій",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "Моніторити зміни",
"Watching for Changes": "Моніторинг щмін",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Коли додаєте новий вузол, пам’ятайте, що цей вузол повинен бути доданий і на іншій стороні.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між пристроями. Назви повинні точно співпадати між усіма пристроями, регістр символів має значення.",
"Yes": "Так",
+6 -2
View File
@@ -289,7 +289,7 @@
"Statistics": "统计",
"Stopped": "已停止",
"Support": "支持",
"Support Bundle": "Support Bundle",
"Support Bundle": "支持捆绑包",
"Sync Protocol Listen Addresses": "协议监听地址",
"Syncing": "同步中",
"Syncthing has been shut down.": "Syncthing 已关闭。",
@@ -336,7 +336,7 @@
"Unavailable": "无效",
"Unavailable/Disabled by administrator or maintainer": "无效/禁用(由管理员或维护者)",
"Undecided (will prompt)": "待定(将提示)",
"Unignore": "屏蔽",
"Unignore": "解除忽略",
"Unknown": "未知",
"Unshared": "未共享",
"Unused": "未使用",
@@ -349,6 +349,9 @@
"Uptime": "已启动",
"Usage reporting is always enabled for candidate releases.": "发布候选版总是会启用使用报告。",
"Use HTTPS for GUI": "使用加密连接到图形管理页面",
"Use notifications from the filesystem to detect changed items.": "使用文件系统的通知来检测更改的项目。",
"Variable Size Blocks": "可变大小块",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "可变大小块(既“较大块”)对于大文件更有效率。",
"Version": "版本",
"Versions": "历史版本",
"Versions Path": "历史版本路径",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "警告:如果你在使用外部的监视器如 {{syncthingInotify}},你应该确保它已经取消激活。",
"Watch for Changes": "监视更改",
"Watching for Changes": "正在监视更改",
"Watching for changes discovers most changes without periodic scanning.": "对更改的监视无需定期扫描就可以发现大多数更改。",
"When adding a new device, keep in mind that this device must be added on the other side too.": "若您在本机添加新设备,记住您也必须在这个设备上添加本机。",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "若你添加了新文件夹,记住文件夹 ID 是用以在不同设备间建立联系的。在不同设备间拥有相同 ID 的文件夹将会被同步。且文件夹 ID 区分大小写。",
"Yes": "是",
+4
View File
@@ -349,6 +349,9 @@
"Uptime": "上線時間",
"Usage reporting is always enabled for candidate releases.": "發行候選版永遠啟用使用數據回報。",
"Use HTTPS for GUI": "為 GUI 使用 HTTPS",
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
"Variable Size Blocks": "Variable Size Blocks",
"Variable size blocks (also \"large blocks\") are more efficient for large files.": "Variable size blocks (also \"large blocks\") are more efficient for large files.",
"Version": "版本",
"Versions": "版本",
"Versions Path": "歷史版本路徑",
@@ -361,6 +364,7 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "警告:如果您正在使用外部監視工具,如 {{syncthingInotify}},您應該確認已經將其關閉。",
"Watch for Changes": "監視變動",
"Watching for Changes": "正在監視變動",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "當新增一個裝置時,務必記住,當前的這個裝置也同樣必須被添加至另一邊。",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "當新增一個資料夾時,請記住,資料夾識別碼是用來將裝置之間的資料夾綁定在一起的。它們有區分大小寫,且必須在所有裝置之間完全相同。",
"Yes": "是",
+2 -2
View File
@@ -709,7 +709,7 @@
<tr>
<th><span class="fas fa-fw fa-link"></span>&nbsp<span translate>Address</span></th>
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }}">
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }} {{ connections[deviceCfg.deviceID].crypto }}">
{{deviceAddr(deviceCfg)}}
</span>
</td>
@@ -740,7 +740,7 @@
<td translate class="text-right">Yes</td>
</tr>
<tr ng-if="deviceCfg.introducedBy">
<th><span class="far fa-fw fa-meh"></span>&nbsp;<span translate>Introduced By</span></th>
<th><span class="far fa-fw fa-handshake-o"></span>&nbsp;<span translate>Introduced By</span></th>
<td class="text-right">{{ deviceName(findDevice(deviceCfg.introducedBy)) || deviceCfg.introducedBy.substring(0, 5) }}</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
+34 -8
View File
@@ -9,10 +9,10 @@
</h1>
<hr />
<p translate>Copyright &copy; 2014-2017 the following Contributors:</p>
<p translate>Copyright &copy; 2014-2019 the following Contributors:</p>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, Aaron Bieber, Adam Piggott, Adel Qalieh, Alessandro G., Andrew Dunham, Andrew Rabert, Andrey D, Antoine Lamielle, Aranjedeath, Arthur Axel fREW Schmidt, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Dale Visser, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Han Boetes, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Iskander (Alex) Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matic Potočnik, Matt Burke, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Tilli, Mike Boone, MikeLund, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Niels Peter Roest, Nils Jakobi, NoLooseEnds, Oyebanji Jacob Mayowa, Pascal Jungblut, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Roman Zaynetdinov, Ross Smith II, Sacheendra Talluri, Scott Klupfel, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tomas Cerveny, Tommy Thorn, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, chucic, derekriemer, desbma, janost, jaseg, klemens, marco-m, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, Aaron Bieber, Adam Piggott, Adel Qalieh, Alessandro G., Andrew Dunham, Andrew Rabert, Andrey D, Antoine Lamielle, Aranjedeath, Arthur Axel fREW Schmidt, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Dale Visser, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Evgeny Kuznetsov, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Han Boetes, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Tilli, Mike Boone, MikeLund, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Niels Peter Roest, Nils Jakobi, NoLooseEnds, Oyebanji Jacob Mayowa, Pascal Jungblut, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Roman Zaynetdinov, Ross Smith II, Sacheendra Talluri, Scott Klupfel, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tomas Cerveny, Tommy Thorn, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, chucic, derekriemer, desbma, janost, jaseg, klemens, marco-m, otbutz, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
</div>
</div>
<hr />
@@ -20,21 +20,47 @@ Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, And
<p translate>Syncthing includes the following software or portions thereof:</p>
<ul class="list-unstyled two-columns" id="copyright-notices">
<li><a href="http://getbootstrap.com/">Bootstrap</a>, Copyright &copy; 2011-2016 Twitter, Inc.</li>
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright &copy; 2010-2016 Google, Inc.</li>
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright &copy; 2010-2014, 2016 Google, Inc.</li>
<li><a href="http://www.daterangepicker.com/">Date Range Picker</a>, Copyright &copy; 2012-2018 Dan Grossman.</li>
<li><a href="https://github.com/mar10/fancytree">JQuery Fancytree Plugin</a>, Copyright &copy; 2008-2018 Martin Wendt.</li>
<li><a href="https://forkaweso.me/Fork-Awesome/">Fork Awesome</a>, Copyright &copy; 2018 Dave Gandy &amp; Fork Awesome.</li>
<li><a href="http://jquery.com/">jQuery JavaScript Library</a>, Copyright &copy; jQuery Foundation and other contributors.</li>
<li><a href="http://momentjs.com/">moment.js</a>, Copyright &copy; JS Foundation and other contributors.</li>
<li><a href="https://prometheus.io/">Prometheus</a>, Copyright &copy; 2012-2015 The Prometheus Authors.</li>
<li><a href="https://github.com/AudriusButkevicius/go-nat-pmp">AudriusButkevicius/go-nat-pmp</a>, Copyright &copy; 2013 John Howard Palevich.</li>
<li><a href="https://github.com/AudriusButkevicius/recli">AudriusButkevicius/recli</a>, Copyright &copy; 2019 Audrius Butkevicius.</li>
<li><a href="https://github.com/beorn7/perks">beorn7/perks</a>, Copyright &copy; 2013 Blake Mizerany.</li>
<li><a href="https://github.com/bkaradzic/go-lz4">bkaradzic/go-lz4</a>, Copyright &copy; 2011-2012 Branimir Karadzic, 2013 Damian Gryski.</li>
<li><a href="https://github.com/chmduquesne/rollinghash">chmduquesne/rollinghash</a>, Copyright &copy; 2015 Christophe-Marie Duquesne</li>
<li><a href="https://github.com/calmh/du">calmh/du</a>, Public domain.</li>
<li><a href="https://github.com/calmh/xdr">calmh/xdr</a>, Copyright &copy; 2014 Jakob Borg.</li>
<li><a href="https://github.com/chmduquesne/rollinghash">chmduquesne/rollinghash</a>, Copyright &copy; 2015 Christophe-Marie Duquesne.</li>
<li><a href="https://github.com/d4l3k/messagediff">d4l3k/messagediff</a>, Copyright &copy; 2015 Tristan Rice.</li>
<li><a href="https://github.com/flynn-archive/go-shlex">flynn-archive/go-shlex</a>, Copyright &copy; 2012 Google Inc.</li>
<li><a href="https://github.com/gobwas/glob">gobwas/glob</a>, Copyright &copy; 2016 Sergey Kamardin.</li>
<li><a href="https://github.com/gogo/protobuf">gogo/protobuf</a>, Copyright &copy; 2013 The GoGo Authors.</li>
<li><a href="https://github.com/golang/groupcache">golang/groupcache</a>, Copyright &copy; 2013 Google Inc.</li>
<li><a href="https://github.com/golang/protobuf">golang/protobuf</a>, Copyright &copy; 2010 The Go Authors.</li>
<li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright &copy; 2011 The Snappy-Go Authors.</li>
<li><a href="https://github.com/jackpal/gateway">jackpal/gateway</a>, Copyright &copy; 2010 Jack Palevich.</li>
<li><a href="https://github.com/minio/sha256-simd">minio/sha256-simd</a>, Copyright &copy; 2016 Minio, Inc.</li>
<li><a href="https://github.com/kballard/go-shellquote">kballard/go-shellquote</a>, Copyright &copy; 2014 Kevin Ballard.</li>
<li><a href="https://github.com/lib/pq">lib/pq</a>, Copyright &copy; 2011-2013, 'pq' Contributors, portions Copyright &copy; 2011 Blake Mizerany.</li>
<li><a href="https://github.com/mattn/go-isatty">mattn/go-isatty</a>, Copyright &copy; Yasuhiro MATSUMOTO.</li>
<li><a href="https://github.com/matttproud/golang_protobuf_extensions">matttproud/golang_protobuf_extensions</a>, Copyright &copy; 2012 Matt T. Proud.</li>
<li><a href="https://github.com/minio/sha256-simd">minio/sha256-simd</a>, Copyright &copy; 2016-2017 Minio, Inc.</li>
<li><a href="https://github.com/oschwald/geoip2-golang">oschwald/geoip2-golang</a>, Copyright &copy; 2015, Gregory J. Oschwald.</li>
<li><a href="https://github.com/oschwald/maxminddb-golang">oschwald/maxminddb-golang</a>, Copyright &copy; 2015, Gregory J. Oschwald.</li>
<li><a href="https://github.com/petermattis/goid">petermattis/goid</a>, Copyright &copy; 2015-2016 Peter Mattis.</li>
<li><a href="https://github.com/pkg/errors">pkg/errors</a>, Copyright &copy; 2015, Dave Cheney.</li>
<li><a href="https://github.com/rcrowley/go-metrics">rcrowley/go-metrics</a>, Copyright &copy; 2012 Richard Crowley.</li>
<li><a href="https://github.com/sasha-s/go-deadlock">sasha-s/go-deadlock</a>, Copyright &copy; 2016 sasha-s</li>
<li><a href="https://github.com/sasha-s/go-deadlock">sasha-s/go-deadlock</a>, Copyright &copy; 2016 sasha-s.</li>
<li><a href="https://github.com/syncthing/notify">syncthing/notify</a>, Copyright &copy; 2014-2015 The Notify Authors.</li>
<li><a href="https://github.com/syndtr/goleveldb">syndtr/goleveldb</a>, Copyright &copy; 2012 Suryandaru Triandana.</li>
<li><a href="https://github.com/thejerf/suture">thejerf/suture</a>, Copyright &copy; 2014-2015 Barracuda Networks, Inc.</li>
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright &copy; The Go Authors.</li>
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright &copy; 2012 The Go Authors.</li>
<li><a href="https://github.com/urfave/cli">urfave/cli</a>, Copyright &copy; 2016 Jeremy Saenz &amp; Contributors.</li>
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright &copy; 2010-2011 The Go Authors.</li>
<li><a href="https://gopkg.in/asn1-ber.v1">gopkg.in/asn1-ber.v1</a>, Copyright &copy; 2011-2015 Michael Mitton, portions Copyright &copy; 2015-2016 go-asn1-ber Authors.</li>
<li><a href="https://gopkg.in/ldap.v2">gopkg.in/ldap.v2</a>, Copyright &copy; 2011-2015 Michael Mitton, portions Copyright &copy; 2015-2016 go-ldap Authors.</li>
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright &copy; 2009 The Go Authors.</li>
<li>Font Awesome by Dave Gandy - <a href="http://fontawesome.io/">http://fontawesome.io</a></li>
</ul>
</div>
+4 -4
View File
@@ -84,18 +84,18 @@ func New(myID protocol.DeviceID) Configuration {
return cfg
}
func NewWithFreePorts(myID protocol.DeviceID) Configuration {
func NewWithFreePorts(myID protocol.DeviceID) (Configuration, error) {
cfg := New(myID)
port, err := getFreePort("127.0.0.1", DefaultGUIPort)
if err != nil {
l.Fatalln("get free port (GUI):", err)
return Configuration{}, fmt.Errorf("get free port (GUI): %v", err)
}
cfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port)
port, err = getFreePort("0.0.0.0", DefaultTCPPort)
if err != nil {
l.Fatalln("get free port (BEP):", err)
return Configuration{}, fmt.Errorf("get free port (BEP): %v", err)
}
if port == DefaultTCPPort {
cfg.Options.ListenAddresses = []string{"default"}
@@ -106,7 +106,7 @@ func NewWithFreePorts(myID protocol.DeviceID) Configuration {
}
}
return cfg
return cfg, nil
}
func ReadXML(r io.Reader, myID protocol.DeviceID) (Configuration, error) {
+3 -3
View File
@@ -93,7 +93,7 @@ func TestDeviceConfig(t *testing.T) {
t.Fatal("Unexpected file")
}
cfg := wr.cfg
cfg := wr.(*wrapper).cfg
expectedFolders := []FolderConfiguration{
{
@@ -515,7 +515,7 @@ func TestNewSaveLoad(t *testing.T) {
cfg := Wrap(path, intCfg)
// To make the equality pass later
cfg.cfg.XMLName.Local = "configuration"
cfg.(*wrapper).cfg.XMLName.Local = "configuration"
if exists(path) {
t.Error(path, "exists")
@@ -827,7 +827,7 @@ func TestIgnoredFolders(t *testing.T) {
// 2 for folder2, 1 for folder1, as non-existing device and device the folder is shared with is removed.
expectedIgnoredFolders := 3
for _, dev := range wrapper.cfg.Devices {
for _, dev := range wrapper.Devices() {
expectedIgnoredFolders -= len(dev.IgnoredFolders)
}
if expectedIgnoredFolders != 0 {
+2 -2
View File
@@ -52,7 +52,7 @@ type FolderConfiguration struct {
Paused bool `xml:"paused" json:"paused"`
WeakHashThresholdPct int `xml:"weakHashThresholdPct" json:"weakHashThresholdPct"` // Use weak hash if more than X percent of the file has changed. Set to -1 to always use weak hash.
MarkerName string `xml:"markerName" json:"markerName"`
UseLargeBlocks bool `xml:"useLargeBlocks" json:"useLargeBlocks"`
UseLargeBlocks bool `xml:"useLargeBlocks" json:"useLargeBlocks" default:"true"`
CopyOwnershipFromParent bool `xml:"copyOwnershipFromParent" json:"copyOwnershipFromParent"`
cachedFilesystem fs.Filesystem
@@ -106,7 +106,7 @@ func (f FolderConfiguration) Versioner() versioner.Versioner {
}
versionerFactory, ok := versioner.Factories[f.Versioning.Type]
if !ok {
l.Fatalf("Requested versioning type %q that does not exist", f.Versioning.Type)
panic(fmt.Sprintf("Requested versioning type %q that does not exist", f.Versioning.Type))
}
return versionerFactory(f.ID, f.Filesystem(), f.Versioning.Params)
+75 -37
View File
@@ -52,10 +52,48 @@ type noopWaiter struct{}
func (noopWaiter) Wait() {}
// A wrapper around a Configuration that manages loads, saves and published
// A Wrapper around a Configuration that manages loads, saves and published
// notifications of changes to registered Handlers
type Wrapper interface {
MyName() string
ConfigPath() string
type Wrapper struct {
RawCopy() Configuration
Replace(cfg Configuration) (Waiter, error)
RequiresRestart() bool
Save() error
GUI() GUIConfiguration
SetGUI(gui GUIConfiguration) (Waiter, error)
LDAP() LDAPConfiguration
Options() OptionsConfiguration
SetOptions(opts OptionsConfiguration) (Waiter, error)
Folder(id string) (FolderConfiguration, bool)
Folders() map[string]FolderConfiguration
FolderList() []FolderConfiguration
SetFolder(fld FolderConfiguration) (Waiter, error)
Device(id protocol.DeviceID) (DeviceConfiguration, bool)
Devices() map[protocol.DeviceID]DeviceConfiguration
RemoveDevice(id protocol.DeviceID) (Waiter, error)
SetDevice(DeviceConfiguration) (Waiter, error)
SetDevices([]DeviceConfiguration) (Waiter, error)
AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string)
AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID)
IgnoredDevice(id protocol.DeviceID) bool
IgnoredFolder(device protocol.DeviceID, folder string) bool
ListenAddresses() []string
GlobalDiscoveryServers() []string
Subscribe(c Committer)
Unsubscribe(c Committer)
}
type wrapper struct {
cfg Configuration
path string
@@ -69,8 +107,8 @@ type Wrapper struct {
// Wrap wraps an existing Configuration structure and ties it to a file on
// disk.
func Wrap(path string, cfg Configuration) *Wrapper {
w := &Wrapper{
func Wrap(path string, cfg Configuration) Wrapper {
w := &wrapper{
cfg: cfg,
path: path,
mut: sync.NewMutex(),
@@ -80,7 +118,7 @@ func Wrap(path string, cfg Configuration) *Wrapper {
// Load loads an existing file on disk and returns a new configuration
// wrapper.
func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
func Load(path string, myID protocol.DeviceID) (Wrapper, error) {
fd, err := os.Open(path)
if err != nil {
return nil, err
@@ -95,13 +133,13 @@ func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
return Wrap(path, cfg), nil
}
func (w *Wrapper) ConfigPath() string {
func (w *wrapper) ConfigPath() string {
return w.path
}
// Subscribe registers the given handler to be called on any future
// configuration changes.
func (w *Wrapper) Subscribe(c Committer) {
func (w *wrapper) Subscribe(c Committer) {
w.mut.Lock()
w.subs = append(w.subs, c)
w.mut.Unlock()
@@ -109,7 +147,7 @@ func (w *Wrapper) Subscribe(c Committer) {
// Unsubscribe de-registers the given handler from any future calls to
// configuration changes
func (w *Wrapper) Unsubscribe(c Committer) {
func (w *wrapper) Unsubscribe(c Committer) {
w.mut.Lock()
for i := range w.subs {
if w.subs[i] == c {
@@ -123,20 +161,20 @@ func (w *Wrapper) Unsubscribe(c Committer) {
}
// RawCopy returns a copy of the currently wrapped Configuration object.
func (w *Wrapper) RawCopy() Configuration {
func (w *wrapper) RawCopy() Configuration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.Copy()
}
// Replace swaps the current configuration object for the given one.
func (w *Wrapper) Replace(cfg Configuration) (Waiter, error) {
func (w *wrapper) Replace(cfg Configuration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
return w.replaceLocked(cfg.Copy())
}
func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
from := w.cfg
if err := to.clean(); err != nil {
@@ -158,7 +196,7 @@ func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
return w.notifyListeners(from.Copy(), to.Copy()), nil
}
func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
func (w *wrapper) notifyListeners(from, to Configuration) Waiter {
wg := sync.NewWaitGroup()
wg.Add(len(w.subs))
for _, sub := range w.subs {
@@ -170,7 +208,7 @@ func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
return wg
}
func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
func (w *wrapper) notifyListener(sub Committer, from, to Configuration) {
l.Debugln(sub, "committing configuration")
if !sub.CommitConfiguration(from, to) {
l.Debugln(sub, "requires restart")
@@ -179,7 +217,7 @@ func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
}
// Devices returns a map of devices.
func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
func (w *wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
if w.deviceMap == nil {
@@ -193,7 +231,7 @@ func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
// SetDevices adds new devices to the configuration, or overwrites existing
// devices with the same ID.
func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
func (w *wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
@@ -218,12 +256,12 @@ func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
// SetDevice adds a new device to the configuration, or overwrites an existing
// device with the same ID.
func (w *Wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
func (w *wrapper) SetDevice(dev DeviceConfiguration) (Waiter, error) {
return w.SetDevices([]DeviceConfiguration{dev})
}
// RemoveDevice removes the device from the configuration
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
func (w *wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
@@ -240,7 +278,7 @@ func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
// Folders returns a map of folders. Folder structures should not be changed,
// other than for the purpose of updating via SetFolder().
func (w *Wrapper) Folders() map[string]FolderConfiguration {
func (w *wrapper) Folders() map[string]FolderConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
if w.folderMap == nil {
@@ -253,7 +291,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
}
// FolderList returns a slice of folders.
func (w *Wrapper) FolderList() []FolderConfiguration {
func (w *wrapper) FolderList() []FolderConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.Copy().Folders
@@ -261,7 +299,7 @@ func (w *Wrapper) FolderList() []FolderConfiguration {
// SetFolder adds a new folder to the configuration, or overwrites an existing
// folder with the same ID.
func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
func (w *wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
@@ -280,14 +318,14 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
}
// Options returns the current options configuration object.
func (w *Wrapper) Options() OptionsConfiguration {
func (w *wrapper) Options() OptionsConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.Options.Copy()
}
// SetOptions replaces the current options configuration object.
func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
func (w *wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
@@ -295,21 +333,21 @@ func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
return w.replaceLocked(newCfg)
}
func (w *Wrapper) LDAP() LDAPConfiguration {
func (w *wrapper) LDAP() LDAPConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.LDAP.Copy()
}
// GUI returns the current GUI configuration object.
func (w *Wrapper) GUI() GUIConfiguration {
func (w *wrapper) GUI() GUIConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.GUI.Copy()
}
// SetGUI replaces the current GUI configuration object.
func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
func (w *wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
@@ -319,7 +357,7 @@ func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
// IgnoredDevice returns whether or not connection attempts from the given
// device should be silently ignored.
func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
w.mut.Lock()
defer w.mut.Unlock()
for _, device := range w.cfg.IgnoredDevices {
@@ -332,7 +370,7 @@ func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
// IgnoredFolder returns whether or not share attempts for the given
// folder should be silently ignored.
func (w *Wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
func (w *wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
dev, ok := w.Device(device)
if !ok {
return false
@@ -341,7 +379,7 @@ func (w *Wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
}
// Device returns the configuration for the given device and an "ok" bool.
func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
func (w *wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
w.mut.Lock()
defer w.mut.Unlock()
for _, device := range w.cfg.Devices {
@@ -353,7 +391,7 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
}
// Folder returns the configuration for the given folder and an "ok" bool.
func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
func (w *wrapper) Folder(id string) (FolderConfiguration, bool) {
w.mut.Lock()
defer w.mut.Unlock()
for _, folder := range w.cfg.Folders {
@@ -365,7 +403,7 @@ func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
}
// Save writes the configuration to disk, and generates a ConfigSaved event.
func (w *Wrapper) Save() error {
func (w *wrapper) Save() error {
w.mut.Lock()
defer w.mut.Unlock()
@@ -390,7 +428,7 @@ func (w *Wrapper) Save() error {
return nil
}
func (w *Wrapper) GlobalDiscoveryServers() []string {
func (w *wrapper) GlobalDiscoveryServers() []string {
var servers []string
for _, srv := range w.Options().GlobalAnnServers {
switch srv {
@@ -407,7 +445,7 @@ func (w *Wrapper) GlobalDiscoveryServers() []string {
return util.UniqueStrings(servers)
}
func (w *Wrapper) ListenAddresses() []string {
func (w *wrapper) ListenAddresses() []string {
var addresses []string
for _, addr := range w.Options().ListenAddresses {
switch addr {
@@ -420,15 +458,15 @@ func (w *Wrapper) ListenAddresses() []string {
return util.UniqueStrings(addresses)
}
func (w *Wrapper) RequiresRestart() bool {
func (w *wrapper) RequiresRestart() bool {
return atomic.LoadUint32(&w.requiresRestart) != 0
}
func (w *Wrapper) setRequiresRestart() {
func (w *wrapper) setRequiresRestart() {
atomic.StoreUint32(&w.requiresRestart, 1)
}
func (w *Wrapper) MyName() string {
func (w *wrapper) MyName() string {
w.mut.Lock()
myID := w.cfg.MyID
w.mut.Unlock()
@@ -436,7 +474,7 @@ func (w *Wrapper) MyName() string {
return cfg.Name
}
func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
func (w *wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) {
defer w.Save()
w.mut.Lock()
@@ -459,7 +497,7 @@ func (w *Wrapper) AddOrUpdatePendingDevice(device protocol.DeviceID, name, addre
})
}
func (w *Wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
func (w *wrapper) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {
defer w.Save()
w.mut.Lock()
+1 -1
View File
@@ -36,7 +36,7 @@ func TestIsLANHost(t *testing.T) {
AlwaysLocalNets: []string{"10.20.30.0/24"},
},
})
s := &Service{cfg: cfg}
s := &service{cfg: cfg}
for _, tc := range cases {
res := s.isLANHost(tc.addr)
+1 -1
View File
@@ -36,7 +36,7 @@ type waiter interface {
const limiterBurstSize = 4 * 128 << 10
func newLimiter(cfg *config.Wrapper) *limiter {
func newLimiter(cfg config.Wrapper) *limiter {
l := &limiter{
write: rate.NewLimiter(rate.Inf, limiterBurstSize),
read: rate.NewLimiter(rate.Inf, limiterBurstSize),
+1 -1
View File
@@ -24,7 +24,7 @@ func init() {
device4, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
}
func initConfig() *config.Wrapper {
func initConfig() config.Wrapper {
cfg := config.Wrap("/dev/null", config.New(device1))
dev1Conf = config.NewDeviceConfiguration(device1, "device1")
dev2Conf = config.NewDeviceConfiguration(device2, "device2")
+2 -2
View File
@@ -22,7 +22,7 @@ func init() {
}
type relayDialer struct {
cfg *config.Wrapper
cfg config.Wrapper
tlsCfg *tls.Config
}
@@ -70,7 +70,7 @@ func (d *relayDialer) RedialFrequency() time.Duration {
type relayDialerFactory struct{}
func (relayDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
func (relayDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &relayDialer{
cfg: cfg,
tlsCfg: tlsCfg,
+2 -2
View File
@@ -29,7 +29,7 @@ type relayListener struct {
onAddressesChangedNotifier
uri *url.URL
cfg *config.Wrapper
cfg config.Wrapper
tlsCfg *tls.Config
conns chan internalConn
factory listenerFactory
@@ -180,7 +180,7 @@ func (t *relayListener) NATType() string {
type relayListenerFactory struct{}
func (f *relayListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
return &relayListener{
uri: uri,
cfg: cfg,
+35 -18
View File
@@ -51,6 +51,7 @@ const (
// From go/src/crypto/tls/cipher_suites.go
var tlsCipherSuiteNames = map[uint16]string{
// TLS 1.2
0x0005: "TLS_RSA_WITH_RC4_128_SHA",
0x000a: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
0x002f: "TLS_RSA_WITH_AES_128_CBC_SHA",
@@ -73,13 +74,29 @@ var tlsCipherSuiteNames = map[uint16]string{
0xc02c: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
0xcca8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
0xcca9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
// TLS 1.3
0x1301: "TLS_AES_128_GCM_SHA256",
0x1302: "TLS_AES_256_GCM_SHA384",
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
}
var tlsVersionNames = map[uint16]string{
tls.VersionTLS12: "TLS1.2",
772: "TLS1.3", // tls.VersionTLS13 constant available in Go 1.12+
}
// Service listens and dials all configured unconnected devices, via supported
// dialers. Successful connections are handed to the model.
type Service struct {
type Service interface {
suture.Service
Status() map[string]interface{}
NATType() string
}
type service struct {
*suture.Supervisor
cfg *config.Wrapper
cfg config.Wrapper
myID protocol.DeviceID
model Model
tlsCfg *tls.Config
@@ -97,10 +114,10 @@ type Service struct {
listenerSupervisor *suture.Supervisor
}
func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
bepProtocolName string, tlsDefaultCommonName string) *Service {
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
bepProtocolName string, tlsDefaultCommonName string) *service {
service := &Service{
service := &service{
Supervisor: suture.New("connections.Service", suture.Spec{
Log: func(line string) {
l.Infoln(line)
@@ -156,7 +173,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
return service
}
func (s *Service) handle() {
func (s *service) handle() {
next:
for c := range s.conns {
cs := c.ConnectionState()
@@ -275,14 +292,14 @@ next:
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
modelConn := completeConn{c, protoConn}
l.Infof("Established secure connection to %s at %s (%s)", remoteID, c, tlsCipherSuiteNames[c.ConnectionState().CipherSuite])
l.Infof("Established secure connection to %s at %s", remoteID, c)
s.model.AddConnection(modelConn, hello)
continue next
}
}
func (s *Service) connect() {
func (s *service) connect() {
nextDial := make(map[string]time.Time)
// Used as delay for the first few connection attempts, increases
@@ -433,7 +450,7 @@ func (s *Service) connect() {
}
}
func (s *Service) isLANHost(host string) bool {
func (s *service) isLANHost(host string) bool {
// Probably we are called with an ip:port combo which we can resolve as
// a TCP address.
if addr, err := net.ResolveTCPAddr("tcp", host); err == nil {
@@ -447,7 +464,7 @@ func (s *Service) isLANHost(host string) bool {
return false
}
func (s *Service) isLAN(addr net.Addr) bool {
func (s *service) isLAN(addr net.Addr) bool {
var ip net.IP
switch addr := addr.(type) {
@@ -488,7 +505,7 @@ func (s *Service) isLAN(addr net.Addr) bool {
return false
}
func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
// must be called with listenerMut held
l.Debugln("Starting listener", uri)
@@ -500,7 +517,7 @@ func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
return true
}
func (s *Service) logListenAddressesChangedEvent(l genericListener) {
func (s *service) logListenAddressesChangedEvent(l genericListener) {
events.Default.Log(events.ListenAddressesChanged, map[string]interface{}{
"address": l.URI(),
"lan": l.LANAddresses(),
@@ -508,11 +525,11 @@ func (s *Service) logListenAddressesChangedEvent(l genericListener) {
})
}
func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
func (s *service) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
func (s *service) CommitConfiguration(from, to config.Configuration) bool {
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
for _, dev := range to.Devices {
newDevices[dev.DeviceID] = true
@@ -589,7 +606,7 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
return true
}
func (s *Service) AllAddresses() []string {
func (s *service) AllAddresses() []string {
s.listenersMut.RLock()
var addrs []string
for _, listener := range s.listeners {
@@ -604,7 +621,7 @@ func (s *Service) AllAddresses() []string {
return util.UniqueStrings(addrs)
}
func (s *Service) ExternalAddresses() []string {
func (s *service) ExternalAddresses() []string {
s.listenersMut.RLock()
var addrs []string
for _, listener := range s.listeners {
@@ -616,7 +633,7 @@ func (s *Service) ExternalAddresses() []string {
return util.UniqueStrings(addrs)
}
func (s *Service) Status() map[string]interface{} {
func (s *service) Status() map[string]interface{} {
s.listenersMut.RLock()
result := make(map[string]interface{})
for addr, listener := range s.listeners {
@@ -636,7 +653,7 @@ func (s *Service) Status() map[string]interface{} {
return result
}
func (s *Service) NATType() string {
func (s *service) NATType() string {
s.listenersMut.RLock()
defer s.listenersMut.RUnlock()
for _, listener := range s.listeners {
+9 -3
View File
@@ -27,6 +27,7 @@ type Connection interface {
RemoteAddr() net.Addr
Priority() int
String() string
Crypto() string
}
// completeConn is the aggregation of an internalConn and the
@@ -101,6 +102,11 @@ func (c internalConn) Priority() int {
return c.priority
}
func (c internalConn) Crypto() string {
cs := c.ConnectionState()
return fmt.Sprintf("%s-%s", tlsVersionNames[cs.Version], tlsCipherSuiteNames[cs.CipherSuite])
}
func (c internalConn) Transport() string {
transport := c.connType.Transport()
host, _, err := net.SplitHostPort(c.LocalAddr().String())
@@ -118,11 +124,11 @@ func (c internalConn) Transport() string {
}
func (c internalConn) String() string {
return fmt.Sprintf("%s-%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type())
return fmt.Sprintf("%s-%s/%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto())
}
type dialerFactory interface {
New(*config.Wrapper, *tls.Config) genericDialer
New(config.Wrapper, *tls.Config) genericDialer
Priority() int
AlwaysWAN() bool
Valid(config.Configuration) error
@@ -135,7 +141,7 @@ type genericDialer interface {
}
type listenerFactory interface {
New(*url.URL, *config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
New(*url.URL, config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
Valid(config.Configuration) error
}
+2 -2
View File
@@ -24,7 +24,7 @@ func init() {
}
type tcpDialer struct {
cfg *config.Wrapper
cfg config.Wrapper
tlsCfg *tls.Config
}
@@ -62,7 +62,7 @@ func (d *tcpDialer) RedialFrequency() time.Duration {
type tcpDialerFactory struct{}
func (tcpDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
func (tcpDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &tcpDialer{
cfg: cfg,
tlsCfg: tlsCfg,
+2 -2
View File
@@ -29,7 +29,7 @@ type tcpListener struct {
onAddressesChangedNotifier
uri *url.URL
cfg *config.Wrapper
cfg config.Wrapper
tlsCfg *tls.Config
stop chan struct{}
conns chan internalConn
@@ -195,7 +195,7 @@ func (t *tcpListener) NATType() string {
type tcpListenerFactory struct{}
func (f *tcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
return &tcpListener{
uri: fixupPort(uri, config.DefaultTCPPort),
cfg: cfg,
+2 -2
View File
@@ -73,7 +73,7 @@ func addToBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
name := []byte(f.Name)
for i, block := range f.Blocks {
binary.BigEndian.PutUint32(blockBuf, uint32(i))
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
keyBuf = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Put(keyBuf, blockBuf)
}
}
@@ -89,7 +89,7 @@ func discardFromBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() {
name := []byte(ef.Name)
for _, block := range ef.Blocks {
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
keyBuf = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Delete(keyBuf)
}
}
+3 -3
View File
@@ -80,7 +80,7 @@ func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
if ok {
if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() {
for _, block := range ef.Blocks {
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
keyBuf = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Delete(keyBuf)
}
}
@@ -100,7 +100,7 @@ func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
l.Debugf("insert (local); folder=%q %v", folder, f)
t.Put(dk, mustMarshal(&f))
gk = t.db.keyer.GenerateGlobalVersionKey(gk, folder, []byte(f.Name))
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, []byte(f.Name))
keyBuf, _ = t.updateGlobal(gk, keyBuf, folder, protocol.LocalDeviceID[:], f, meta)
keyBuf = db.keyer.GenerateSequenceKey(keyBuf, folder, f.Sequence)
@@ -110,7 +110,7 @@ func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
if !f.IsDirectory() && !f.IsDeleted() && !f.IsInvalid() {
for i, block := range f.Blocks {
binary.BigEndian.PutUint32(blockBuf, uint32(i))
keyBuf = t.db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
keyBuf = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Put(keyBuf, blockBuf)
}
}
+28 -1
View File
@@ -19,7 +19,8 @@ import (
const (
dbMaxOpenFiles = 100
dbWriteBuffer = 4 << 20
dbWriteBuffer = 16 << 20
dbFlushBatch = dbWriteBuffer / 4 // Some leeway for any leveldb in-memory optimizations
)
// Lowlevel is the lowest level database interface. It has a very simple
@@ -127,3 +128,29 @@ func leveldbIsCorrupted(err error) bool {
return false
}
type batch struct {
*leveldb.Batch
db *Lowlevel
}
func (db *Lowlevel) newBatch() *batch {
return &batch{
Batch: new(leveldb.Batch),
db: db,
}
}
// checkFlush flushes and resets the batch if its size exceeds dbFlushBatch.
func (b *batch) checkFlush() {
if len(b.Dump()) > dbFlushBatch {
b.flush()
b.Reset()
}
}
func (b *batch) flush() {
if err := b.db.Write(b.Batch, nil); err != nil {
panic(err)
}
}
+3 -13
View File
@@ -10,7 +10,6 @@ import (
"encoding/binary"
"time"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
@@ -39,21 +38,12 @@ func NewNamespacedKV(db *Lowlevel, prefix string) *NamespacedKV {
func (n *NamespacedKV) Reset() {
it := n.db.NewIterator(util.BytesPrefix(n.prefix), nil)
defer it.Release()
batch := new(leveldb.Batch)
batch := n.db.newBatch()
for it.Next() {
batch.Delete(it.Key())
if batch.Len() > batchFlushSize {
if err := n.db.Write(batch, nil); err != nil {
panic(err)
}
batch.Reset()
}
}
if batch.Len() > 0 {
if err := n.db.Write(batch, nil); err != nil {
panic(err)
}
batch.checkFlush()
}
batch.flush()
}
// PutInt64 stores a new int64. Any existing value (even if of another type)
+5 -7
View File
@@ -104,18 +104,18 @@ func (db *schemaUpdater) updateSchema0to1() {
var gk, buf []byte
for dbi.Next() {
t.checkFlush()
folder, ok := db.keyer.FolderFromDeviceFileKey(dbi.Key())
if !ok {
// not having the folder in the index is bad; delete and continue
t.Delete(dbi.Key())
t.checkFlush()
continue
}
device, ok := db.keyer.DeviceFromDeviceFileKey(dbi.Key())
if !ok {
// not having the device in the index is bad; delete and continue
t.Delete(dbi.Key())
t.checkFlush()
continue
}
name := db.keyer.NameFromDeviceFileKey(dbi.Key())
@@ -128,7 +128,6 @@ func (db *schemaUpdater) updateSchema0to1() {
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
buf = t.removeFromGlobal(gk, buf, folder, device, nil, nil)
t.Delete(dbi.Key())
t.checkFlush()
continue
}
@@ -149,7 +148,6 @@ func (db *schemaUpdater) updateSchema0to1() {
panic("can't happen: " + err.Error())
}
t.Put(dbi.Key(), bs)
t.checkFlush()
symlinkConv++
}
@@ -210,7 +208,7 @@ func (db *schemaUpdater) updateSchema2to3() {
if !need(f, ok, v) {
return true
}
nk = t.db.keyer.GenerateNeedFileKey(nk, folder, []byte(f.FileName()))
nk = t.keyer.GenerateNeedFileKey(nk, folder, []byte(f.FileName()))
t.Put(nk, nil)
t.checkFlush()
return true
@@ -282,7 +280,7 @@ func (db *schemaUpdater) updateSchema6to7() {
svl, err := t.Get(gk, nil)
if err != nil {
// If there is no global list, we hardly need it.
t.Delete(t.db.keyer.GenerateNeedFileKey(nk, folder, name))
t.Delete(t.keyer.GenerateNeedFileKey(nk, folder, name))
return true
}
var fl VersionList
@@ -293,7 +291,7 @@ func (db *schemaUpdater) updateSchema6to7() {
return true
}
if localFV, haveLocalFV := fl.Get(protocol.LocalDeviceID[:]); !need(global, haveLocalFV, localFV.Version) {
t.Delete(t.db.keyer.GenerateNeedFileKey(nk, folder, name))
t.Delete(t.keyer.GenerateNeedFileKey(nk, folder, name))
}
return true
})
+14 -31
View File
@@ -12,13 +12,10 @@ import (
"github.com/syndtr/goleveldb/leveldb/util"
)
// Flush batches to disk when they contain this many records.
const batchFlushSize = 64
// A readOnlyTransaction represents a database snapshot.
type readOnlyTransaction struct {
*leveldb.Snapshot
db *instance
keyer keyer
}
func (db *instance) newReadOnlyTransaction() readOnlyTransaction {
@@ -28,7 +25,7 @@ func (db *instance) newReadOnlyTransaction() readOnlyTransaction {
}
return readOnlyTransaction{
Snapshot: snap,
db: db,
keyer: db.keyer,
}
}
@@ -37,7 +34,7 @@ func (t readOnlyTransaction) close() {
}
func (t readOnlyTransaction) getFile(folder, device, file []byte) (protocol.FileInfo, bool) {
return t.getFileByKey(t.db.keyer.GenerateDeviceFileKey(nil, folder, device, file))
return t.getFileByKey(t.keyer.GenerateDeviceFileKey(nil, folder, device, file))
}
func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool) {
@@ -65,7 +62,7 @@ func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (FileIntf, boo
}
func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate bool) ([]byte, FileIntf, bool) {
keyBuf = t.db.keyer.GenerateGlobalVersionKey(keyBuf, folder, file)
keyBuf = t.keyer.GenerateGlobalVersionKey(keyBuf, folder, file)
bs, err := t.Get(keyBuf, nil)
if err != nil {
@@ -77,7 +74,7 @@ func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate boo
return keyBuf, nil, false
}
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, vl.Versions[0].Device, file)
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, vl.Versions[0].Device, file)
if fi, ok := t.getFileTrunc(keyBuf, truncate); ok {
return keyBuf, fi, true
}
@@ -90,14 +87,13 @@ func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate boo
// batch size.
type readWriteTransaction struct {
readOnlyTransaction
*leveldb.Batch
*batch
}
func (db *instance) newReadWriteTransaction() readWriteTransaction {
t := db.newReadOnlyTransaction()
return readWriteTransaction{
readOnlyTransaction: t,
Batch: new(leveldb.Batch),
readOnlyTransaction: db.newReadOnlyTransaction(),
batch: db.newBatch(),
}
}
@@ -106,19 +102,6 @@ func (t readWriteTransaction) close() {
t.readOnlyTransaction.close()
}
func (t readWriteTransaction) checkFlush() {
if t.Batch.Len() > batchFlushSize {
t.flush()
t.Batch.Reset()
}
}
func (t readWriteTransaction) flush() {
if err := t.db.Write(t.Batch, nil); err != nil {
panic(err)
}
}
// updateGlobal adds this device+version to the version list for the given
// file. If the device is already present in the list, the version is updated.
// If the file does not have an entry in the global list, it is created.
@@ -142,7 +125,7 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
// Inserted a new newest version
global = file
} else {
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, name)
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, name)
if new, ok := t.getFileByKey(keyBuf); ok {
global = new
} else {
@@ -167,7 +150,7 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
// The previous newest version is now at index 1
oldGlobalFV = fl.Versions[1]
}
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, oldGlobalFV.Device, name)
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, oldGlobalFV.Device, name)
if oldFile, ok := t.getFileByKey(keyBuf); ok {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
@@ -187,7 +170,7 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
// device according to the version list and global FileInfo given and updates
// the db accordingly.
func (t readWriteTransaction) updateLocalNeed(keyBuf, folder, name []byte, fl VersionList, global protocol.FileInfo) []byte {
keyBuf = t.db.keyer.GenerateNeedFileKey(keyBuf, folder, name)
keyBuf = t.keyer.GenerateNeedFileKey(keyBuf, folder, name)
hasNeeded, _ := t.Has(keyBuf, nil)
if localFV, haveLocalFV := fl.Get(protocol.LocalDeviceID[:]); need(global, haveLocalFV, localFV.Version) {
if !hasNeeded {
@@ -246,21 +229,21 @@ func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device []byte
if removedAt == 0 {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, device, file)
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, device, file)
if f, ok := t.getFileByKey(keyBuf); ok {
meta.removeFile(protocol.GlobalDeviceID, f)
}
}
if len(fl.Versions) == 0 {
keyBuf = t.db.keyer.GenerateNeedFileKey(keyBuf, folder, file)
keyBuf = t.keyer.GenerateNeedFileKey(keyBuf, folder, file)
t.Delete(keyBuf)
t.Delete(gk)
return keyBuf
}
if removedAt == 0 {
keyBuf = t.db.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, file)
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, file)
global, ok := t.getFileByKey(keyBuf)
if !ok {
panic("This file must exist in the db")
+18 -18
View File
@@ -144,7 +144,7 @@ func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
if err != nil {
return nil, err
}
return fsFileInfo{fi}, err
return basicFileInfo{fi}, err
}
func (f *BasicFilesystem) Remove(name string) error {
@@ -184,7 +184,7 @@ func (f *BasicFilesystem) Stat(name string) (FileInfo, error) {
if err != nil {
return nil, err
}
return fsFileInfo{fi}, err
return basicFileInfo{fi}, err
}
func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
@@ -215,7 +215,7 @@ func (f *BasicFilesystem) Open(name string) (File, error) {
if err != nil {
return nil, err
}
return fsFile{fd, name}, err
return basicFile{fd, name}, err
}
func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File, error) {
@@ -227,7 +227,7 @@ func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File,
if err != nil {
return nil, err
}
return fsFile{fd, name}, err
return basicFile{fd, name}, err
}
func (f *BasicFilesystem) Create(name string) (File, error) {
@@ -239,7 +239,7 @@ func (f *BasicFilesystem) Create(name string) (File, error) {
if err != nil {
return nil, err
}
return fsFile{fd, name}, err
return basicFile{fd, name}, err
}
func (f *BasicFilesystem) Walk(root string, walkFn WalkFunc) error {
@@ -283,8 +283,8 @@ func (f *BasicFilesystem) URI() string {
func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
// Like os.SameFile, we always return false unless fi1 and fi2 were created
// by this package's Stat/Lstat method.
f1, ok1 := fi1.(fsFileInfo)
f2, ok2 := fi2.(fsFileInfo)
f1, ok1 := fi1.(basicFileInfo)
f2, ok2 := fi2.(basicFileInfo)
if !ok1 || !ok2 {
return false
}
@@ -292,36 +292,36 @@ func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
return os.SameFile(f1.FileInfo, f2.FileInfo)
}
// fsFile implements the fs.File interface on top of an os.File
type fsFile struct {
// basicFile implements the fs.File interface on top of an os.File
type basicFile struct {
*os.File
name string
}
func (f fsFile) Name() string {
func (f basicFile) Name() string {
return f.name
}
func (f fsFile) Stat() (FileInfo, error) {
func (f basicFile) Stat() (FileInfo, error) {
info, err := f.File.Stat()
if err != nil {
return nil, err
}
return fsFileInfo{info}, nil
return basicFileInfo{info}, nil
}
// fsFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
type fsFileInfo struct {
// basicFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
type basicFileInfo struct {
os.FileInfo
}
func (e fsFileInfo) IsSymlink() bool {
// Must use fsFileInfo.Mode() because it may apply magic.
func (e basicFileInfo) IsSymlink() bool {
// Must use basicFileInfo.Mode() because it may apply magic.
return e.Mode()&ModeSymlink != 0
}
func (e fsFileInfo) IsRegular() bool {
// Must use fsFileInfo.Mode() because it may apply magic.
func (e basicFileInfo) IsRegular() bool {
// Must use basicFileInfo.Mode() because it may apply magic.
return e.Mode()&ModeType == 0
}
@@ -1,4 +1,4 @@
// Copyright (C) 2019 The Syncthing Authors.
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -10,14 +10,18 @@ package fs
import "syscall"
func (e fsFileInfo) Owner() int {
func (e basicFileInfo) Mode() FileMode {
return FileMode(e.FileInfo.Mode())
}
func (e basicFileInfo) Owner() int {
if st, ok := e.Sys().(*syscall.Stat_t); ok {
return int(st.Uid)
}
return -1
}
func (e fsFileInfo) Group() int {
func (e basicFileInfo) Group() int {
if st, ok := e.Sys().(*syscall.Stat_t); ok {
return int(st.Gid)
}
@@ -30,7 +30,7 @@ func isWindowsExecutable(path string) bool {
return execExts[strings.ToLower(filepath.Ext(path))]
}
func (e fsFileInfo) Mode() FileMode {
func (e basicFileInfo) Mode() FileMode {
m := e.FileInfo.Mode()
if m&os.ModeSymlink != 0 && e.Size() > 0 {
// "Symlinks" with nonzero size are in fact "hard" links, such as
@@ -48,3 +48,11 @@ func (e fsFileInfo) Mode() FileMode {
m &^= 0022
return FileMode(m)
}
func (e basicFileInfo) Owner() int {
return -1
}
func (e basicFileInfo) Group() int {
return -1
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package fs
func (e fsFileInfo) Owner() int {
return -1
}
func (e fsFileInfo) Group() int {
return -1
}
-13
View File
@@ -1,13 +0,0 @@
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build !windows
package fs
func (e fsFileInfo) Mode() FileMode {
return FileMode(e.FileInfo.Mode())
}
-25
View File
@@ -25,7 +25,6 @@ const (
LevelVerbose
LevelInfo
LevelWarn
LevelFatal
NumLevels
)
@@ -49,8 +48,6 @@ type Logger interface {
Infof(format string, vals ...interface{})
Warnln(vals ...interface{})
Warnf(format string, vals ...interface{})
Fatalln(vals ...interface{})
Fatalf(format string, vals ...interface{})
ShouldDebug(facility string) bool
SetDebug(facility string, enabled bool)
Facilities() map[string]string
@@ -190,28 +187,6 @@ func (l *logger) Warnf(format string, vals ...interface{}) {
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{}) {
s := fmt.Sprintln(vals...)
l.mut.Lock()
defer l.mut.Unlock()
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{}) {
s := fmt.Sprintf(format, vals...)
l.mut.Lock()
defer l.mut.Unlock()
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}
// ShouldDebug returns true if the given facility has debugging enabled.
func (l *logger) ShouldDebug(facility string) bool {
l.mut.Lock()
+33 -32
View File
@@ -25,6 +25,7 @@ import (
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/watchaggregator"
)
@@ -37,10 +38,14 @@ var errWatchNotStarted = errors.New("not started")
type folder struct {
stateTracker
config.FolderConfiguration
*stats.FolderStatisticsReference
localFlags uint32
model *Model
model *model
shortID protocol.ShortID
fset *db.FileSet
ignores *ignore.Matcher
ctx context.Context
cancel context.CancelFunc
@@ -73,15 +78,18 @@ type puller interface {
pull() bool // true when successfull and should not be retried
}
func newFolder(model *Model, cfg config.FolderConfiguration) folder {
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration) folder {
ctx, cancel := context.WithCancel(context.Background())
return folder{
stateTracker: newStateTracker(cfg.ID),
FolderConfiguration: cfg,
stateTracker: newStateTracker(cfg.ID),
FolderConfiguration: cfg,
FolderStatisticsReference: stats.NewFolderStatisticsReference(model.db, cfg.ID),
model: model,
shortID: model.shortID,
fset: fset,
ignores: ignores,
ctx: ctx,
cancel: cancel,
@@ -285,11 +293,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
return err
}
f.model.fmut.RLock()
fset := f.model.folderFiles[f.ID]
ignores := f.model.folderIgnores[f.ID]
f.model.fmut.RUnlock()
mtimefs := fset.MtimeFS()
mtimefs := f.fset.MtimeFS()
f.setState(FolderScanWaiting)
scanLimiter.take(1)
@@ -311,16 +315,16 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// Check if the ignore patterns changed as part of scanning this folder.
// If they did we should schedule a pull of the folder so that we
// request things we might have suddenly become unignored and so on.
oldHash := ignores.Hash()
oldHash := f.ignores.Hash()
defer func() {
if ignores.Hash() != oldHash {
if f.ignores.Hash() != oldHash {
l.Debugln("Folder", f.Description(), "ignore patterns change detected while scanning; triggering puller")
f.ignoresUpdated()
f.SchedulePull()
}
}()
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
err = fmt.Errorf("loading ignores: %v", err)
f.setError(err)
return err
@@ -329,8 +333,8 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// Clean the list of subitems to ensure that we start at a known
// directory, and don't scan subdirectories of things we've already
// scanned.
subDirs = unifySubs(subDirs, func(f string) bool {
_, ok := fset.Get(protocol.LocalDeviceID, f)
subDirs = unifySubs(subDirs, func(file string) bool {
_, ok := f.fset.Get(protocol.LocalDeviceID, file)
return ok
})
@@ -339,14 +343,14 @@ func (f *folder) scanSubdirs(subDirs []string) error {
fchan := scanner.Walk(f.ctx, scanner.Config{
Folder: f.ID,
Subs: subDirs,
Matcher: ignores,
Matcher: f.ignores,
TempLifetime: time.Duration(f.model.cfg.Options().KeepTemporariesH) * time.Hour,
CurrentFiler: cFiler{f.model, f.ID},
Filesystem: mtimefs,
IgnorePerms: f.IgnorePerms,
AutoNormalize: f.AutoNormalize,
Hashers: f.model.numHashers(f.ID),
ShortID: f.model.shortID,
ShortID: f.shortID,
ProgressTickIntervalS: f.ScanProgressIntervalS,
UseLargeBlocks: f.UseLargeBlocks,
LocalFlags: f.localFlags,
@@ -365,7 +369,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
oldBatchFn := batchFn // can't reference batchFn directly (recursion)
batchFn = func(fs []protocol.FileInfo) error {
for i := range fs {
switch gf, ok := fset.GetGlobal(fs[i].Name); {
switch gf, ok := f.fset.GetGlobal(fs[i].Name); {
case !ok:
continue
case gf.IsEquivalentOptional(fs[i], false, false, protocol.FlagLocalReceiveOnly):
@@ -425,7 +429,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
for _, sub := range subDirs {
var iterError error
fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
f.fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
file := fi.(db.FileInfoTruncated)
if err := batch.flushIfFull(); err != nil {
@@ -436,7 +440,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
if ignoredParent != "" && !fs.IsParent(file.Name, ignoredParent) {
for _, file := range toIgnore {
l.Debugln("marking file as ignored", file)
nf := file.ConvertToIgnoredFileInfo(f.model.id.Short())
nf := file.ConvertToIgnoredFileInfo(f.shortID)
batch.append(nf)
changes++
if err := batch.flushIfFull(); err != nil {
@@ -448,7 +452,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
ignoredParent = ""
}
switch ignored := ignores.Match(file.Name).IsIgnored(); {
switch ignored := f.ignores.Match(file.Name).IsIgnored(); {
case !file.IsIgnored() && ignored:
// File was not ignored at last pass but has been ignored.
if file.IsDirectory() {
@@ -463,7 +467,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
}
l.Debugln("marking file as ignored", f)
nf := file.ConvertToIgnoredFileInfo(f.model.id.Short())
nf := file.ConvertToIgnoredFileInfo(f.shortID)
batch.append(nf)
changes++
@@ -490,9 +494,9 @@ func (f *folder) scanSubdirs(subDirs []string) error {
Size: 0,
ModifiedS: file.ModifiedS,
ModifiedNs: file.ModifiedNs,
ModifiedBy: f.model.id.Short(),
ModifiedBy: f.shortID,
Deleted: true,
Version: file.Version.Update(f.model.shortID),
Version: file.Version.Update(f.shortID),
LocalFlags: f.localFlags,
}
// We do not want to override the global version
@@ -501,7 +505,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// other existing versions, which will be resolved
// by the normal pulling mechanisms.
if file.ShouldConflict() {
nf.Version = nf.Version.DropOthers(f.model.shortID)
nf.Version = nf.Version.DropOthers(f.shortID)
}
batch.append(nf)
@@ -513,7 +517,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
if iterError == nil && len(toIgnore) > 0 {
for _, file := range toIgnore {
l.Debugln("marking file as ignored", f)
nf := file.ConvertToIgnoredFileInfo(f.model.id.Short())
nf := file.ConvertToIgnoredFileInfo(f.shortID)
batch.append(nf)
changes++
if iterError = batch.flushIfFull(); iterError != nil {
@@ -532,7 +536,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
return err
}
f.model.folderStatRef(f.ID).ScanCompleted()
f.ScanCompleted()
f.setState(FolderIdle)
return nil
}
@@ -603,24 +607,21 @@ func (f *folder) restartWatch() {
// this asynchronously, you should probably use scheduleWatchRestart instead.
func (f *folder) startWatch() {
ctx, cancel := context.WithCancel(f.ctx)
f.model.fmut.RLock()
ignores := f.model.folderIgnores[f.folderID]
f.model.fmut.RUnlock()
f.watchMut.Lock()
f.watchChan = make(chan []string)
f.watchCancel = cancel
f.watchMut.Unlock()
go f.startWatchAsync(ctx, ignores)
go f.startWatchAsync(ctx)
}
// startWatchAsync tries to start the filesystem watching and retries every minute on failure.
// It is a convenience function that should not be used except in startWatch.
func (f *folder) startWatchAsync(ctx context.Context, ignores *ignore.Matcher) {
func (f *folder) startWatchAsync(ctx context.Context) {
timer := time.NewTimer(0)
for {
select {
case <-timer.C:
eventChan, err := f.Filesystem().Watch(".", ignores, ctx, f.IgnorePerms)
eventChan, err := f.Filesystem().Watch(".", f.ignores, ctx, f.IgnorePerms)
f.watchMut.Lock()
prevErr := f.watchErr
f.watchErr = err
+8 -13
View File
@@ -56,8 +56,8 @@ type receiveOnlyFolder struct {
*sendReceiveFolder
}
func newReceiveOnlyFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
sr := newSendReceiveFolder(model, cfg, ver, fs).(*sendReceiveFolder)
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs).(*sendReceiveFolder)
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
return &receiveOnlyFolder{sr}
}
@@ -66,18 +66,13 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File
f.setState(FolderScanning)
defer f.setState(FolderIdle)
// XXX: This *really* should be given to us in the constructor...
f.model.fmut.RLock()
ignores := f.model.folderIgnores[f.folderID]
f.model.fmut.RUnlock()
scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
defer close(scanChan)
delQueue := &deleteQueue{
handler: f, // for the deleteFile and deleteDir methods
ignores: ignores,
handler: f, // for the deleteItemOnDisk and deleteDirOnDisk methods
ignores: f.ignores,
scanChan: scanChan,
}
@@ -171,8 +166,8 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File
// directories for last.
type deleteQueue struct {
handler interface {
deleteFile(file protocol.FileInfo, scanChan chan<- string) (dbUpdateJob, error)
deleteDir(dir string, ignores *ignore.Matcher, scanChan chan<- string) error
deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) error
deleteDirOnDisk(dir string, scanChan chan<- string) error
}
ignores *ignore.Matcher
dirs []string
@@ -193,7 +188,7 @@ func (q *deleteQueue) handle(fi protocol.FileInfo) (bool, error) {
}
// Kill it.
_, err := q.handler.deleteFile(fi, q.scanChan)
err := q.handler.deleteItemOnDisk(fi, q.scanChan)
return true, err
}
@@ -205,7 +200,7 @@ func (q *deleteQueue) flush() ([]string, error) {
var deleted []string
for _, dir := range q.dirs {
if err := q.handler.deleteDir(dir, q.ignores, q.scanChan); err == nil {
if err := q.handler.deleteDirOnDisk(dir, q.scanChan); err == nil {
deleted = append(deleted, dir)
} else if err != nil && firstError == nil {
firstError = err
+11 -29
View File
@@ -35,15 +35,9 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
testOs.MkdirAll("_recvonly/.stfolder", 0755)
testOs.MkdirAll("_recvonly/ignDir", 0755)
testOs.MkdirAll("_recvonly/unknownDir", 0755)
if err := ioutil.WriteFile("_recvonly/ignDir/ignFile", []byte("hello\n"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("_recvonly/unknownDir/unknownFile", []byte("hello\n"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("_recvonly/.stignore", []byte("ignDir\n"), 0644); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile("_recvonly/ignDir/ignFile", []byte("hello\n"), 0644))
must(t, ioutil.WriteFile("_recvonly/unknownDir/unknownFile", []byte("hello\n"), 0644))
must(t, ioutil.WriteFile("_recvonly/.stignore", []byte("ignDir\n"), 0644))
knownFiles := setupKnownFiles(t, []byte("hello\n"))
@@ -166,15 +160,11 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Update the file.
newData := []byte("totally different data\n")
if err := ioutil.WriteFile("_recvonly/knownDir/knownFile", newData, 0644); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile("_recvonly/knownDir/knownFile", newData, 0644))
// Rescan.
if err := m.ScanFolder("ro"); err != nil {
t.Fatal(err)
}
must(t, m.ScanFolder("ro"))
// We now have a newer file than the rest of the cluster. Global state should reflect this.
@@ -268,13 +258,9 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Create a file and modify another
file := "_recvonly/foo"
if err := ioutil.WriteFile(file, []byte("hello\n"), 0644); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile(file, []byte("hello\n"), 0644))
if err := ioutil.WriteFile("_recvonly/knownDir/knownFile", []byte("bye\n"), 0644); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile("_recvonly/knownDir/knownFile", []byte("bye\n"), 0644))
m.ScanFolder("ro")
@@ -286,9 +272,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Remove the file again and undo the modification
testOs.Remove(file)
if err := ioutil.WriteFile("_recvonly/knownDir/knownFile", oldData, 0644); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile("_recvonly/knownDir/knownFile", oldData, 0644))
folderFs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime())
m.ScanFolder("ro")
@@ -303,9 +287,7 @@ func setupKnownFiles(t *testing.T, data []byte) []protocol.FileInfo {
testOs := &fatalOs{t}
testOs.MkdirAll("_recvonly/knownDir", 0755)
if err := ioutil.WriteFile("_recvonly/knownDir/knownFile", data, 0644); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile("_recvonly/knownDir/knownFile", data, 0644))
t0 := time.Now().Add(-1 * time.Minute)
testOs.Chtimes("_recvonly/knownDir/knownFile", t0, t0)
@@ -336,7 +318,7 @@ func setupKnownFiles(t *testing.T, data []byte) []protocol.FileInfo {
return knownFiles
}
func setupROFolder() *Model {
func setupROFolder() *model {
fcfg := config.NewFolderConfiguration(myID, "ro", "receive only test", fs.FilesystemTypeBasic, "_recvonly")
fcfg.Type = config.FolderTypeReceiveOnly
fcfg.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
@@ -349,7 +331,7 @@ func setupROFolder() *Model {
wrp := createTmpWrapper(cfg)
db := db.OpenMemory()
m := NewModel(wrp, myID, "syncthing", "dev", db, nil)
m := newModel(wrp, myID, "syncthing", "dev", db, nil)
m.ServeBackground()
m.AddFolder(fcfg)
+6 -10
View File
@@ -10,6 +10,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/versioner"
)
@@ -22,9 +23,9 @@ type sendOnlyFolder struct {
folder
}
func newSendOnlyFolder(model *Model, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service {
func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service {
f := &sendOnlyFolder{
folder: newFolder(model, cfg),
folder: newFolder(model, fset, ignores, cfg),
}
f.folder.puller = f
return f
@@ -43,22 +44,17 @@ func (f *sendOnlyFolder) pull() bool {
return false
}
f.model.fmut.RLock()
folderFiles := f.model.folderFiles[f.folderID]
ignores := f.model.folderIgnores[f.folderID]
f.model.fmut.RUnlock()
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
f.fset.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
f.model.updateLocalsFromPulling(f.folderID, batch)
batch = batch[:0]
batchSizeBytes = 0
}
if ignores.ShouldIgnore(intf.FileName()) {
if f.ignores.ShouldIgnore(intf.FileName()) {
file := intf.(protocol.FileInfo)
file.SetIgnored(f.shortID)
batch = append(batch, file)
@@ -67,7 +63,7 @@ func (f *sendOnlyFolder) pull() bool {
return true
}
curFile, ok := f.model.CurrentFolderFile(f.folderID, intf.FileName())
curFile, ok := f.fset.Get(protocol.LocalDeviceID, intf.FileName())
if !ok {
if intf.IsDeleted() {
panic("Should never get a deleted file as needed when we don't have it")
+289 -239
View File
@@ -60,13 +60,16 @@ type copyBlocksState struct {
const retainBits = fs.ModeSetgid | fs.ModeSetuid | fs.ModeSticky
var (
activity = newDeviceActivity()
errNoDevice = errors.New("peers who had this file went away, or the file has changed while syncing. will retry later")
errDirHasToBeScanned = errors.New("directory contains unexpected files, scheduling scan")
errDirHasIgnored = errors.New("directory contains ignored files (see ignore documentation for (?d) prefix)")
errDirNotEmpty = errors.New("directory is not empty; files within are probably ignored on connected devices only")
errNotAvailable = errors.New("no connected device has the required version of this file")
errModified = errors.New("file modified but not rescanned; will try again later")
activity = newDeviceActivity()
errNoDevice = errors.New("peers who had this file went away, or the file has changed while syncing. will retry later")
errDirHasToBeScanned = errors.New("directory contains unexpected files, scheduling scan")
errDirHasIgnored = errors.New("directory contains ignored files (see ignore documentation for (?d) prefix)")
errDirNotEmpty = errors.New("directory is not empty; files within are probably ignored on connected devices only")
errNotAvailable = errors.New("no connected device has the required version of this file")
errModified = errors.New("file modified but not rescanned; will try again later")
errUnexpectedDirOnFileDel = errors.New("encountered directory when trying to remove file/symlink")
errIncompatibleSymlink = errors.New("incompatible symlink entry; rescan with newer Syncthing on source")
contextRemovingOldItem = "removing item to be replaced"
)
const (
@@ -104,9 +107,9 @@ type sendReceiveFolder struct {
pullErrorsMut sync.Mutex
}
func newSendReceiveFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
f := &sendReceiveFolder{
folder: newFolder(model, cfg),
folder: newFolder(model, fset, ignores, cfg),
fs: fs,
versioner: ver,
queue: newJobQueue(),
@@ -141,14 +144,9 @@ func (f *sendReceiveFolder) pull() bool {
return true
}
f.model.fmut.RLock()
ignores := f.model.folderIgnores[f.folderID]
folderFiles := f.model.folderFiles[f.folderID]
f.model.fmut.RUnlock()
// If there is nothing to do, don't even enter pulling state.
abort := true
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
f.fset.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
abort = false
return false
})
@@ -162,13 +160,13 @@ func (f *sendReceiveFolder) pull() bool {
}
// Check if the ignore patterns changed.
oldHash := ignores.Hash()
oldHash := f.ignores.Hash()
defer func() {
if ignores.Hash() != oldHash {
if f.ignores.Hash() != oldHash {
f.ignoresUpdated()
}
}()
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
err = fmt.Errorf("loading ignores: %v", err)
f.setError(err)
return false
@@ -194,9 +192,9 @@ func (f *sendReceiveFolder) pull() bool {
default:
}
changed := f.pullerIteration(ignores, folderFiles, scanChan)
changed := f.pullerIteration(scanChan)
l.Debugln(f, "changed", changed, "on try", tries)
l.Debugln(f, "changed", changed, "on try", tries+1)
if changed == 0 {
// No files were changed by the puller, so we are in
@@ -224,7 +222,7 @@ func (f *sendReceiveFolder) pull() bool {
// returns the number items that should have been synced (even those that
// might have failed). One puller iteration handles all files currently
// flagged as needed in the folder.
func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles *db.FileSet, scanChan chan<- string) int {
func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int {
pullChan := make(chan pullBlockState)
copyChan := make(chan copyBlocksState)
finisherChan := make(chan *sharedPullerState)
@@ -263,11 +261,11 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles
doneWg.Add(1)
// finisherRoutine finishes when finisherChan is closed
go func() {
f.finisherRoutine(ignores, finisherChan, dbUpdateChan, scanChan)
f.finisherRoutine(finisherChan, dbUpdateChan, scanChan)
doneWg.Done()
}()
changed, fileDeletions, dirDeletions, err := f.processNeeded(ignores, folderFiles, dbUpdateChan, copyChan, finisherChan, scanChan)
changed, fileDeletions, dirDeletions, err := f.processNeeded(dbUpdateChan, copyChan, finisherChan, scanChan)
// Signal copy and puller routines that we are done with the in data for
// this iteration. Wait for them to finish.
@@ -282,7 +280,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles
doneWg.Wait()
if err == nil {
f.processDeletions(ignores, fileDeletions, dirDeletions, dbUpdateChan, scanChan)
f.processDeletions(fileDeletions, dirDeletions, dbUpdateChan, scanChan)
}
// Wait for db updates and scan scheduling to complete
@@ -292,7 +290,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles
return changed
}
func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *db.FileSet, dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) {
func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) {
changed := 0
var processDirectly []protocol.FileInfo
var dirDeletions []protocol.FileInfo
@@ -303,7 +301,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *
// Regular files to pull goes into the file queue, everything else
// (directories, symlinks and deletes) goes into the "process directly"
// pile.
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
f.fset.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
select {
case <-f.ctx.Done():
return false
@@ -318,14 +316,14 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *
file := intf.(protocol.FileInfo)
switch {
case ignores.ShouldIgnore(file.Name):
case f.ignores.ShouldIgnore(file.Name):
file.SetIgnored(f.shortID)
l.Debugln(f, "Handling ignored file", file)
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
changed++
case runtime.GOOS == "windows" && fs.WindowsInvalidFilename(file.Name):
f.newPullError("pull", file.Name, fs.ErrInvalidFilename)
f.newPullError(file.Name, fs.ErrInvalidFilename)
case file.IsDeleted():
if file.IsDirectory() {
@@ -334,7 +332,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *
dirDeletions = append(dirDeletions, file)
} else {
fileDeletions[file.Name] = file
df, ok := f.model.CurrentFolderFile(f.folderID, file.Name)
df, ok := f.fset.Get(protocol.LocalDeviceID, file.Name)
// Local file can be already deleted, but with a lower version
// number, hence the deletion coming in again as part of
// WithNeed, furthermore, the file can simply be of the wrong
@@ -394,12 +392,11 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *
switch {
case fi.IsDirectory() && !fi.IsSymlink():
l.Debugln(f, "Handling directory", fi.Name)
f.handleDir(fi, dbUpdateChan)
f.handleDir(fi, dbUpdateChan, scanChan)
case fi.IsSymlink():
l.Debugln("Handling symlink", fi.Name)
l.Debugln(f, "Handling symlink", fi.Name)
f.handleSymlink(fi, dbUpdateChan)
f.handleSymlink(fi, dbUpdateChan, scanChan)
default:
l.Warnln(fi)
@@ -439,7 +436,7 @@ nextFile:
break
}
fi, ok := f.model.CurrentGlobalFile(f.folderID, fileName)
fi, ok := f.fset.GetGlobal(fileName)
if !ok {
// File is no longer in the index. Mark it as done and drop it.
f.queue.Done(fileName)
@@ -472,7 +469,7 @@ nextFile:
// desired state with the delete bit set is in the deletion
// map.
desired := fileDeletions[candidate.Name]
if err := f.renameFile(candidate, desired, fi, ignores, dbUpdateChan, scanChan); err != nil {
if err := f.renameFile(candidate, desired, fi, dbUpdateChan, scanChan); err != nil {
// Failed to rename, try to handle files as separate
// deletions and updates.
break
@@ -486,7 +483,7 @@ nextFile:
}
}
devices := folderFiles.Availability(fileName)
devices := f.fset.Availability(fileName)
for _, dev := range devices {
if _, ok := f.model.Connection(dev); ok {
changed++
@@ -495,13 +492,13 @@ nextFile:
continue nextFile
}
}
f.newPullError("pull", fileName, errNotAvailable)
f.newPullError(fileName, errNotAvailable)
}
return changed, fileDeletions, dirDeletions, nil
}
func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
for _, file := range fileDeletions {
select {
case <-f.ctx.Done():
@@ -511,7 +508,7 @@ func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeleti
l.Debugln(f, "Deleting file", file.Name)
if update, err := f.deleteFile(file, scanChan); err != nil {
f.newPullError("delete file", file.Name, err)
f.newPullError(file.Name, errors.Wrap(err, "delete file"))
} else {
dbUpdateChan <- update
}
@@ -526,12 +523,12 @@ func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeleti
dir := dirDeletions[len(dirDeletions)-i-1]
l.Debugln(f, "Deleting dir", dir.Name)
f.handleDeleteDir(dir, ignores, dbUpdateChan, scanChan)
f.deleteDir(dir, dbUpdateChan, scanChan)
}
}
// handleDir creates or updates the given directory
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -559,19 +556,45 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
}
if shouldDebug() {
curFile, _ := f.model.CurrentFolderFile(f.folderID, file.Name)
curFile, _ := f.fset.Get(protocol.LocalDeviceID, file.Name)
l.Debugf("need dir\n\t%v\n\t%v", file, curFile)
}
info, err := f.fs.Lstat(file.Name)
switch {
// There is already something under that name, but it's a file/link.
// Most likely a file/link is getting replaced with a directory.
// Remove the file/link and fall through to directory creation.
case err == nil && (!info.IsDir() || info.IsSymlink()):
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
// There is already something under that name, we need to handle that.
// Unless it already is a directory, as we only track permissions,
// that don't result in a conflict.
case err == nil && !info.IsDir():
// Check that it is what we have in the database.
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
if changed, err := f.itemChanged(info, curFile, hasCurFile, scanChan); err != nil {
f.newPullError(file.Name, err)
return
} else if changed {
l.Debugln("item changed on disk compared to db; not replacing with dir:", file.Name)
scanChan <- curFile.Name
f.newPullError(file.Name, errModified)
return
}
// Remove it to replace with the dir.
if !curFile.IsSymlink() && f.inConflict(curFile.Version, file.Version) {
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
// archiving. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
// Symlinks aren't checked for conflicts.
file.Version = file.Version.Merge(curFile.Version)
err = osutil.InWritableDir(func(name string) error {
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
}, f.fs, curFile.Name)
} else {
err = f.deleteItemOnDisk(file, scanChan)
}
if err != nil {
f.newPullError("dir replace", file.Name, err)
f.newPullError(file.Name, err)
return
}
fallthrough
@@ -606,26 +629,26 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
if err = osutil.InWritableDir(mkdir, f.fs, file.Name); err == nil {
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else {
f.newPullError("dir mkdir", file.Name, err)
f.newPullError(file.Name, errors.Wrap(err, "creating directory"))
}
return
// Weird error when stat()'ing the dir. Probably won't work to do
// anything else with it if we can't even stat() it.
case err != nil:
f.newPullError("dir stat", file.Name, err)
f.newPullError(file.Name, errors.Wrap(err, "checking file to be replaced"))
return
}
// The directory already exists, so we just correct the mode bits. (We
// don't handle modification times on directories, because that sucks...)
// It's OK to change mode bits on stuff within non-writable directories.
if f.IgnorePerms || file.NoPermissions {
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err == nil {
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else {
f.newPullError("dir chmod", file.Name, err)
if !f.IgnorePerms && !file.NoPermissions {
if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err != nil {
f.newPullError(file.Name, err)
return
}
}
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
}
// checkParent verifies that the thing we are handling lives inside a directory,
@@ -634,7 +657,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
parent := filepath.Dir(file)
if err := osutil.TraversesSymlink(f.fs, parent); err != nil {
f.newPullError("traverses q", file, err)
f.newPullError(file, errors.Wrap(err, "checking parent dirs"))
return false
}
@@ -655,7 +678,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
}
l.Debugf("%v resurrecting parent directory of %v", f, file)
if err := f.fs.MkdirAll(parent, 0755); err != nil {
f.newPullError("resurrecting parent dir", file, err)
f.newPullError(file, errors.Wrap(err, "resurrecting parent dir"))
return false
}
scanChan <- parent
@@ -663,7 +686,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
}
// handleSymlink creates or updates the given symlink
func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -686,25 +709,48 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
}()
if shouldDebug() {
curFile, _ := f.model.CurrentFolderFile(f.folderID, file.Name)
curFile, _ := f.fset.Get(protocol.LocalDeviceID, file.Name)
l.Debugf("need symlink\n\t%v\n\t%v", file, curFile)
}
if len(file.SymlinkTarget) == 0 {
// Index entry from a Syncthing predating the support for including
// the link target in the index entry. We log this as an error.
err = errors.New("incompatible symlink entry; rescan with newer Syncthing on source")
f.newPullError("symlink", file.Name, err)
f.newPullError(file.Name, errIncompatibleSymlink)
return
}
if _, err = f.fs.Lstat(file.Name); err == nil {
// There is already something under that name. Remove it to replace
// with the symlink. This also handles the "change symlink type"
// path.
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
// There is already something under that name, we need to handle that.
if info, err := f.fs.Lstat(file.Name); err == nil {
// Check that it is what we have in the database.
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
if changed, err := f.itemChanged(info, curFile, hasCurFile, scanChan); err != nil {
f.newPullError(file.Name, err)
return
} else if changed {
l.Debugln("item changed on disk compared to db; not replacing with symlink:", file.Name)
scanChan <- curFile.Name
f.newPullError(file.Name, errModified)
return
}
// Remove it to replace with the symlink. This also handles the
// "change symlink type" path.
if !curFile.IsDirectory() && !curFile.IsSymlink() && f.inConflict(curFile.Version, file.Version) {
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
// archiving. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
// Directories and symlinks aren't checked for conflicts.
file.Version = file.Version.Merge(curFile.Version)
err = osutil.InWritableDir(func(name string) error {
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
}, f.fs, curFile.Name)
} else {
err = f.deleteItemOnDisk(file, scanChan)
}
if err != nil {
f.newPullError("symlink remove", file.Name, err)
f.newPullError(file.Name, errors.Wrap(err, "symlink remove"))
return
}
}
@@ -721,12 +767,12 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
if err = osutil.InWritableDir(createLink, f.fs, file.Name); err == nil {
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleSymlink}
} else {
f.newPullError("symlink create", file.Name, err)
f.newPullError(file.Name, errors.Wrap(err, "symlink create"))
}
}
// handleDeleteDir attempts to remove a directory that was deleted on a remote
func (f *sendReceiveFolder) handleDeleteDir(file protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
// deleteDir attempts to remove a directory that was deleted on a remote
func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -748,8 +794,8 @@ func (f *sendReceiveFolder) handleDeleteDir(file protocol.FileInfo, ignores *ign
})
}()
if err = f.deleteDir(file.Name, ignores, scanChan); err != nil {
f.newPullError("delete dir", file.Name, err)
if err = f.deleteDirOnDisk(file.Name, scanChan); err != nil {
f.newPullError(file.Name, errors.Wrap(err, "delete dir"))
return
}
@@ -779,7 +825,7 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, scanChan chan<- s
})
}()
cur, ok := f.model.CurrentFolderFile(f.folderID, file.Name)
cur, ok := f.fset.Get(protocol.LocalDeviceID, file.Name)
if !ok {
// We should never try to pull a deletion for a file we don't have in the DB.
l.Debugln(f, "not deleting file we don't have", file.Name)
@@ -789,15 +835,21 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, scanChan chan<- s
return dbUpdateJob{}, err
}
// We are asked to delete a file, but what we have on disk and in db
// is a directory. Something is wrong here, should probably not happen.
if cur.IsDirectory() {
return dbUpdateJob{}, errUnexpectedDirOnFileDel
}
if f.inConflict(cur.Version, file.Version) {
// There is a conflict here. Move the file to a conflict copy instead
// of deleting. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
file.Version = file.Version.Merge(cur.Version)
err = osutil.InWritableDir(func(name string) error {
return f.moveForConflict(name, file.ModifiedBy.String())
}, f.fs, file.Name)
} else if f.versioner != nil && !cur.IsSymlink() {
// There is a conflict here, which shouldn't happen as deletions
// always lose. Merge the version vector of the file we have
// locally and commit it to db to resolve the conflict.
cur.Version = cur.Version.Merge(file.Version)
return dbUpdateJob{cur, dbUpdateHandleFile}, nil
}
if f.versioner != nil && !cur.IsSymlink() {
err = osutil.InWritableDir(f.versioner.Archive, f.fs, file.Name)
} else {
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
@@ -822,7 +874,7 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, scanChan chan<- s
// renameFile attempts to rename an existing file to a destination
// and set the right attributes on it.
func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -864,7 +916,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ig
return err
}
// Check that the target corresponds to what we have in the DB
curTarget, ok := f.model.CurrentFolderFile(f.folderID, target.Name)
curTarget, ok := f.fset.Get(protocol.LocalDeviceID, target.Name)
switch stat, serr := f.fs.Lstat(target.Name); {
case serr != nil && fs.IsNotExist(serr):
if !ok || curTarget.IsDeleted() {
@@ -920,7 +972,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ig
// of the source and the creation of the target temp file. Fix-up the metadata,
// update the local index of the target file and rename from temp to real name.
if err = f.performFinish(ignores, target, curTarget, true, tempName, dbUpdateChan, scanChan); err != nil {
if err = f.performFinish(target, curTarget, true, tempName, dbUpdateChan, scanChan); err != nil {
return err
}
@@ -966,7 +1018,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ig
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, dbUpdateChan chan<- dbUpdateJob) {
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
curFile, hasCurFile := f.fset.Get(protocol.LocalDeviceID, file.Name)
have, need := blockDiff(curFile.Blocks, file.Blocks)
@@ -1025,7 +1077,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
}
if err := f.CheckAvailableSpace(blocksSize); err != nil {
f.newPullError("pulling file", file.Name, err)
f.newPullError(file.Name, err)
f.queue.Done(file.Name)
return
}
@@ -1137,7 +1189,7 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
if !f.IgnorePerms && !file.NoPermissions {
if err = f.fs.Chmod(file.Name, fs.FileMode(file.Permissions&0777)); err != nil {
f.newPullError("shortcut", file.Name, err)
f.newPullError(file.Name, err)
return
}
}
@@ -1173,12 +1225,10 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
folderFilesystems := make(map[string]fs.Filesystem)
var folders []string
f.model.fmut.RLock()
for folder, cfg := range f.model.folderCfgs {
for folder, cfg := range f.model.cfg.Folders() {
folderFilesystems[folder] = cfg.Filesystem()
folders = append(folders, folder)
}
f.model.fmut.RUnlock()
var file fs.File
var weakHashFinder *weakhash.Finder
@@ -1215,7 +1265,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
for _, block := range state.blocks {
select {
case <-f.ctx.Done():
state.fail("folder stopped", f.ctx.Err())
state.fail(errors.Wrap(f.ctx.Err(), "folder stopped"))
break blocks
default:
}
@@ -1242,7 +1292,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
_, err = dstFd.WriteAt(buf, block.Offset)
if err != nil {
state.fail("dst write", err)
state.fail(errors.Wrap(err, "dst write"))
}
if offset == block.Offset {
@@ -1278,7 +1328,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
_, err = dstFd.WriteAt(buf, block.Offset)
if err != nil {
state.fail("dst write", err)
state.fail(errors.Wrap(err, "dst write"))
}
if path == state.file.Name {
state.copiedFromOrigin()
@@ -1383,7 +1433,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
for {
select {
case <-f.ctx.Done():
state.fail("folder stopped", f.ctx.Err())
state.fail(errors.Wrap(f.ctx.Err(), "folder stopped"))
return
default:
}
@@ -1394,9 +1444,9 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
selected, found := activity.leastBusy(candidates)
if !found {
if lastError != nil {
state.fail("pull", lastError)
state.fail(errors.Wrap(lastError, "pull"))
} else {
state.fail("pull", errNoDevice)
state.fail(errors.Wrap(errNoDevice, "pull"))
}
break
}
@@ -1425,7 +1475,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
// Save the block data we got from the cluster
_, err = fd.WriteAt(buf, state.block.Offset)
if err != nil {
state.fail("save", err)
state.fail(errors.Wrap(err, "save"))
} else {
state.pullDone(state.block)
}
@@ -1434,7 +1484,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
out <- state.sharedPullerState
}
func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile protocol.FileInfo, hasCurFile bool, tempName string, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCurFile bool, tempName string, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
// Set the correct permission bits on the new file
if !f.IgnorePerms && !file.NoPermissions {
if err := f.fs.Chmod(tempName, fs.FileMode(file.Permissions&0777)); err != nil {
@@ -1451,81 +1501,30 @@ func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile
// There is an old file or directory already in place. We need to
// handle that.
curMode := uint32(stat.Mode())
// Check that the file on disk is what we expect it to be according
// to the database. If there's a mismatch here, there might be local
// changes that we don't know about yet and we should scan before
// touching the file. There is also a case where we think the file
// should be there, but it was removed, which is a conflict, yet
// creations always wins when competing with a deletion, so no need
// to handle that specially.
changed := false
switch {
case !hasCurFile || curFile.Deleted:
// The file appeared from nowhere
l.Debugln("file exists on disk but not in db; not finishing:", file.Name)
changed = true
case stat.IsDir() != curFile.IsDirectory() || stat.IsSymlink() != curFile.IsSymlink():
// The file changed type. IsRegular is implicitly tested in the condition above
l.Debugln("file type changed but not rescanned; not finishing:", curFile.Name)
changed = true
case stat.IsRegular():
if !stat.ModTime().Equal(curFile.ModTime()) || stat.Size() != curFile.Size {
l.Debugln("file modified but not rescanned; not finishing:", curFile.Name)
changed = true
break
}
// check permissions
fallthrough
case stat.IsDir():
// Dirs only have perm, no modetime/size
if !f.IgnorePerms && !curFile.NoPermissions && curFile.HasPermissionBits() && !protocol.PermsEqual(curFile.Permissions, curMode) {
l.Debugln("file permission modified but not rescanned; not finishing:", curFile.Name)
changed = true
}
}
if changed {
if changed, err := f.itemChanged(stat, curFile, hasCurFile, scanChan); err != nil {
return err
} else if changed {
l.Debugln("file changed on disk compared to db; not finishing:", file.Name)
scanChan <- curFile.Name
return errModified
}
switch {
case stat.IsDir() || stat.IsSymlink():
// It's a directory or a symlink. These are not versioned or
// archived for conflicts, only removed (which of course fails for
// non-empty directories).
if err = f.deleteDir(file.Name, ignores, scanChan); err != nil {
return err
}
case f.inConflict(curFile.Version, file.Version):
if !curFile.IsDirectory() && !curFile.IsSymlink() && f.inConflict(curFile.Version, file.Version) {
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
// archiving. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
// Directories and symlinks aren't checked for conflicts.
file.Version = file.Version.Merge(curFile.Version)
err = osutil.InWritableDir(func(name string) error {
return f.moveForConflict(name, file.ModifiedBy.String())
}, f.fs, file.Name)
if err != nil {
return err
}
case f.versioner != nil && !file.IsSymlink():
// If we should use versioning, let the versioner archive the old
// file before we replace it. Archiving a non-existent file is not
// an error.
if err = osutil.InWritableDir(f.versioner.Archive, f.fs, file.Name); err != nil {
return err
}
return f.moveForConflict(name, file.ModifiedBy.String(), scanChan)
}, f.fs, curFile.Name)
} else {
err = f.deleteItemOnDisk(curFile, scanChan)
}
if err != nil {
return err
}
}
@@ -1543,7 +1542,7 @@ func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile
return nil
}
func (f *sendReceiveFolder) finisherRoutine(ignores *ignore.Matcher, in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
for state := range in {
if closed, err := state.finalClose(); closed {
l.Debugln(f, "closing", state.file.Name)
@@ -1551,11 +1550,11 @@ func (f *sendReceiveFolder) finisherRoutine(ignores *ignore.Matcher, in <-chan *
f.queue.Done(state.file.Name)
if err == nil {
err = f.performFinish(ignores, state.file, state.curFile, state.hasCurFile, state.tempName, dbUpdateChan, scanChan)
err = f.performFinish(state.file, state.curFile, state.hasCurFile, state.tempName, dbUpdateChan, scanChan)
}
if err != nil {
f.newPullError("finisher", state.file.Name, err)
f.newPullError(state.file.Name, err)
} else {
blockStatsMut.Lock()
blockStats["total"] += state.reused + state.copyTotal + state.pullTotal
@@ -1598,42 +1597,15 @@ func (f *sendReceiveFolder) Jobs() ([]string, []string) {
func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
const maxBatchTime = 2 * time.Second
batch := make([]dbUpdateJob, 0, maxBatchSizeFiles)
files := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batch := newFileInfoBatch(nil)
tick := time.NewTicker(maxBatchTime)
defer tick.Stop()
changedDirs := make(map[string]struct{})
found := false
var lastFile protocol.FileInfo
handleBatch := func() {
found := false
var lastFile protocol.FileInfo
for _, job := range batch {
files = append(files, job.file)
switch job.jobType {
case dbUpdateHandleFile, dbUpdateShortcutFile:
changedDirs[filepath.Dir(job.file.Name)] = struct{}{}
case dbUpdateHandleDir:
changedDirs[job.file.Name] = struct{}{}
case dbUpdateHandleSymlink, dbUpdateInvalidate:
// fsyncing symlinks is only supported by MacOS
// and invalidated files are db only changes -> no sync
}
if job.file.IsInvalid() || (job.file.IsDirectory() && !job.file.IsSymlink()) {
continue
}
if job.jobType&(dbUpdateHandleFile|dbUpdateDeleteFile) == 0 {
continue
}
found = true
lastFile = job.file
}
batch.flushFn = func(files []protocol.FileInfo) error {
// sync directories
for dir := range changedDirs {
delete(changedDirs, dir)
@@ -1653,14 +1625,13 @@ func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
f.model.updateLocalsFromPulling(f.folderID, files)
if found {
f.model.receivedFile(f.folderID, lastFile)
f.ReceivedFile(lastFile.Name, lastFile.IsDeleted())
found = false
}
batch = batch[:0]
files = files[:0]
return nil
}
batchSizeBytes := 0
loop:
for {
select {
@@ -1669,26 +1640,35 @@ loop:
break loop
}
job.file.Sequence = 0
batch = append(batch, job)
batchSizeBytes += job.file.ProtoSize()
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
handleBatch()
batchSizeBytes = 0
switch job.jobType {
case dbUpdateHandleFile, dbUpdateShortcutFile:
changedDirs[filepath.Dir(job.file.Name)] = struct{}{}
case dbUpdateHandleDir:
changedDirs[job.file.Name] = struct{}{}
case dbUpdateHandleSymlink, dbUpdateInvalidate:
// fsyncing symlinks is only supported by MacOS
// and invalidated files are db only changes -> no sync
}
// For some reason we seem to care about file deletions and
// content modification, but not about metadata and dirs/symlinks.
if !job.file.IsInvalid() && job.jobType&(dbUpdateHandleFile|dbUpdateDeleteFile) != 0 {
found = true
lastFile = job.file
}
job.file.Sequence = 0
batch.append(job.file)
batch.flushIfFull()
case <-tick.C:
if len(batch) > 0 {
handleBatch()
batchSizeBytes = 0
}
batch.flush()
}
}
if len(batch) > 0 {
handleBatch()
}
batch.flush()
}
// pullScannerRoutine aggregates paths to be scanned after pulling. The scan is
@@ -1736,25 +1716,23 @@ func removeAvailability(availabilities []Availability, availability Availability
return availabilities
}
func (f *sendReceiveFolder) moveForConflict(name string, lastModBy string) error {
if strings.Contains(filepath.Base(name), ".sync-conflict-") {
func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan chan<- string) error {
if isConflict(name) {
l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")
if err := f.fs.Remove(name); err != nil && !fs.IsNotExist(err) {
return err
return errors.Wrap(err, contextRemovingOldItem)
}
return nil
}
if f.MaxConflicts == 0 {
if err := f.fs.Remove(name); err != nil && !fs.IsNotExist(err) {
return err
return errors.Wrap(err, contextRemovingOldItem)
}
return nil
}
ext := filepath.Ext(name)
withoutExt := name[:len(name)-len(ext)]
newName := withoutExt + time.Now().Format(".sync-conflict-20060102-150405-") + lastModBy + ext
newName := conflictName(name, lastModBy)
err := f.fs.Rename(name, newName)
if fs.IsNotExist(err) {
// We were supposed to move a file away but it does not exist. Either
@@ -1764,23 +1742,23 @@ func (f *sendReceiveFolder) moveForConflict(name string, lastModBy string) error
err = nil
}
if f.MaxConflicts > -1 {
matches, gerr := f.fs.Glob(withoutExt + ".sync-conflict-????????-??????*" + ext)
if gerr == nil && len(matches) > f.MaxConflicts {
matches := existingConflicts(name, f.fs)
if len(matches) > f.MaxConflicts {
sort.Sort(sort.Reverse(sort.StringSlice(matches)))
for _, match := range matches[f.MaxConflicts:] {
gerr = f.fs.Remove(match)
if gerr != nil {
if gerr := f.fs.Remove(match); gerr != nil {
l.Debugln(f, "removing extra conflict", gerr)
}
}
} else if gerr != nil {
l.Debugln(f, "globbing for conflicts", gerr)
}
}
if err == nil {
scanChan <- newName
}
return err
}
func (f *sendReceiveFolder) newPullError(context, path string, err error) {
func (f *sendReceiveFolder) newPullError(path string, err error) {
f.pullErrorsMut.Lock()
defer f.pullErrorsMut.Unlock()
@@ -1790,8 +1768,13 @@ func (f *sendReceiveFolder) newPullError(context, path string, err error) {
if _, ok := f.pullErrors[path]; ok {
return
}
l.Infof("Puller (folder %s, file %q): %s: %v", f.Description(), path, context, err)
f.pullErrors[path] = fmt.Sprintf("%s: %s", context, err.Error())
l.Infof("Puller (folder %s, file %q): %v", f.Description(), path, err)
// Establish context to differentiate from errors while scanning.
// Use "syncing" as opposed to "pulling" as the latter might be used
// for errors occurring specificly in the puller routine.
f.pullErrors[path] = fmt.Sprintln("syncing:", err)
}
func (f *sendReceiveFolder) clearPullErrors() {
@@ -1813,9 +1796,33 @@ func (f *sendReceiveFolder) Errors() []FileError {
return errors
}
// deleteDir attempts to delete a directory. It checks for files/dirs inside
// deleteItemOnDisk deletes the file represented by old that is about to be replaced by new.
func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) (err error) {
defer func() {
err = errors.Wrap(err, contextRemovingOldItem)
}()
switch {
case item.IsDirectory():
// Directories aren't archived and need special treatment due
// to potential children.
return f.deleteDirOnDisk(item.Name, scanChan)
case !item.IsSymlink() && f.versioner != nil:
// If we should use versioning, let the versioner archive the
// file before we replace it. Archiving a non-existent file is not
// an error.
// Symlinks aren't archived.
return osutil.InWritableDir(f.versioner.Archive, f.fs, item.Name)
}
return osutil.InWritableDir(f.fs.Remove, f.fs, item.Name)
}
// deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside
// the directory and removes them if possible or returns an error if it fails
func (f *sendReceiveFolder) deleteDir(dir string, ignores *ignore.Matcher, scanChan chan<- string) error {
func (f *sendReceiveFolder) deleteDirOnDisk(dir string, scanChan chan<- string) error {
files, _ := f.fs.DirNames(dir)
toBeDeleted := make([]string, 0, len(files))
@@ -1826,11 +1833,11 @@ func (f *sendReceiveFolder) deleteDir(dir string, ignores *ignore.Matcher, scanC
for _, dirFile := range files {
fullDirFile := filepath.Join(dir, dirFile)
if fs.IsTemporary(dirFile) || ignores.Match(fullDirFile).IsDeletable() {
if fs.IsTemporary(dirFile) || f.ignores.Match(fullDirFile).IsDeletable() {
toBeDeleted = append(toBeDeleted, fullDirFile)
} else if ignores != nil && ignores.Match(fullDirFile).IsIgnored() {
} else if f.ignores != nil && f.ignores.Match(fullDirFile).IsIgnored() {
hasIgnored = true
} else if cf, ok := f.model.CurrentFolderFile(f.ID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() {
} else if cf, ok := f.fset.Get(protocol.LocalDeviceID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() {
// Something appeared in the dir that we either are not aware of
// at all, that we think should be deleted or that is invalid,
// but not currently ignored -> schedule scan. The scanChan
@@ -1877,8 +1884,35 @@ func (f *sendReceiveFolder) deleteDir(dir string, ignores *ignore.Matcher, scanC
return err
}
// itemChanged returns true if the given disk file differs from the information
// in the database and schedules that file for scanning
func (f *sendReceiveFolder) itemChanged(stat fs.FileInfo, item protocol.FileInfo, hasItem bool, scanChan chan<- string) (changed bool, err error) {
defer func() {
if changed {
scanChan <- item.Name
}
}()
if !hasItem || item.Deleted {
// The item appeared from nowhere
return true, nil
}
// Check that the item on disk is what we expect it to be according
// to the database. If there's a mismatch here, there might be local
// changes that we don't know about yet and we should scan before
// touching the item.
statItem, err := scanner.CreateFileInfo(stat, item.Name, f.fs)
if err != nil {
return false, errors.Wrap(err, "comparing item on disk to db")
}
return !statItem.IsEquivalentOptional(item, f.IgnorePerms, true, protocol.LocalAllFlags), nil
}
// checkToBeDeleted makes sure the file on disk is compatible with what there is
// in the DB before the caller proceeds with actually deleting it.
// I.e. non-nil error status means "Do not delete!".
func (f *sendReceiveFolder) checkToBeDeleted(cur protocol.FileInfo, scanChan chan<- string) error {
stat, err := f.fs.Lstat(cur.Name)
if err != nil {
@@ -1890,13 +1924,11 @@ func (f *sendReceiveFolder) checkToBeDeleted(cur protocol.FileInfo, scanChan cha
// do not delete.
return err
}
fi, err := scanner.CreateFileInfo(stat, cur.Name, f.fs)
changed, err := f.itemChanged(stat, cur, true, scanChan)
if err != nil {
return err
}
if !fi.IsEquivalentOptional(cur, f.IgnorePerms, true, protocol.LocalAllFlags) {
// File changed
scanChan <- cur.Name
if changed {
return errModified
}
return nil
@@ -1967,3 +1999,21 @@ func componentCount(name string) int {
}
return count
}
func conflictName(name, lastModBy string) string {
ext := filepath.Ext(name)
return name[:len(name)-len(ext)] + time.Now().Format(".sync-conflict-20060102-150405-") + lastModBy + ext
}
func isConflict(name string) bool {
return strings.Contains(filepath.Base(name), ".sync-conflict-")
}
func existingConflicts(name string, fs fs.Filesystem) []string {
ext := filepath.Ext(name)
matches, err := fs.Glob(name[:len(name)-len(ext)] + ".sync-conflict-????????-??????*" + ext)
if err != nil {
l.Debugln("globbing for conflicts", err)
}
return matches
}
+198 -125
View File
@@ -62,7 +62,7 @@ var diffTestData = []struct {
{"cont", "contents", 3, []protocol.BlockInfo{{Offset: 3, Size: 3}, {Offset: 6, Size: 2}}},
}
func setUpFile(filename string, blockNumbers []int) protocol.FileInfo {
func setupFile(filename string, blockNumbers []int) protocol.FileInfo {
// Create existing file
existingBlocks := make([]protocol.BlockInfo, len(blockNumbers))
for i := range blockNumbers {
@@ -75,27 +75,38 @@ func setUpFile(filename string, blockNumbers []int) protocol.FileInfo {
}
}
func setUpModel(files ...protocol.FileInfo) *Model {
db := db.OpenMemory()
model := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
model.AddFolder(defaultFolderConfig)
// Update index
model.updateLocalsFromScanning("default", files)
return model
func createFile(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo {
t.Helper()
f, err := fs.Create(name)
must(t, err)
f.Close()
fi, err := fs.Stat(name)
must(t, err)
file, err := scanner.CreateFileInfo(fi, name, fs)
must(t, err)
return file
}
func setUpSendReceiveFolder(model *Model) *sendReceiveFolder {
func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFolder) {
w := createTmpWrapper(defaultCfg)
model := newModel(w, myID, "syncthing", "dev", db.OpenMemory(), nil)
fcfg := testFolderConfigTmp()
model.AddFolder(fcfg)
// Update index
if files != nil {
model.updateLocalsFromScanning("default", files)
}
f := &sendReceiveFolder{
folder: folder{
stateTracker: newStateTracker("default"),
model: model,
fset: model.folderFiles[fcfg.ID],
initialScanFinished: make(chan struct{}),
ctx: context.TODO(),
FolderConfiguration: config.FolderConfiguration{
FilesystemType: fs.FilesystemTypeBasic,
Path: "testdata",
PullerMaxPendingKiB: defaultPullerPendingKiB,
},
FolderConfiguration: fcfg,
},
queue: newJobQueue(),
@@ -107,7 +118,7 @@ func setUpSendReceiveFolder(model *Model) *sendReceiveFolder {
// Folders are never actually started, so no initial scan will be done
close(f.initialScanFinished)
return f
return model, f
}
// Layout of the files: (indexes from the above array)
@@ -121,12 +132,16 @@ func TestHandleFile(t *testing.T) {
// Pull: 1, 3, 4, 6, 7
existingBlocks := []int{0, 2, 0, 0, 5, 0, 0, 8}
existingFile := setUpFile("filex", existingBlocks)
existingFile := setupFile("filex", existingBlocks)
requiredFile := existingFile
requiredFile.Blocks = blocks[1:]
m := setUpModel(existingFile)
f := setUpSendReceiveFolder(m)
m, f := setupSendReceiveFolder(existingFile)
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
}()
copyChan := make(chan copyBlocksState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
@@ -163,12 +178,20 @@ func TestHandleFileWithTemp(t *testing.T) {
// Pull: 1, 6
existingBlocks := []int{0, 2, 0, 0, 5, 0, 0, 8}
existingFile := setUpFile("file", existingBlocks)
existingFile := setupFile("file", existingBlocks)
requiredFile := existingFile
requiredFile.Blocks = blocks[1:]
m := setUpModel(existingFile)
f := setUpSendReceiveFolder(m)
m, f := setupSendReceiveFolder(existingFile)
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
}()
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
t.Fatal(err)
}
copyChan := make(chan copyBlocksState, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
@@ -197,8 +220,6 @@ func TestHandleFileWithTemp(t *testing.T) {
}
func TestCopierFinder(t *testing.T) {
testOs := &fatalOs{t}
// After diff between required and existing we should:
// Copy: 1, 2, 3, 4, 6, 7, 8
// Since there is no existing file, nor a temp file
@@ -206,18 +227,24 @@ func TestCopierFinder(t *testing.T) {
// After dropping out blocks found locally:
// Pull: 1, 5, 6, 8
tempFile := filepath.Join("testdata", fs.TempName("file2"))
testOs.Remove(tempFile)
defer testOs.Remove(tempFile)
tempFile := fs.TempName("file2")
existingBlocks := []int{0, 2, 3, 4, 0, 0, 7, 0}
existingFile := setUpFile(fs.TempName("file"), existingBlocks)
existingFile := setupFile(fs.TempName("file"), existingBlocks)
requiredFile := existingFile
requiredFile.Blocks = blocks[1:]
requiredFile.Name = "file2"
m := setUpModel(existingFile)
f := setUpSendReceiveFolder(m)
m, f := setupSendReceiveFolder(existingFile)
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
}()
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
t.Fatal(err)
}
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState, 4)
finisherChan := make(chan *sharedPullerState, 1)
@@ -259,7 +286,7 @@ func TestCopierFinder(t *testing.T) {
}
// Verify that the fetched blocks have actually been written to the temp file
blks, err := scanner.HashFile(context.TODO(), fs.NewFilesystem(fs.FilesystemTypeBasic, "."), tempFile, protocol.MinBlockSize, nil, false)
blks, err := scanner.HashFile(context.TODO(), f.Filesystem(), tempFile, protocol.MinBlockSize, nil, false)
if err != nil {
t.Log(err)
}
@@ -273,9 +300,15 @@ func TestCopierFinder(t *testing.T) {
}
func TestWeakHash(t *testing.T) {
testOs := &fatalOs{t}
// Setup the model/pull environment
model, fo := setupSendReceiveFolder()
ffs := fo.Filesystem()
defer func() {
os.Remove(model.cfg.ConfigPath())
os.Remove(ffs.URI())
}()
tempFile := filepath.Join("testdata", fs.TempName("weakhash"))
tempFile := fs.TempName("weakhash")
var shift int64 = 10
var size int64 = 1 << 20
expectBlocks := int(size / protocol.MinBlockSize)
@@ -284,18 +317,10 @@ func TestWeakHash(t *testing.T) {
expectPulls++
}
cleanup := func() {
for _, path := range []string{tempFile, "testdata/weakhash"} {
testOs.Remove(path)
}
}
cleanup()
defer cleanup()
f := testOs.Create("testdata/weakhash")
f, err := ffs.Create("weakhash")
must(t, err)
defer f.Close()
_, err := io.CopyN(f, rand.Reader, size)
_, err = io.CopyN(f, rand.Reader, size)
if err != nil {
t.Error(err)
}
@@ -337,9 +362,8 @@ func TestWeakHash(t *testing.T) {
ModifiedS: info.ModTime().Unix() + 1,
}
// Setup the model/pull environment
m := setUpModel(existingFile)
fo := setUpSendReceiveFolder(m)
model.updateLocalsFromScanning("default", []protocol.FileInfo{existingFile})
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState, expectBlocks)
finisherChan := make(chan *sharedPullerState, 1)
@@ -372,7 +396,9 @@ func TestWeakHash(t *testing.T) {
}
finish.fd.Close()
testOs.Remove(tempFile)
if err := ffs.Remove(tempFile); err != nil {
t.Fatal(err)
}
// Test 2 - using weak hash, expectPulls blocks pulled.
fo.WeakHashThresholdPct = -1
@@ -404,8 +430,12 @@ func TestCopierCleanup(t *testing.T) {
}
// Create a file
file := setUpFile("test", []int{0})
m := setUpModel(file)
file := setupFile("test", []int{0})
m, f := setupSendReceiveFolder(file)
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
}()
file.Blocks = []protocol.BlockInfo{blocks[1]}
file.Version = file.Version.Update(myID.Short())
@@ -435,21 +465,17 @@ func TestCopierCleanup(t *testing.T) {
}
func TestDeregisterOnFailInCopy(t *testing.T) {
testOs := &fatalOs{t}
file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
file := setUpFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
defer testOs.Remove("testdata/" + fs.TempName("filex"))
db := db.OpenMemory()
m := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig)
m, f := setupSendReceiveFolder()
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
}()
// Set up our evet subscription early
s := events.Default.Subscribe(events.ItemFinished)
f := setUpSendReceiveFolder(m)
// queue.Done should be called by the finisher routine
f.queue.Push("filex", 0, time.Time{})
f.queue.Pop()
@@ -465,7 +491,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
go f.copierRoutine(copyChan, pullChan, finisherBufferChan)
go f.finisherRoutine(ignore.New(defaultFs), finisherChan, dbUpdateChan, make(chan string))
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
f.handleFile(file, copyChan, finisherChan, dbUpdateChan)
@@ -474,7 +500,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
toPull := <-pullChan
// Close the file, causing errors on further access
toPull.sharedPullerState.fail("test", os.ErrNotExist)
toPull.sharedPullerState.fail(os.ErrNotExist)
// Unblock copier
go func() {
@@ -529,20 +555,17 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
}
func TestDeregisterOnFailInPull(t *testing.T) {
testOs := &fatalOs{t}
file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
file := setUpFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
defer testOs.Remove("testdata/" + fs.TempName("filex"))
db := db.OpenMemory()
m := NewModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig)
m, f := setupSendReceiveFolder()
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
}()
// Set up our evet subscription early
s := events.Default.Subscribe(events.ItemFinished)
f := setUpSendReceiveFolder(m)
// queue.Done should be called by the finisher routine
f.queue.Push("filex", 0, time.Time{})
f.queue.Pop()
@@ -559,7 +582,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
go f.copierRoutine(copyChan, pullChan, finisherBufferChan)
go f.pullerRoutine(pullChan, finisherBufferChan)
go f.finisherRoutine(ignore.New(defaultFs), finisherChan, dbUpdateChan, make(chan string))
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
f.handleFile(file, copyChan, finisherChan, dbUpdateChan)
@@ -612,35 +635,32 @@ func TestDeregisterOnFailInPull(t *testing.T) {
}
func TestIssue3164(t *testing.T) {
m := setUpModel(protocol.FileInfo{})
f := setUpSendReceiveFolder(m)
m, f := setupSendReceiveFolder()
ffs := f.Filesystem()
tmpDir := ffs.URI()
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(tmpDir)
}()
defaultFs.RemoveAll("issue3164")
defer defaultFs.RemoveAll("issue3164")
if err := defaultFs.MkdirAll("issue3164/oktodelete/foobar", 0777); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/issue3164/oktodelete/foobar/file", []byte("Hello"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/issue3164/oktodelete/file", []byte("Hello"), 0644); err != nil {
t.Fatal(err)
}
ignDir := filepath.Join("issue3164", "oktodelete")
subDir := filepath.Join(ignDir, "foobar")
must(t, ffs.MkdirAll(subDir, 0777))
must(t, ioutil.WriteFile(filepath.Join(tmpDir, subDir, "file"), []byte("Hello"), 0644))
must(t, ioutil.WriteFile(filepath.Join(tmpDir, ignDir, "file"), []byte("Hello"), 0644))
file := protocol.FileInfo{
Name: "issue3164",
}
matcher := ignore.New(defaultFs)
if err := matcher.Parse(bytes.NewBufferString("(?d)oktodelete"), ""); err != nil {
t.Fatal(err)
}
matcher := ignore.New(ffs)
must(t, matcher.Parse(bytes.NewBufferString("(?d)oktodelete"), ""))
f.ignores = matcher
dbUpdateChan := make(chan dbUpdateJob, 1)
f.handleDeleteDir(file, matcher, dbUpdateChan, make(chan string))
f.deleteDir(file, dbUpdateChan, make(chan string))
if _, err := defaultFs.Stat("testdata/issue3164"); !fs.IsNotExist(err) {
if _, err := ffs.Stat("issue3164"); !fs.IsNotExist(err) {
t.Fatal(err)
}
}
@@ -707,27 +727,25 @@ func TestDiffEmpty(t *testing.T) {
// option is true and the permissions do not match between the file on disk and
// in the db.
func TestDeleteIgnorePerms(t *testing.T) {
m := setUpModel()
f := setUpSendReceiveFolder(m)
m, f := setupSendReceiveFolder()
ffs := f.Filesystem()
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(ffs.URI())
}()
f.IgnorePerms = true
ffs := f.Filesystem()
name := "deleteIgnorePerms"
file, err := ffs.Create(name)
if err != nil {
t.Error(err)
}
defer ffs.Remove(name)
defer file.Close()
stat, err := file.Stat()
if err != nil {
t.Fatal(err)
}
must(t, err)
fi, err := scanner.CreateFileInfo(stat, name, ffs)
if err != nil {
t.Fatal(err)
}
must(t, err)
ffs.Chmod(name, 0600)
scanChan := make(chan string)
finished := make(chan struct{})
@@ -740,9 +758,7 @@ func TestDeleteIgnorePerms(t *testing.T) {
<-finished
case <-finished:
}
if err != nil {
t.Fatal(err)
}
must(t, err)
}
func TestCopyOwner(t *testing.T) {
@@ -761,24 +777,10 @@ func TestCopyOwner(t *testing.T) {
// Set up a folder with the CopyParentOwner bit and backed by a fake
// filesystem.
m := setUpModel()
f := &sendReceiveFolder{
folder: folder{
stateTracker: newStateTracker("default"),
model: m,
initialScanFinished: make(chan struct{}),
ctx: context.TODO(),
FolderConfiguration: config.FolderConfiguration{
FilesystemType: fs.FilesystemTypeFake,
Path: "/TestCopyOwner",
CopyOwnershipFromParent: true,
},
},
queue: newJobQueue(),
pullErrors: make(map[string]string),
pullErrorsMut: sync.NewMutex(),
}
m, f := setupSendReceiveFolder()
defer os.Remove(m.cfg.ConfigPath())
f.folder.FolderConfiguration = config.NewFolderConfiguration(m.id, f.ID, f.Label, fs.FilesystemTypeFake, "/TestCopyOwner")
f.folder.FolderConfiguration.CopyOwnershipFromParent = true
f.fs = f.Filesystem()
@@ -798,7 +800,7 @@ func TestCopyOwner(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
defer close(dbUpdateChan)
f.handleDir(dir, dbUpdateChan)
f.handleDir(dir, dbUpdateChan, nil)
<-dbUpdateChan // empty the channel for later
info, err := f.fs.Lstat("foo/bar")
@@ -829,7 +831,7 @@ func TestCopyOwner(t *testing.T) {
copierChan := make(chan copyBlocksState)
defer close(copierChan)
go f.copierRoutine(copierChan, nil, finisherChan)
go f.finisherRoutine(nil, finisherChan, dbUpdateChan, nil)
go f.finisherRoutine(finisherChan, dbUpdateChan, nil)
f.handleFile(file, copierChan, nil, nil)
<-dbUpdateChan
@@ -849,7 +851,7 @@ func TestCopyOwner(t *testing.T) {
SymlinkTarget: "over the rainbow",
}
f.handleSymlink(symlink, dbUpdateChan)
f.handleSymlink(symlink, dbUpdateChan, nil)
<-dbUpdateChan
info, err = f.fs.Lstat("foo/bar/sym")
@@ -860,3 +862,74 @@ func TestCopyOwner(t *testing.T) {
t.Fatalf("Expected symlink owner/group to be %d/%d, not %d/%d", expOwner, expGroup, info.Owner(), info.Group())
}
}
// TestSRConflictReplaceFileByDir checks that a conflict is created when an existing file
// is replaced with a directory and versions are conflicting
func TestSRConflictReplaceFileByDir(t *testing.T) {
m, f := setupSendReceiveFolder()
ffs := f.Filesystem()
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(ffs.URI())
}()
name := "foo"
// create local file
file := createFile(t, name, ffs)
file.Version = protocol.Vector{}.Update(myID.Short())
m.updateLocalsFromScanning(f.ID, []protocol.FileInfo{file})
// Simulate remote creating a dir with the same name
file.Type = protocol.FileInfoTypeDirectory
rem := device1.Short()
file.Version = protocol.Vector{}.Update(rem)
file.ModifiedBy = rem
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string, 1)
f.handleDir(file, dbUpdateChan, scanChan)
if confls := existingConflicts(name, ffs); len(confls) != 1 {
t.Fatal("Expected one conflict, got", len(confls))
} else if scan := <-scanChan; confls[0] != scan {
t.Fatal("Expected request to scan", confls[0], "got", scan)
}
}
// TestSRConflictReplaceFileByLink checks that a conflict is created when an existing file
// is replaced with a link and versions are conflicting
func TestSRConflictReplaceFileByLink(t *testing.T) {
m, f := setupSendReceiveFolder()
ffs := f.Filesystem()
defer func() {
os.Remove(m.cfg.ConfigPath())
os.Remove(ffs.URI())
}()
name := "foo"
// create local file
file := createFile(t, name, ffs)
file.Version = protocol.Vector{}.Update(myID.Short())
m.updateLocalsFromScanning(f.ID, []protocol.FileInfo{file})
// Simulate remote creating a symlink with the same name
file.Type = protocol.FileInfoTypeSymlink
file.SymlinkTarget = "bar"
rem := device1.Short()
file.Version = protocol.Vector{}.Update(rem)
file.ModifiedBy = rem
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string, 1)
f.handleSymlink(file, dbUpdateChan, scanChan)
if confls := existingConflicts(name, ffs); len(confls) != 1 {
t.Fatal("Expected one conflict, got", len(confls))
} else if scan := <-scanChan; confls[0] != scan {
t.Fatal("Expected request to scan", confls[0], "got", scan)
}
}
+152 -115
View File
@@ -65,6 +65,7 @@ type service interface {
CheckHealth() error
Errors() []FileError
WatchError() error
GetStatistics() stats.FolderStatistics
getState() (folderState, time.Time, error)
setState(state folderState)
@@ -76,10 +77,60 @@ type Availability struct {
FromTemporary bool `json:"fromTemporary"`
}
type Model struct {
type Model interface {
suture.Service
connections.Model
AddFolder(cfg config.FolderConfiguration)
RestartFolder(from, to config.FolderConfiguration)
StartFolder(folder string)
ResetFolder(folder string)
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
ScanFolderSubdirs(folder string, subs []string) error
State(folder string) (string, time.Time, error)
FolderErrors(folder string) ([]FileError, error)
WatchError(folder string) error
Override(folder string)
Revert(folder string)
BringToFront(folder, file string)
GetIgnores(folder string) ([]string, []string, error)
SetIgnores(folder string, content []string) error
GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability
GlobalSize(folder string) db.Counts
LocalSize(folder string) db.Counts
NeedSize(folder string) db.Counts
ReceiveOnlyChangedSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool)
Completion(device protocol.DeviceID, folder string) FolderCompletion
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
UsageReportingStats(version int, preview bool) map[string]interface{}
StartDeadlockDetector(timeout time.Duration)
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
}
type model struct {
*suture.Supervisor
cfg *config.Wrapper
cfg config.Wrapper
db *db.Lowlevel
finder *db.BlockFinder
progressEmitter *ProgressEmitter
@@ -98,7 +149,6 @@ type Model struct {
folderIgnores map[string]*ignore.Matcher // folder -> matcher object
folderRunners map[string]service // folder -> puller or scanner
folderRunnerTokens map[string][]suture.ServiceToken // folder -> tokens for puller or scanner
folderStatRefs map[string]*stats.FolderStatisticsReference // folder -> statsRef
folderRestartMuts syncMutexMap // folder -> restart mutex
pmut sync.RWMutex // protects the below
@@ -112,7 +162,7 @@ type Model struct {
foldersRunning int32 // for testing only
}
type folderFactory func(*Model, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service
var (
folderFactories = make(map[config.FolderType]folderFactory)
@@ -134,8 +184,8 @@ var (
// NewModel creates and starts a new model. The model starts in read-only mode,
// where it sends index information to connected peers and responds to requests
// for file data without altering the local folder in any way.
func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) *Model {
m := &Model{
func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string) Model {
m := &model{
Supervisor: suture.New("model", suture.Spec{
Log: func(line string) {
l.Debugln(line)
@@ -158,7 +208,6 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersi
folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: make(map[string]service),
folderRunnerTokens: make(map[string][]suture.ServiceToken),
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
conn: make(map[protocol.DeviceID]connections.Connection),
connRequestLimiters: make(map[protocol.DeviceID]*byteSemaphore),
closed: make(map[protocol.DeviceID]chan struct{}),
@@ -180,7 +229,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersi
// StartDeadlockDetector starts a deadlock detector on the models locks which
// causes panics in case the locks cannot be acquired in the given timeout
// period.
func (m *Model) StartDeadlockDetector(timeout time.Duration) {
func (m *model) StartDeadlockDetector(timeout time.Duration) {
l.Infof("Starting deadlock detector with %v timeout", timeout)
detector := newDeadlockDetector(timeout)
detector.Watch("fmut", m.fmut)
@@ -188,7 +237,7 @@ func (m *Model) StartDeadlockDetector(timeout time.Duration) {
}
// StartFolder constructs the folder service and starts it.
func (m *Model) StartFolder(folder string) {
func (m *model) StartFolder(folder string) {
m.fmut.Lock()
m.pmut.Lock()
folderType := m.startFolderLocked(folder)
@@ -199,7 +248,7 @@ func (m *Model) StartFolder(folder string) {
l.Infof("Ready to synchronize %s (%s)", folderCfg.Description(), folderType)
}
func (m *Model) startFolderLocked(folder string) config.FolderType {
func (m *model) startFolderLocked(folder string) config.FolderType {
if err := m.checkFolderRunningLocked(folder); err == errFolderMissing {
panic("cannot start nonexistent folder " + folder)
} else if err == nil {
@@ -213,15 +262,15 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
panic(fmt.Sprintf("unknown folder type 0x%x", cfg.Type))
}
fs := m.folderFiles[folder]
fset := m.folderFiles[folder]
// Find any devices for which we hold the index in the db, but the folder
// is not shared, and drop it.
expected := mapDevices(cfg.DeviceIDs())
for _, available := range fs.ListDevices() {
for _, available := range fset.ListDevices() {
if _, ok := expected[available]; !ok {
l.Debugln("dropping", folder, "state for", available)
fs.Drop(available)
fset.Drop(available)
}
}
@@ -230,7 +279,7 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
m.closeLocked(id, fmt.Errorf("started folder %v", cfg.Description()))
}
v, ok := fs.Sequence(protocol.LocalDeviceID), true
v, ok := fset.Sequence(protocol.LocalDeviceID), true
indexHasFiles := ok && v > 0
if !indexHasFiles {
// It's a blank folder, so this may the first time we're looking at
@@ -255,14 +304,14 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
m.folderRunnerTokens[folder] = append(m.folderRunnerTokens[folder], token)
}
ffs := fs.MtimeFS()
ffs := fset.MtimeFS()
// These are our metadata files, and they should always be hidden.
ffs.Hide(config.DefaultMarkerName)
ffs.Hide(".stversions")
ffs.Hide(".stignore")
p := folderFactory(m, cfg, ver, ffs)
p := folderFactory(m, fset, m.folderIgnores[folder], cfg, ver, ffs)
m.folderRunners[folder] = p
@@ -274,7 +323,7 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
return cfg.Type
}
func (m *Model) warnAboutOverwritingProtectedFiles(folder string) {
func (m *model) warnAboutOverwritingProtectedFiles(folder string) {
if m.folderCfgs[folder].Type == config.FolderTypeSendOnly {
return
}
@@ -308,7 +357,7 @@ func (m *Model) warnAboutOverwritingProtectedFiles(folder string) {
}
}
func (m *Model) AddFolder(cfg config.FolderConfiguration) {
func (m *model) AddFolder(cfg config.FolderConfiguration) {
if len(cfg.ID) == 0 {
panic("cannot add empty folder id")
}
@@ -322,7 +371,7 @@ func (m *Model) AddFolder(cfg config.FolderConfiguration) {
m.fmut.Unlock()
}
func (m *Model) addFolderLocked(cfg config.FolderConfiguration) {
func (m *model) addFolderLocked(cfg config.FolderConfiguration) {
m.folderCfgs[cfg.ID] = cfg
folderFs := cfg.Filesystem()
m.folderFiles[cfg.ID] = db.NewFileSet(cfg.ID, folderFs, m.db)
@@ -334,7 +383,7 @@ func (m *Model) addFolderLocked(cfg config.FolderConfiguration) {
m.folderIgnores[cfg.ID] = ignores
}
func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
func (m *model) RemoveFolder(cfg config.FolderConfiguration) {
m.fmut.Lock()
m.pmut.Lock()
// Delete syncthing specific files
@@ -348,7 +397,7 @@ func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
m.fmut.Unlock()
}
func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration, err error) {
func (m *model) tearDownFolderLocked(cfg config.FolderConfiguration, err error) {
// Close connections to affected devices
// Must happen before stopping the folder service to abort ongoing
// transmissions and thus allow timely service termination.
@@ -373,10 +422,9 @@ func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration, err error)
delete(m.folderIgnores, cfg.ID)
delete(m.folderRunners, cfg.ID)
delete(m.folderRunnerTokens, cfg.ID)
delete(m.folderStatRefs, cfg.ID)
}
func (m *Model) RestartFolder(from, to config.FolderConfiguration) {
func (m *model) RestartFolder(from, to config.FolderConfiguration) {
if len(to.ID) == 0 {
panic("bug: cannot restart empty folder ID")
}
@@ -421,7 +469,7 @@ func (m *Model) RestartFolder(from, to config.FolderConfiguration) {
l.Infof("%v folder %v (%v)", infoMsg, to.Description(), to.Type)
}
func (m *Model) UsageReportingStats(version int, preview bool) map[string]interface{} {
func (m *model) UsageReportingStats(version int, preview bool) map[string]interface{} {
stats := make(map[string]interface{})
if version >= 3 {
// Block stats
@@ -524,6 +572,7 @@ type ConnectionInfo struct {
Address string
ClientVersion string
Type string
Crypto string
}
func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
@@ -536,11 +585,12 @@ func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
"address": info.Address,
"clientVersion": info.ClientVersion,
"type": info.Type,
"crypto": info.Crypto,
})
}
// ConnectionStats returns a map with connection statistics for each device.
func (m *Model) ConnectionStats() map[string]interface{} {
func (m *model) ConnectionStats() map[string]interface{} {
m.fmut.RLock()
m.pmut.RLock()
@@ -559,6 +609,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
}
if conn, ok := m.conn[device]; ok {
ci.Type = conn.Type()
ci.Crypto = conn.Crypto()
ci.Connected = ok
ci.Statistics = conn.Statistics()
if addr := conn.RemoteAddr(); addr != nil {
@@ -587,7 +638,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
}
// DeviceStatistics returns statistics about each device
func (m *Model) DeviceStatistics() map[string]stats.DeviceStatistics {
func (m *model) DeviceStatistics() map[string]stats.DeviceStatistics {
res := make(map[string]stats.DeviceStatistics)
for id := range m.cfg.Devices() {
res[id.String()] = m.deviceStatRef(id).GetStatistics()
@@ -596,10 +647,12 @@ func (m *Model) DeviceStatistics() map[string]stats.DeviceStatistics {
}
// FolderStatistics returns statistics about each folder
func (m *Model) FolderStatistics() map[string]stats.FolderStatistics {
func (m *model) FolderStatistics() map[string]stats.FolderStatistics {
res := make(map[string]stats.FolderStatistics)
for id := range m.cfg.Folders() {
res[id] = m.folderStatRef(id).GetStatistics()
m.fmut.RLock()
defer m.fmut.RUnlock()
for id, runner := range m.folderRunners {
res[id] = runner.GetStatistics()
}
return res
}
@@ -614,7 +667,7 @@ type FolderCompletion struct {
// Completion returns the completion status, in percent, for the given device
// and folder.
func (m *Model) Completion(device protocol.DeviceID, folder string) FolderCompletion {
func (m *model) Completion(device protocol.DeviceID, folder string) FolderCompletion {
m.fmut.RLock()
rf, ok := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -696,7 +749,7 @@ func addSizeOfFile(s *db.Counts, f db.FileIntf) {
// GlobalSize returns the number of files, deleted files and total bytes for all
// files in the global model.
func (m *Model) GlobalSize(folder string) db.Counts {
func (m *model) GlobalSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
@@ -707,7 +760,7 @@ func (m *Model) GlobalSize(folder string) db.Counts {
// LocalSize returns the number of files, deleted files and total bytes for all
// files in the local folder.
func (m *Model) LocalSize(folder string) db.Counts {
func (m *model) LocalSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
@@ -719,7 +772,7 @@ func (m *Model) LocalSize(folder string) db.Counts {
// ReceiveOnlyChangedSize returns the number of files, deleted files and
// total bytes for all files that have changed locally in a receieve only
// folder.
func (m *Model) ReceiveOnlyChangedSize(folder string) db.Counts {
func (m *model) ReceiveOnlyChangedSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
@@ -729,7 +782,7 @@ func (m *Model) ReceiveOnlyChangedSize(folder string) db.Counts {
}
// NeedSize returns the number and total size of currently needed files.
func (m *Model) NeedSize(folder string) db.Counts {
func (m *model) NeedSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
@@ -753,7 +806,7 @@ func (m *Model) NeedSize(folder string) db.Counts {
// NeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
m.fmut.RLock()
defer m.fmut.RUnlock()
@@ -820,7 +873,7 @@ func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfo
// LocalChangedFiles returns a paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (m *Model) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated {
func (m *model) LocalChangedFiles(folder string, page, perpage int) []db.FileInfoTruncated {
m.fmut.RLock()
defer m.fmut.RUnlock()
@@ -861,7 +914,7 @@ func (m *Model) LocalChangedFiles(folder string, page, perpage int) []db.FileInf
// RemoteNeedFolderFiles returns paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
func (m *Model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
func (m *model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
m.fmut.RLock()
m.pmut.RLock()
if err := m.checkDeviceFolderConnectedLocked(device, folder); err != nil {
@@ -891,17 +944,17 @@ func (m *Model) RemoteNeedFolderFiles(device protocol.DeviceID, folder string, p
// Index is called when a new device is connected and we receive their full index.
// Implements the protocol.Model interface.
func (m *Model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) {
func (m *model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) {
m.handleIndex(deviceID, folder, fs, false)
}
// IndexUpdate is called for incremental updates to connected devices' indexes.
// Implements the protocol.Model interface.
func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) {
func (m *model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) {
m.handleIndex(deviceID, folder, fs, true)
}
func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, update bool) {
func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, update bool) {
op := "Index"
if update {
op += " update"
@@ -923,7 +976,7 @@ func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
m.fmut.RUnlock()
if !existing {
l.Fatalf("%v for nonexistent folder %q", op, folder)
panic(fmt.Sprintf("%v for nonexistent folder %q", op, folder))
}
if running {
@@ -931,7 +984,7 @@ func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
} else if update {
// Runner may legitimately not be set if this is the "cleanup" Index
// message at startup.
l.Fatalf("%v for not running folder %q", op, folder)
panic(fmt.Sprintf("%v for not running folder %q", op, folder))
}
m.pmut.RLock()
@@ -956,7 +1009,7 @@ func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
})
}
func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) {
func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) {
// Check the peer device's announced folders against our own. Emits events
// for folders that we don't expect (unknown or not shared).
// Also, collect a list of folders we do share, and if he's interested in
@@ -1096,7 +1149,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
}
}
go sendIndexes(conn, folder.ID, fs, m.folderIgnores[folder.ID], startSequence, dropSymlinks)
go sendIndexes(conn, folder.ID, fs, startSequence, dropSymlinks)
}
m.pmut.Lock()
@@ -1136,7 +1189,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
}
// handleIntroductions handles adding devices/shares that are shared by an introducer device
func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (folderDeviceSet, bool) {
func (m *model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (folderDeviceSet, bool) {
// This device is an introducer. Go through the announced lists of folders
// and devices and add what we are missing, remove what we have extra that
// has been introducer by the introducer.
@@ -1192,7 +1245,7 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
}
// handleDeintroductions handles removals of devices/shares that are removed by an introducer device
func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
func (m *model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
changed := false
devicesNotIntroduced := make(map[protocol.DeviceID]struct{})
@@ -1249,7 +1302,7 @@ func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
// handleAutoAccepts handles adding and sharing folders for devices that have
// AutoAcceptFolders set to true.
func (m *Model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) bool {
func (m *model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) bool {
if cfg, ok := m.cfg.Folder(folder.ID); !ok {
defaultPath := m.cfg.Options().DefaultFolderPath
defaultPathFs := fs.NewFilesystem(fs.FilesystemTypeBasic, defaultPath)
@@ -1295,7 +1348,7 @@ func (m *Model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder p
}
}
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
func (m *model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
addresses := []string{"dynamic"}
for _, addr := range device.Addresses {
if addr != "dynamic" {
@@ -1324,7 +1377,7 @@ func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.Dev
}
// Closed is called when a connection has been closed
func (m *Model) Closed(conn protocol.Connection, err error) {
func (m *model) Closed(conn protocol.Connection, err error) {
device := conn.ID()
m.pmut.Lock()
@@ -1350,14 +1403,14 @@ func (m *Model) Closed(conn protocol.Connection, err error) {
}
// close will close the underlying connection for a given device
func (m *Model) close(device protocol.DeviceID, err error) {
func (m *model) close(device protocol.DeviceID, err error) {
m.pmut.Lock()
m.closeLocked(device, err)
m.pmut.Unlock()
}
// closeLocked will close the underlying connection for a given device
func (m *Model) closeLocked(device protocol.DeviceID, err error) {
func (m *model) closeLocked(device protocol.DeviceID, err error) {
conn, ok := m.conn[device]
if !ok {
// There is no connection to close
@@ -1398,7 +1451,7 @@ func (r *requestResponse) Wait() {
// Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface.
func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) {
func (m *model) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) {
if size < 0 || offset < 0 {
return nil, protocol.ErrInvalid
}
@@ -1519,7 +1572,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, size in
return res, nil
}
func (m *Model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem, folder, name string, blockIndex int, hash []byte) {
func (m *model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem, folder, name string, blockIndex int, hash []byte) {
cf, ok := m.CurrentFolderFile(folder, name)
if !ok {
l.Debugf("%v recheckFile: %s: %q / %q: no current file", m, deviceID, folder, name)
@@ -1560,7 +1613,7 @@ func (m *Model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem,
}
}
func (m *Model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -1570,7 +1623,7 @@ func (m *Model) CurrentFolderFile(folder string, file string) (protocol.FileInfo
return fs.Get(protocol.LocalDeviceID, file)
}
func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -1581,7 +1634,7 @@ func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo
}
type cFiler struct {
m *Model
m Model
r string
}
@@ -1591,7 +1644,7 @@ func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
}
// Connection returns the current connection for device, and a boolean whether a connection was found.
func (m *Model) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) {
func (m *model) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) {
m.pmut.RLock()
cn, ok := m.conn[deviceID]
m.pmut.RUnlock()
@@ -1601,7 +1654,7 @@ func (m *Model) Connection(deviceID protocol.DeviceID) (connections.Connection,
return cn, ok
}
func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
func (m *model) GetIgnores(folder string) ([]string, []string, error) {
m.fmut.RLock()
defer m.fmut.RUnlock()
@@ -1630,7 +1683,7 @@ func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
return ignores.Lines(), ignores.Patterns(), nil
}
func (m *Model) SetIgnores(folder string, content []string) error {
func (m *model) SetIgnores(folder string, content []string) error {
cfg, ok := m.cfg.Folders()[folder]
if !ok {
return fmt.Errorf("folder %s does not exist", cfg.Description())
@@ -1664,7 +1717,7 @@ func (m *Model) SetIgnores(folder string, content []string) error {
// OnHello is called when an device connects to us.
// This allows us to extract some information from the Hello message
// and add it to a list of known devices ahead of any checks.
func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) error {
func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) error {
if m.cfg.IgnoredDevice(remoteID) {
return errDeviceIgnored
}
@@ -1694,7 +1747,7 @@ func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco
}
// GetHello is called when we are about to connect to some remote device.
func (m *Model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
func (m *model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
name := ""
if _, ok := m.cfg.Device(id); ok {
name = m.cfg.MyName()
@@ -1709,7 +1762,7 @@ func (m *Model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
// AddConnection adds a new peer connection to the model. An initial index will
// be sent to the connected peer, thereafter index updates whenever the local
// folder changes.
func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloResult) {
func (m *model) AddConnection(conn connections.Connection, hello protocol.HelloResult) {
deviceID := conn.ID()
device, ok := m.cfg.Device(deviceID)
if !ok {
@@ -1778,7 +1831,7 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
m.deviceWasSeen(deviceID)
}
func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
func (m *model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
m.fmut.RLock()
cfg, ok := m.folderCfgs[folder]
m.fmut.RUnlock()
@@ -1799,7 +1852,7 @@ func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, update
})
}
func (m *Model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatisticsReference {
func (m *model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatisticsReference {
m.fmut.Lock()
defer m.fmut.Unlock()
@@ -1812,27 +1865,11 @@ func (m *Model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatistic
return sr
}
func (m *Model) deviceWasSeen(deviceID protocol.DeviceID) {
func (m *model) deviceWasSeen(deviceID protocol.DeviceID) {
m.deviceStatRef(deviceID).WasSeen()
}
func (m *Model) folderStatRef(folder string) *stats.FolderStatisticsReference {
m.fmut.Lock()
defer m.fmut.Unlock()
sr, ok := m.folderStatRefs[folder]
if !ok {
sr = stats.NewFolderStatisticsReference(m.db, folder)
m.folderStatRefs[folder] = sr
}
return sr
}
func (m *Model) receivedFile(folder string, file protocol.FileInfo) {
m.folderStatRef(folder).ReceivedFile(file.Name, file.IsDeleted())
}
func sendIndexes(conn protocol.Connection, folder string, fs *db.FileSet, ignores *ignore.Matcher, prevSequence int64, dropSymlinks bool) {
func sendIndexes(conn protocol.Connection, folder string, fs *db.FileSet, prevSequence int64, dropSymlinks bool) {
deviceID := conn.ID()
var err error
@@ -1840,7 +1877,7 @@ func sendIndexes(conn protocol.Connection, folder string, fs *db.FileSet, ignore
defer l.Debugf("Exiting sendIndexes for %s to %s at %s: %v", folder, deviceID, conn, err)
// We need to send one index, regardless of whether there is something to send or not
prevSequence, err = sendIndexTo(prevSequence, conn, folder, fs, ignores, dropSymlinks)
prevSequence, err = sendIndexTo(prevSequence, conn, folder, fs, dropSymlinks)
// Subscribe to LocalIndexUpdated (we have new information to send) and
// DeviceDisconnected (it might be us who disconnected, so we should
@@ -1863,7 +1900,7 @@ func sendIndexes(conn protocol.Connection, folder string, fs *db.FileSet, ignore
continue
}
prevSequence, err = sendIndexTo(prevSequence, conn, folder, fs, ignores, dropSymlinks)
prevSequence, err = sendIndexTo(prevSequence, conn, folder, fs, dropSymlinks)
// Wait a short amount of time before entering the next loop. If there
// are continuous changes happening to the local index, this gives us
@@ -1874,7 +1911,7 @@ func sendIndexes(conn protocol.Connection, folder string, fs *db.FileSet, ignore
// sendIndexTo sends file infos with a sequence number higher than prevSequence and
// returns the highest sent sequence number.
func sendIndexTo(prevSequence int64, conn protocol.Connection, folder string, fs *db.FileSet, ignores *ignore.Matcher, dropSymlinks bool) (int64, error) {
func sendIndexTo(prevSequence int64, conn protocol.Connection, folder string, fs *db.FileSet, dropSymlinks bool) (int64, error) {
deviceID := conn.ID()
initial := prevSequence == 0
batch := newFileInfoBatch(nil)
@@ -1949,7 +1986,7 @@ func sendIndexTo(prevSequence int64, conn protocol.Connection, folder string, fs
return f.Sequence, err
}
func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) {
func (m *model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) {
m.updateLocals(folder, fs)
m.fmut.RLock()
@@ -1959,7 +1996,7 @@ func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo)
m.diskChangeDetected(folderCfg, fs, events.LocalChangeDetected)
}
func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) {
func (m *model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) {
m.updateLocals(folder, fs)
m.fmut.RLock()
@@ -1969,7 +2006,7 @@ func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) {
m.diskChangeDetected(folderCfg, fs, events.RemoteChangeDetected)
}
func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
func (m *model) updateLocals(folder string, fs []protocol.FileInfo) {
m.fmut.RLock()
files := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -1992,7 +2029,7 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
})
}
func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo, typeOfEvent events.EventType) {
func (m *model) diskChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo, typeOfEvent events.EventType) {
for _, file := range files {
if file.IsInvalid() {
continue
@@ -2035,7 +2072,7 @@ func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files [
}
}
func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
func (m *model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
m.pmut.RLock()
nc, ok := m.conn[deviceID]
m.pmut.RUnlock()
@@ -2049,7 +2086,7 @@ func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, o
return nc.Request(folder, name, offset, size, hash, weakHash, fromTemporary)
}
func (m *Model) ScanFolders() map[string]error {
func (m *model) ScanFolders() map[string]error {
m.fmut.RLock()
folders := make([]string, 0, len(m.folderCfgs))
for folder := range m.folderCfgs {
@@ -2087,11 +2124,11 @@ func (m *Model) ScanFolders() map[string]error {
return errors
}
func (m *Model) ScanFolder(folder string) error {
func (m *model) ScanFolder(folder string) error {
return m.ScanFolderSubdirs(folder, nil)
}
func (m *Model) ScanFolderSubdirs(folder string, subs []string) error {
func (m *model) ScanFolderSubdirs(folder string, subs []string) error {
m.fmut.RLock()
if err := m.checkFolderRunningLocked(folder); err != nil {
m.fmut.RUnlock()
@@ -2103,7 +2140,7 @@ func (m *Model) ScanFolderSubdirs(folder string, subs []string) error {
return runner.Scan(subs)
}
func (m *Model) DelayScan(folder string, next time.Duration) {
func (m *model) DelayScan(folder string, next time.Duration) {
m.fmut.Lock()
runner, ok := m.folderRunners[folder]
m.fmut.Unlock()
@@ -2115,7 +2152,7 @@ func (m *Model) DelayScan(folder string, next time.Duration) {
// numHashers returns the number of hasher routines to use for a given folder,
// taking into account configuration and available CPU cores.
func (m *Model) numHashers(folder string) int {
func (m *model) numHashers(folder string) int {
m.fmut.Lock()
folderCfg := m.folderCfgs[folder]
numFolders := len(m.folderCfgs)
@@ -2144,7 +2181,7 @@ func (m *Model) numHashers(folder string) int {
// generateClusterConfig returns a ClusterConfigMessage that is correct for
// the given peer device
func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfig {
func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfig {
var message protocol.ClusterConfig
m.fmut.RLock()
@@ -2201,7 +2238,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
return message
}
func (m *Model) State(folder string) (string, time.Time, error) {
func (m *model) State(folder string) (string, time.Time, error) {
m.fmut.RLock()
runner, ok := m.folderRunners[folder]
m.fmut.RUnlock()
@@ -2215,7 +2252,7 @@ func (m *Model) State(folder string) (string, time.Time, error) {
return state.String(), changed, err
}
func (m *Model) FolderErrors(folder string) ([]FileError, error) {
func (m *model) FolderErrors(folder string) ([]FileError, error) {
m.fmut.RLock()
defer m.fmut.RUnlock()
if err := m.checkFolderRunningLocked(folder); err != nil {
@@ -2224,7 +2261,7 @@ func (m *Model) FolderErrors(folder string) ([]FileError, error) {
return m.folderRunners[folder].Errors(), nil
}
func (m *Model) WatchError(folder string) error {
func (m *model) WatchError(folder string) error {
m.fmut.RLock()
defer m.fmut.RUnlock()
if err := m.checkFolderRunningLocked(folder); err != nil {
@@ -2233,7 +2270,7 @@ func (m *Model) WatchError(folder string) error {
return m.folderRunners[folder].WatchError()
}
func (m *Model) Override(folder string) {
func (m *model) Override(folder string) {
// Grab the runner and the file set.
m.fmut.RLock()
@@ -2251,7 +2288,7 @@ func (m *Model) Override(folder string) {
})
}
func (m *Model) Revert(folder string) {
func (m *model) Revert(folder string) {
// Grab the runner and the file set.
m.fmut.RLock()
@@ -2272,7 +2309,7 @@ func (m *Model) Revert(folder string) {
// CurrentSequence returns the change version for the given folder.
// This is guaranteed to increment if the contents of the local folder has
// changed.
func (m *Model) CurrentSequence(folder string) (int64, bool) {
func (m *model) CurrentSequence(folder string) (int64, bool) {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -2288,7 +2325,7 @@ func (m *Model) CurrentSequence(folder string) (int64, bool) {
// RemoteSequence returns the change version for the given folder, as
// sent by remote peers. This is guaranteed to increment if the contents of
// the remote or global folder has changed.
func (m *Model) RemoteSequence(folder string) (int64, bool) {
func (m *model) RemoteSequence(folder string) (int64, bool) {
m.fmut.RLock()
defer m.fmut.RUnlock()
@@ -2308,7 +2345,7 @@ func (m *Model) RemoteSequence(folder string) (int64, bool) {
return ver, true
}
func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} {
func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} {
m.fmut.RLock()
files, ok := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -2372,7 +2409,7 @@ func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly
return output
}
func (m *Model) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) {
func (m *model) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) {
fcfg, ok := m.cfg.Folder(folder)
if !ok {
return nil, errFolderMissing
@@ -2432,7 +2469,7 @@ func (m *Model) GetFolderVersions(folder string) (map[string][]versioner.FileVer
return files, nil
}
func (m *Model) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error) {
func (m *model) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error) {
fcfg, ok := m.cfg.Folder(folder)
if !ok {
return nil, errFolderMissing
@@ -2503,7 +2540,7 @@ func (m *Model) RestoreFolderVersions(folder string, versions map[string]time.Ti
return errors, nil
}
func (m *Model) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
func (m *model) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
// The slightly unusual locking sequence here is because we need to hold
// pmut for the duration (as the value returned from foldersFiles can
// get heavily modified on Close()), but also must acquire fmut before
@@ -2544,7 +2581,7 @@ next:
}
// BringToFront bumps the given files priority in the job queue.
func (m *Model) BringToFront(folder, file string) {
func (m *model) BringToFront(folder, file string) {
m.pmut.RLock()
defer m.pmut.RUnlock()
@@ -2554,20 +2591,20 @@ func (m *Model) BringToFront(folder, file string) {
}
}
func (m *Model) ResetFolder(folder string) {
func (m *model) ResetFolder(folder string) {
l.Infof("Cleaning data for folder %q", folder)
db.DropFolder(m.db, folder)
}
func (m *Model) String() string {
func (m *model) String() string {
return fmt.Sprintf("model@%p", m)
}
func (m *Model) VerifyConfiguration(from, to config.Configuration) error {
func (m *model) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
func (m *model) CommitConfiguration(from, to config.Configuration) bool {
// TODO: This should not use reflect, and should take more care to try to handle stuff without restart.
// Go through the folder configs and figure out if we need to restart or not.
@@ -2661,7 +2698,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
// checkFolderRunningLocked returns nil if the folder is up and running and a
// descriptive error if not.
// Need to hold (read) lock on m.fmut when calling this.
func (m *Model) checkFolderRunningLocked(folder string) error {
func (m *model) checkFolderRunningLocked(folder string) error {
_, ok := m.folderRunners[folder]
if ok {
return nil
@@ -2679,7 +2716,7 @@ func (m *Model) checkFolderRunningLocked(folder string) error {
// checkFolderDeviceStatusLocked first checks the folder and then whether the
// given device is connected and shares this folder.
// Need to hold (read) lock on both m.fmut and m.pmut when calling this.
func (m *Model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folder string) error {
func (m *model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folder string) error {
if err := m.checkFolderRunningLocked(folder); err != nil {
return err
}
@@ -2814,7 +2851,7 @@ func (b *fileInfoBatch) append(f protocol.FileInfo) {
}
func (b *fileInfoBatch) flushIfFull() error {
if len(b.infos) == maxBatchSizeFiles || b.size > maxBatchSizeBytes {
if len(b.infos) >= maxBatchSizeFiles || b.size >= maxBatchSizeBytes {
return b.flush()
}
return nil
+206 -352
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -31,7 +31,7 @@ type ProgressEmitter struct {
// NewProgressEmitter creates a new progress emitter which emits
// DownloadProgress events every interval.
func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter {
func NewProgressEmitter(cfg config.Wrapper) *ProgressEmitter {
t := &ProgressEmitter{
stop: make(chan struct{}),
registry: make(map[string]*sharedPullerState),
+3 -6
View File
@@ -8,6 +8,7 @@ package model
import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
@@ -51,12 +52,10 @@ func expectTimeout(w *events.Subscription, t *testing.T) {
}
func TestProgressEmitter(t *testing.T) {
testOs := &fatalOs{t}
w := events.Default.Subscribe(events.DownloadProgress)
c := createTmpWrapper(config.Configuration{})
defer testOs.Remove(c.ConfigPath())
defer os.Remove(c.ConfigPath())
c.SetOptions(config.OptionsConfiguration{
ProgressUpdateIntervalS: 0,
})
@@ -104,10 +103,8 @@ func TestProgressEmitter(t *testing.T) {
}
func TestSendDownloadProgressMessages(t *testing.T) {
testOs := &fatalOs{t}
c := createTmpWrapper(config.Configuration{})
defer testOs.Remove(c.ConfigPath())
defer os.Remove(c.ConfigPath())
c.SetOptions(config.OptionsConfiguration{
ProgressUpdateIntervalS: 0,
TempIndexMinBlocks: 10,
+130 -130
View File
@@ -18,7 +18,6 @@ import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
@@ -26,16 +25,15 @@ import (
)
func TestRequestSimple(t *testing.T) {
testOs := &fatalOs{t}
// Verify that the model performs a request and creates a file based on
// an incoming index update.
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
tfs := fcfg.Filesystem()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(tfs.URI())
os.Remove(w.ConfigPath())
}()
// We listen for incoming index updates and trigger when we see one for
@@ -64,14 +62,12 @@ func TestRequestSimple(t *testing.T) {
<-done
// Verify the contents
if err := equalContents(filepath.Join(tmpDir, "testfile"), contents); err != nil {
if err := equalContents(filepath.Join(tfs.URI(), "testfile"), contents); err != nil {
t.Error("File did not sync correctly:", err)
}
}
func TestSymlinkTraversalRead(t *testing.T) {
testOs := &fatalOs{t}
// Verify that a symlink can not be traversed for reading.
if runtime.GOOS == "windows" {
@@ -79,11 +75,11 @@ func TestSymlinkTraversalRead(t *testing.T) {
return
}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(fcfg.Filesystem().URI())
os.Remove(w.ConfigPath())
}()
// We listen for incoming index updates and trigger when we see one for
@@ -119,8 +115,6 @@ func TestSymlinkTraversalRead(t *testing.T) {
}
func TestSymlinkTraversalWrite(t *testing.T) {
testOs := &fatalOs{t}
// Verify that a symlink can not be traversed for writing.
if runtime.GOOS == "windows" {
@@ -128,11 +122,11 @@ func TestSymlinkTraversalWrite(t *testing.T) {
return
}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(fcfg.Filesystem().URI())
os.Remove(w.ConfigPath())
}()
// We listen for incoming index updates and trigger when we see one for
@@ -189,15 +183,13 @@ func TestSymlinkTraversalWrite(t *testing.T) {
}
func TestRequestCreateTmpSymlink(t *testing.T) {
testOs := &fatalOs{t}
// Test that an update for a temporary file is invalidated
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(fcfg.Filesystem().URI())
os.Remove(w.ConfigPath())
}()
// We listen for incoming index updates and trigger when we see one for
@@ -231,8 +223,6 @@ func TestRequestCreateTmpSymlink(t *testing.T) {
}
func TestRequestVersioningSymlinkAttack(t *testing.T) {
testOs := &fatalOs{t}
if runtime.GOOS == "windows" {
t.Skip("no symlink support on Windows")
}
@@ -240,39 +230,25 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
// Sets up a folder with trashcan versioning and tries to use a
// deleted symlink to escape
tmpDir := createTmpDir()
defer testOs.RemoveAll(tmpDir)
w, fcfg := tmpDefaultWrapper()
defer func() {
os.RemoveAll(fcfg.Filesystem().URI())
os.Remove(w.ConfigPath())
}()
cfg := defaultCfgWrapper.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: myID},
{DeviceID: device1},
}
cfg.Folders[0].Versioning = config.VersioningConfiguration{
Type: "trashcan",
}
w := createTmpWrapper(cfg)
defer testOs.Remove(w.ConfigPath())
fcfg.Versioning = config.VersioningConfiguration{Type: "trashcan"}
w.SetFolder(fcfg)
db := db.OpenMemory()
m := NewModel(w, myID, "syncthing", "dev", db, nil)
m.AddFolder(cfg.Folders[0])
m.ServeBackground()
m.StartFolder("default")
m, fc := setupModelWithConnectionFromWrapper(w)
defer m.Stop()
defer testOs.RemoveAll(tmpDir)
fc := addFakeConn(m, device1)
fc.folder = "default"
// Create a temporary directory that we will use as target to see if
// we can escape to it
tmpdir, err := ioutil.TempDir("", "syncthing-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
// We listen for incoming index updates and trigger when we see one for
// the expected test file.
@@ -328,17 +304,16 @@ func TestPullInvalidIgnoredSR(t *testing.T) {
func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
t.Helper()
testOs := &fatalOs{t}
w := createTmpWrapper(defaultCfgWrapper.RawCopy())
fcfg, tmpDir := testFolderConfigTmp()
fcfg := testFolderConfigTmp()
fss := fcfg.Filesystem()
fcfg.Type = ft
w.SetFolder(fcfg)
m, fc := setupModelWithConnectionFromWrapper(w)
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(fss.URI())
os.Remove(w.ConfigPath())
}()
// Reach in and update the ignore matcher to one that always does
@@ -346,7 +321,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
// because we might be changing the files on disk often enough that the
// mtimes will be unreliable to determine change status.
m.fmut.Lock()
m.folderIgnores["default"] = ignore.New(fcfg.Filesystem(), ignore.WithChangeDetector(newAlwaysChanged()))
m.folderIgnores["default"] = ignore.New(fss, ignore.WithChangeDetector(newAlwaysChanged()))
m.fmut.Unlock()
if err := m.SetIgnores("default", []string{"*ignored*"}); err != nil {
@@ -366,7 +341,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
fc.deleteFile(invDel)
fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
if err := ioutil.WriteFile(filepath.Join(tmpDir, ignExisting), otherContents, 0644); err != nil {
if err := ioutil.WriteFile(filepath.Join(fss.URI(), ignExisting), otherContents, 0644); err != nil {
panic(err)
}
@@ -456,13 +431,11 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
}
func TestIssue4841(t *testing.T) {
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(fcfg.Filesystem().URI())
os.Remove(w.ConfigPath())
}()
received := make(chan protocol.FileInfo)
@@ -499,20 +472,17 @@ func TestIssue4841(t *testing.T) {
}
func TestRescanIfHaveInvalidContent(t *testing.T) {
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
tmpDir := fcfg.Filesystem().URI()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(tmpDir)
os.Remove(w.ConfigPath())
}()
payload := []byte("hello")
if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777))
received := make(chan protocol.FileInfo)
fc.mut.Lock()
@@ -549,9 +519,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
payload = []byte("bye")
buf = make([]byte, len(payload))
if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777); err != nil {
t.Fatal(err)
}
must(t, ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777))
_, err = m.Request(device1, "default", "foo", int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
if err == nil {
@@ -569,18 +537,16 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
}
func TestParentDeletion(t *testing.T) {
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
testFs := fcfg.Filesystem()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(testFs.URI())
os.Remove(w.ConfigPath())
}()
parent := "foo"
child := filepath.Join(parent, "bar")
testFs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
received := make(chan []protocol.FileInfo)
fc.addFile(parent, 0777, protocol.FileInfoTypeDirectory, nil)
@@ -603,9 +569,7 @@ func TestParentDeletion(t *testing.T) {
}
// Delete parent dir
if err := testFs.RemoveAll(parent); err != nil {
t.Fatal(err)
}
must(t, testFs.RemoveAll(parent))
// Scan only the child dir (not the parent)
if err := m.ScanFolderSubdirs("default", []string{child}); err != nil {
@@ -656,13 +620,11 @@ func TestRequestSymlinkWindows(t *testing.T) {
t.Skip("windows specific test")
}
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(fcfg.Filesystem().URI())
os.Remove(w.ConfigPath())
}()
done := make(chan struct{})
@@ -719,9 +681,16 @@ func TestRequestSymlinkWindows(t *testing.T) {
}
}
func testFolderConfigTmp() (config.FolderConfiguration, string) {
func tmpDefaultWrapper() (config.Wrapper, config.FolderConfiguration) {
w := createTmpWrapper(defaultCfgWrapper.RawCopy())
fcfg := testFolderConfigTmp()
w.SetFolder(fcfg)
return w, fcfg
}
func testFolderConfigTmp() config.FolderConfiguration {
tmpDir := createTmpDir()
return testFolderConfig(tmpDir), tmpDir
return testFolderConfig(tmpDir)
}
func testFolderConfig(path string) config.FolderConfiguration {
@@ -731,15 +700,13 @@ func testFolderConfig(path string) config.FolderConfiguration {
return cfg
}
func setupModelWithConnection() (*Model, *fakeConnection, string, *config.Wrapper) {
w := createTmpWrapper(defaultCfgWrapper.RawCopy())
fcfg, tmpDir := testFolderConfigTmp()
w.SetFolder(fcfg)
func setupModelWithConnection() (*model, *fakeConnection, config.FolderConfiguration, config.Wrapper) {
w, fcfg := tmpDefaultWrapper()
m, fc := setupModelWithConnectionFromWrapper(w)
return m, fc, tmpDir, w
return m, fc, fcfg, w
}
func setupModelWithConnectionFromWrapper(w *config.Wrapper) (*Model, *fakeConnection) {
func setupModelWithConnectionFromWrapper(w config.Wrapper) (*model, *fakeConnection) {
m := setupModel(w)
fc := addFakeConn(m, device1)
@@ -751,7 +718,7 @@ func setupModelWithConnectionFromWrapper(w *config.Wrapper) (*Model, *fakeConnec
}
func createTmpDir() string {
tmpDir, err := ioutil.TempDir("", "_request-")
tmpDir, err := ioutil.TempDir("", "syncthing_testFolder-")
if err != nil {
panic("Failed to create temporary testing dir")
}
@@ -768,15 +735,14 @@ func equalContents(path string, contents []byte) error {
}
func TestRequestRemoteRenameChanged(t *testing.T) {
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
tfs := fcfg.Filesystem()
tmpDir := tfs.URI()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(tmpDir)
os.Remove(w.ConfigPath())
}()
tfs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
done := make(chan struct{})
fc.mut.Lock()
@@ -806,26 +772,49 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
fc.sendIndexUpdate()
select {
case <-done:
done = make(chan struct{})
fc.mut.Lock()
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
select {
case <-done:
t.Fatalf("More than one index update sent")
default:
}
close(done)
}
fc.mut.Unlock()
case <-time.After(10 * time.Second):
t.Fatal("timed out")
}
for _, n := range [2]string{a, b} {
if err := equalContents(filepath.Join(tmpDir, n), data[n]); err != nil {
t.Fatal(err)
must(t, equalContents(filepath.Join(tmpDir, n), data[n]))
}
var gotA, gotB, gotConfl bool
done = make(chan struct{})
fc.mut.Lock()
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
select {
case <-done:
t.Fatalf("Received more index updates than expected")
default:
}
for _, f := range fs {
switch {
case f.Name == a:
if gotA {
t.Error("Got more than one index update for", f.Name)
}
gotA = true
case f.Name == b:
if gotB {
t.Error("Got more than one index update for", f.Name)
}
gotB = true
case strings.HasPrefix(f.Name, "b.sync-conflict-"):
if gotConfl {
t.Error("Got more than one index update for conflicts of", f.Name)
}
gotConfl = true
default:
t.Error("Got unexpected file in index update", f.Name)
}
}
if gotA && gotB && gotConfl {
close(done)
}
}
fc.mut.Unlock()
fd, err := tfs.OpenFile(b, fs.OptReadWrite, 0644)
if err != nil {
@@ -840,11 +829,20 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
// rename
fc.deleteFile(a)
fc.updateFile(b, 0644, protocol.FileInfoTypeFile, data[a])
// Make sure the remote file for b is newer and thus stays global -> local conflict
fc.mut.Lock()
for i := range fc.files {
if fc.files[i].Name == b {
fc.files[i].ModifiedS += 100
break
}
}
fc.mut.Unlock()
fc.sendIndexUpdate()
select {
case <-done:
case <-time.After(10 * time.Second):
t.Fatal("timed out")
t.Errorf("timed out without receiving all expected index updates")
}
// Check outcome
@@ -853,24 +851,30 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
case path == a:
t.Errorf(`File "a" was not removed`)
case path == b:
if err := equalContents(filepath.Join(tmpDir, b), otherData); err != nil {
t.Errorf(`Modified file "b" was overwritten`)
if err := equalContents(filepath.Join(tmpDir, b), data[a]); err != nil {
t.Error(`File "b" has unexpected content (renamed from a on remote)`)
}
case strings.HasPrefix(path, b+".sync-conflict-"):
if err := equalContents(filepath.Join(tmpDir, path), otherData); err != nil {
t.Error(`Sync conflict of "b" has unexptected content`)
}
case path == "." || strings.HasPrefix(path, ".stfolder"):
default:
t.Error("Found unexpected file", path)
}
return nil
})
}
func TestRequestRemoteRenameConflict(t *testing.T) {
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
tfs := fcfg.Filesystem()
tmpDir := tfs.URI()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(tmpDir)
os.Remove(w.ConfigPath())
}()
tfs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
recv := make(chan int)
fc.mut.Lock()
@@ -900,9 +904,7 @@ func TestRequestRemoteRenameConflict(t *testing.T) {
}
for _, n := range [2]string{a, b} {
if err := equalContents(filepath.Join(tmpDir, n), data[n]); err != nil {
t.Fatal(err)
}
must(t, equalContents(filepath.Join(tmpDir, n), data[n]))
}
fd, err := tfs.OpenFile(b, fs.OptReadWrite, 0644)
@@ -960,15 +962,13 @@ func TestRequestRemoteRenameConflict(t *testing.T) {
}
func TestRequestDeleteChanged(t *testing.T) {
testOs := &fatalOs{t}
m, fc, tmpDir, w := setupModelWithConnection()
m, fc, fcfg, w := setupModelWithConnection()
tfs := fcfg.Filesystem()
defer func() {
m.Stop()
testOs.RemoveAll(tmpDir)
testOs.Remove(w.ConfigPath())
os.RemoveAll(tfs.URI())
os.Remove(w.ConfigPath())
}()
tfs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
done := make(chan struct{})
fc.mut.Lock()
+10 -9
View File
@@ -7,11 +7,12 @@
package model
import (
"fmt"
"io"
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
@@ -96,7 +97,7 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
// here.
dir := filepath.Dir(s.tempName)
if info, err := s.fs.Stat(dir); err != nil {
s.failLocked("dst stat dir", err)
s.failLocked(errors.Wrap(err, "ensuring parent dir is writeable"))
return nil, err
} else if info.Mode()&0200 == 0 {
err := s.fs.Chmod(dir, 0755)
@@ -139,13 +140,13 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
// what the umask dictates.
if err := s.fs.Chmod(s.tempName, mode); err != nil {
s.failLocked("dst create chmod", err)
s.failLocked(errors.Wrap(err, "setting perms on temp file"))
return nil, err
}
}
fd, err := s.fs.OpenFile(s.tempName, flags, mode)
if err != nil {
s.failLocked("dst create", err)
s.failLocked(errors.Wrap(err, "opening temp file"))
return nil, err
}
@@ -176,7 +177,7 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
l.Debugln("failed to remove temporary file:", remErr)
}
s.failLocked("dst truncate", err)
s.failLocked(err)
return nil, err
}
}
@@ -190,19 +191,19 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
// fail sets the error on the puller state compose of error, and marks the
// sharedPullerState as failed. Is a no-op when called on an already failed state.
func (s *sharedPullerState) fail(context string, err error) {
func (s *sharedPullerState) fail(err error) {
s.mut.Lock()
defer s.mut.Unlock()
s.failLocked(context, err)
s.failLocked(err)
}
func (s *sharedPullerState) failLocked(context string, err error) {
func (s *sharedPullerState) failLocked(err error) {
if s.err != nil || err == nil {
return
}
s.err = fmt.Errorf("%s: %s", context, err.Error())
s.err = err
}
func (s *sharedPullerState) failed() error {
+10 -10
View File
@@ -7,6 +7,7 @@
package model
import (
"os"
"testing"
"github.com/syncthing/syncthing/lib/fs"
@@ -15,18 +16,17 @@ import (
// Test creating temporary file inside read-only directory
func TestReadOnlyDir(t *testing.T) {
testOs := &fatalOs{t}
// Create a read only directory, clean it up afterwards.
testOs.Mkdir("testdata/read_only_dir", 0555)
defer func() {
testOs.Chmod("testdata/read_only_dir", 0755)
testOs.RemoveAll("testdata/read_only_dir")
}()
tmpDir := createTmpDir()
defer os.RemoveAll(tmpDir)
if err := os.Chmod(tmpDir, 0555); err != nil {
t.Fatal(err)
}
defer os.Chmod(tmpDir, 0755)
s := sharedPullerState{
fs: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
tempName: "read_only_dir/.temp_name",
fs: fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir),
tempName: ".temp_name",
mut: sync.NewRWMutex(),
}
@@ -38,6 +38,6 @@ func TestReadOnlyDir(t *testing.T) {
t.Fatal("Unexpected nil fd")
}
s.fail("Test done", nil)
s.fail(nil)
s.finalClose()
}
+9 -13
View File
@@ -21,40 +21,38 @@ type fatalOs struct {
fatal
}
func (f *fatalOs) must(fn func() error) {
func must(f fatal, err error) {
f.Helper()
if err := fn(); err != nil {
if err != nil {
f.Fatal(err)
}
}
func (f *fatalOs) Chmod(name string, mode os.FileMode) {
f.Helper()
f.must(func() error { return os.Chmod(name, mode) })
must(f, os.Chmod(name, mode))
}
func (f *fatalOs) Chtimes(name string, atime time.Time, mtime time.Time) {
f.Helper()
f.must(func() error { return os.Chtimes(name, atime, mtime) })
must(f, os.Chtimes(name, atime, mtime))
}
func (f *fatalOs) Create(name string) *os.File {
f.Helper()
file, err := os.Create(name)
if err != nil {
f.Fatal(err)
}
must(f, err)
return file
}
func (f *fatalOs) Mkdir(name string, perm os.FileMode) {
f.Helper()
f.must(func() error { return os.Mkdir(name, perm) })
must(f, os.Mkdir(name, perm))
}
func (f *fatalOs) MkdirAll(name string, perm os.FileMode) {
f.Helper()
f.must(func() error { return os.MkdirAll(name, perm) })
must(f, os.MkdirAll(name, perm))
}
func (f *fatalOs) Remove(name string) {
@@ -73,14 +71,12 @@ func (f *fatalOs) RemoveAll(name string) {
func (f *fatalOs) Rename(oldname, newname string) {
f.Helper()
f.must(func() error { return os.Rename(oldname, newname) })
must(f, os.Rename(oldname, newname))
}
func (f *fatalOs) Stat(name string) os.FileInfo {
f.Helper()
info, err := os.Stat(name)
if err != nil {
f.Fatal(err)
}
must(f, err)
return info
}
+2 -2
View File
@@ -23,7 +23,7 @@ import (
// setup/renewal of a port mapping.
type Service struct {
id protocol.DeviceID
cfg *config.Wrapper
cfg config.Wrapper
stop chan struct{}
mappings []*Mapping
@@ -31,7 +31,7 @@ type Service struct {
mut sync.RWMutex
}
func NewService(id protocol.DeviceID, cfg *config.Wrapper) *Service {
func NewService(id protocol.DeviceID, cfg config.Wrapper) *Service {
return &Service{
id: id,
cfg: cfg,
+4 -14
View File
@@ -304,7 +304,7 @@ func (w *walker) handleItem(ctx context.Context, path string, toHashChan chan<-
switch {
case info.IsSymlink():
if err := w.walkSymlink(ctx, path, finishedChan); err != nil {
if err := w.walkSymlink(ctx, path, info, finishedChan); err != nil {
return err
}
if info.IsDir() {
@@ -411,19 +411,14 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
// walkSymlink returns nil or an error, if the error is of the nature that
// it should stop the entire walk.
func (w *walker) walkSymlink(ctx context.Context, relPath string, finishedChan chan<- ScanResult) error {
func (w *walker) walkSymlink(ctx context.Context, relPath string, info fs.FileInfo, finishedChan chan<- ScanResult) error {
// Symlinks are not supported on Windows. We ignore instead of returning
// an error.
if runtime.GOOS == "windows" {
return nil
}
// We always rehash symlinks as they have no modtime or
// permissions. We check if they point to the old target by
// checking that their existing blocks match with the blocks in
// the index.
target, err := w.Filesystem.ReadSymlink(relPath)
f, err := CreateFileInfo(info, relPath, w.Filesystem)
if err != nil {
w.handleError(ctx, "reading link:", relPath, err, finishedChan)
return nil
@@ -431,12 +426,6 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, finishedChan c
curFile, hasCurFile := w.CurrentFiler.CurrentFile(relPath)
f := protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeSymlink,
NoPermissions: true, // Symlinks don't have permissions of their own
SymlinkTarget: target,
}
f = w.updateFileInfo(f, curFile)
if hasCurFile {
@@ -613,6 +602,7 @@ func CreateFileInfo(fi fs.FileInfo, name string, filesystem fs.Filesystem) (prot
return protocol.FileInfo{}, err
}
f.SymlinkTarget = target
f.NoPermissions = true // Symlinks don't have permissions of their own
return f, nil
}
f.Permissions = uint32(fi.Mode() & fs.ModePerm)
+7 -1
View File
@@ -20,6 +20,7 @@ import (
"os"
"time"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/rand"
)
@@ -69,6 +70,11 @@ var (
func init() {
// Creates the list of ciper suites that SecureDefault uses.
cipherSuites = buildCipherSuites()
if build.IsBeta {
// Append "tls13=1" to GODEBUG before starting TLS, to enable TLS
// 1.3 in Go 1.12.
os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
}
}
// SecureDefault returns a tls.Config with reasonable, secure defaults set.
@@ -84,7 +90,7 @@ func SecureDefault() *tls.Config {
// secure (so the web tells me, don't ask me to explain the
// details).
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
// The cipher suite lists built above.
// The cipher suite lists built above. These are ignored in TLS 1.3.
CipherSuites: cs,
// We've put some thought into this choice and would like it to
// matter.
+2 -2
View File
@@ -125,14 +125,14 @@ func newAggregator(folderCfg config.FolderConfiguration, ctx context.Context) *a
return a
}
func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg *config.Wrapper, ctx context.Context) {
func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg config.Wrapper, ctx context.Context) {
a := newAggregator(folderCfg, ctx)
// Necessary for unit tests where the backend is mocked
go a.mainLoop(in, out, cfg)
}
func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg *config.Wrapper) {
func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg config.Wrapper) {
a.notifyTimer = time.NewTimer(a.notifyDelay)
defer a.notifyTimer.Stop()
+19 -19
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STDISCOSRV" "1" "Jan 26, 2019" "v1" "Syncthing"
.TH "STDISCOSRV" "1" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.
@@ -52,12 +52,12 @@ Syncthing project also maintains a global cluster for public use.
.INDENT 0.0
.TP
.B \-cert=<file>
Certificate file (default ./cert.pem).
Certificate file (default "./cert.pem").
.UNINDENT
.INDENT 0.0
.TP
.B \-db\-dir=<string>
Database directory, where data is stored (default ./discovery.db).
Database directory, where data is stored (default "./discovery.db").
.UNINDENT
.INDENT 0.0
.TP
@@ -72,12 +72,12 @@ Listen on HTTP (behind an HTTPS proxy).
.INDENT 0.0
.TP
.B \-key=<file>
Key file (default ./key.pem).
Key file (default "./key.pem").
.UNINDENT
.INDENT 0.0
.TP
.B \-listen=<address>
Listen address (default :8443).
Listen address (default ":8443").
.UNINDENT
.INDENT 0.0
.TP
@@ -92,14 +92,14 @@ Replication peers, \fI\%id@address\fP <\fBid@address\fP>, comma separated
.INDENT 0.0
.TP
.B \-replication\-listen=<address>
Listen address for incoming replication connections (default :19200).
Listen address for incoming replication connections (default ":19200").
.UNINDENT
.SH POINTING SYNCTHING AT YOUR DISCOVERY SERVER
.sp
By default, Syncthing uses a number of global discovery servers, signified by
the entry \fBdefault\fP in the list of discovery servers. To make Syncthing use
your own instance of stdiscosrv, open up Syncthings web GUI. Go to settings,
Global Discovery Server and add stdiscosrvs host address to the comma\-separated
your own instance of stdiscosrv, open up Syncthing\(aqs web GUI. Go to settings,
Global Discovery Server and add stdiscosrv\(aqs host address to the comma\-separated
list, e.g. \fBhttps://disco.example.com:8443/\fP\&. Note that stdiscosrv uses port
8443 by default. For stdiscosrv to be available over the internet with a dynamic
IP address, you will need a dynamic DNS service.
@@ -114,7 +114,7 @@ entry from the list.
.SS Description
.sp
This guide assumes that you have already set up Syncthing. If you
havent yet, head over to getting\-started first.
haven\(aqt yet, head over to getting\-started first.
.SS Installing
.sp
Go to \fI\%releases\fP <\fBhttps://github.com/syncthing/discosrv/releases\fP> and
@@ -146,7 +146,7 @@ discovery server. This is like any other HTTPS website; clients will
authenticate the server based on its certificate and domain name.
.IP \(bu 2
Use any certificate pair and let clients authenticate the server based on
its device ID (similar to Syncthing\-to\-Syncthing authentication). This
its "device ID" (similar to Syncthing\-to\-Syncthing authentication). This
option can be used with the certificate automatically generated by the
discovery server.
.IP \(bu 2
@@ -155,7 +155,7 @@ reverse proxy. See below for configuration.
.UNINDENT
.sp
For the first two options, the discovery server must be given the paths to
the certificate and key at startup. This isnt necessary with the \fBhttp\fP flag:
the certificate and key at startup. This isn\(aqt necessary with the \fBhttp\fP flag:
.INDENT 0.0
.INDENT 3.5
.sp
@@ -218,9 +218,9 @@ sender and listener.
As an example, lets assume two discovery servers:
.INDENT 0.0
.IP \(bu 2
Server one is on 192.0.2.20 and has certificate ID I6KH76
Server one is on 192.0.2.20 and has certificate ID I6K...H76
.IP \(bu 2
Server two is on 192.0.2.55 and has certificate ID MRI7OK
Server two is on 192.0.2.55 and has certificate ID MRI...7OK
.UNINDENT
.sp
In order for both to replicate to the other and thus form a redundant pair,
@@ -257,7 +257,7 @@ port must be specified in peer addresses.
.sp
It is possible to only allow incoming connections from a peer without
establishing an outgoing replication connection. To do so, give only the
device ID without @ip:port address:
device ID without "@ip:port" address:
.INDENT 0.0
.INDENT 3.5
.sp
@@ -270,7 +270,7 @@ $ stdiscosrv \-replicate=I6K...H76 <other options>
.UNINDENT
.sp
Discosrv will listen on the replication port only when \fB\-replicate\fP is
given. The default replication listen address is :19200.
given. The default replication listen address is ":19200".
.sp
To achieve load balancing over two mutually replicating discovery server
instances, add multiple A / AAAA DNS records for a given name and point
@@ -293,10 +293,10 @@ Run the discovery server using the \-http flag \fBstdiscosrv \-http\fP\&.
.IP \(bu 2
SSL certificate/key configured for the reverse proxy
.IP \(bu 2
The X\-Forwarded\-For http header must be passed through with the clients
The "X\-Forwarded\-For" http header must be passed through with the client\(aqs
real IP address
.IP \(bu 2
The X\-SSL\-Cert must be passed through with the PEM\-encoded client SSL
The "X\-SSL\-Cert" must be passed through with the PEM\-encoded client SSL
certificate
.IP \(bu 2
The proxy must request the client SSL certificate but not require it to be
@@ -371,13 +371,13 @@ server {
.UNINDENT
.sp
An example of automating the SSL certificates and reverse\-proxying the Discovery
Server and Syncthing using Nginx, \fI\%Lets Encrypt\fP <\fBhttps://letsencrypt.org/\fP> and Docker can be found \fI\%here\fP <\fBhttps://forum.syncthing.net/t/docker-syncthing-and-syncthing-discovery-behind-nginx-reverse-proxy-with-lets-encrypt/6880\fP>\&.
Server and Syncthing using Nginx, \fI\%Let\(aqs Encrypt\fP <\fBhttps://letsencrypt.org/\fP> and Docker can be found \fI\%here\fP <\fBhttps://forum.syncthing.net/t/docker-syncthing-and-syncthing-discovery-behind-nginx-reverse-proxy-with-lets-encrypt/6880\fP>\&.
.SH SEE ALSO
.sp
\fBsyncthing\-networking(7)\fP, \fBsyncthing\-faq(7)\fP
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+9 -9
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STRELAYSRV" "1" "Jan 26, 2019" "v1" "Syncthing"
.TH "STRELAYSRV" "1" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.
@@ -72,12 +72,12 @@ Global rate limit, in bytes/s.
.INDENT 0.0
.TP
.B \-keys=<dir>
Directory where cert.pem and key.pem is stored (default “.”).
Directory where cert.pem and key.pem is stored (default ".").
.UNINDENT
.INDENT 0.0
.TP
.B \-listen=<listen addr>
Protocol listen address (default :22067).
Protocol listen address (default ":22067").
.UNINDENT
.INDENT 0.0
.TP
@@ -127,13 +127,13 @@ How often pings are sent (default 1m0s).
.TP
.B \-pools=<pool addresses>
Comma separated list of relay pool addresses to join (default
\fI\%http://relays.syncthing.net/endpoint\fP). Blank to disable announcement to
"\fI\%http://relays.syncthing.net/endpoint\fP"). Blank to disable announcement to
a pool, thereby remaining a private relay.
.UNINDENT
.INDENT 0.0
.TP
.B \-protocol=<string>
Protocol used for listening. tcp for IPv4 and IPv6, tcp4 for IPv4, tcp6 for IPv6 (default tcp).
Protocol used for listening. \(aqtcp\(aq for IPv4 and IPv6, \(aqtcp4\(aq for IPv4, \(aqtcp6\(aq for IPv6 (default "tcp").
.UNINDENT
.INDENT 0.0
.TP
@@ -143,7 +143,7 @@ An optional description about who provides the relay.
.INDENT 0.0
.TP
.B \-status\-srv=<listen addr>
Listen address for status service (blank to disable) (default :22070).
Listen address for status service (blank to disable) (default ":22070").
Status service is used by the relay pool server UI for displaying stats (data transfered, number of clients, etc.)
.UNINDENT
.SH SETTING UP
@@ -191,7 +191,7 @@ relay://private\-relay\-1.example.com:443/?id=ITZRNXE\-YNROGBZ\-HXTH5P7\-VK5NYE5
.UNINDENT
.UNINDENT
.sp
The relays device ID is output on start\-up.
The relay\(aqs device ID is output on start\-up.
.SS Running on port 443 as an unprivileged user
.sp
It is recommended that you run the relay on port 443 (or another port which is
@@ -213,7 +213,7 @@ iptables \-t nat \-A PREROUTING \-i eth0 \-p tcp \-\-dport 443 \-j REDIRECT \-\-
.UNINDENT
.UNINDENT
.sp
Or, if youre using \fBufw\fP, add the following to \fB/etc/ufw/before.rules\fP:
Or, if you\(aqre using \fBufw\fP, add the following to \fB/etc/ufw/before.rules\fP:
.INDENT 0.0
.INDENT 3.5
.sp
@@ -271,6 +271,6 @@ Please consult Linux distribution documentation to persist firewall rules.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+18 -18
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.
@@ -46,8 +46,8 @@ File data is described and transferred in units of \fIblocks\fP, each being from
block size may vary between files but is constant in any given file, except
for the last block which may be smaller.
.sp
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT,
SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119.
.SH TRANSPORT AND AUTHENTICATION
.sp
@@ -72,8 +72,8 @@ v ... v
.UNINDENT
.sp
The encryption and authentication layer SHALL use TLS 1.2 or a higher
revision. A strong cipher suite SHALL be used, with strong cipher
suite being defined as being without known weaknesses and providing
revision. A strong cipher suite SHALL be used, with "strong cipher
suite" being defined as being without known weaknesses and providing
Perfect Forward Secrecy (PFS). Examples of strong cipher suites are
given at the end of this document. This is not to be taken as an
exhaustive list of allowed cipher suites but represents best practices
@@ -85,7 +85,7 @@ connection. Possibilities include certificates signed by a common
trusted CA, preshared certificates, preshared certificate fingerprints
or certificate pinning combined with some out of band first
verification. The reference implementation uses preshared certificate
fingerprints (SHA\-256) referred to as Device IDs.
fingerprints (SHA\-256) referred to as "Device IDs".
.sp
There is no required order or synchronization among BEP messages except
as noted per message type \- any message type may be sent at any time and
@@ -94,9 +94,9 @@ another.
.sp
The underlying transport protocol MUST guarantee reliable packet delivery.
.sp
In this document, in diagrams and text, bit 0 refers to the \fImost
significant\fP bit of a word; bit 15 is thus the least significant bit of a
16 bit word (int16) and bit 31 is the least significant bit of a 32 bit
In this document, in diagrams and text, "bit 0" refers to the \fImost
significant\fP bit of a word; "bit 15" is thus the least significant bit of a
16 bit word (int16) and "bit 31" is the least significant bit of a 32 bit
word (int32). Non protocol buffer integers are always represented in network
byte order (i.e., big endian) and are signed unless stated otherwise, but
when describing message lengths negative values do not make sense and the
@@ -109,7 +109,7 @@ message is \fIvalid\fP with all fields empty \- for example, an index entry for
file that does not have a name is not useful and MAY be rejected by the
implementation. However the folder label is for human consumption only so an
empty label should be accepted \- the implementation will have to choose some
way to represent the folder, perhaps by using the ID in its place or
way to represent the folder, perhaps by using the ID in it\(aqs place or
automatically generating a label.
.SH PRE-AUTHENTICATION MESSAGES
.sp
@@ -171,7 +171,7 @@ name or host name, for the remote device.
The \fBclient_name\fP and \fBclient_version\fP identifies the implementation. The
values SHOULD be simple strings identifying the implementation name, as a
user would expect to see it, and the version string in the same manner. An
example client name is syncthing and an example client version is v0.7.2.
example client name is "syncthing" and an example client version is "v0.7.2".
The client version field SHOULD follow the patterns laid out in the \fI\%Semantic
Versioning\fP <\fBhttp://semver.org/\fP> standard.
.sp
@@ -467,7 +467,7 @@ The \fBfiles\fP field is a list of files making up the index information.
The \fBname\fP is the file name path relative to the folder root. Like all
strings in BEP, the Name is always in UTF\-8 NFC regardless of operating
system or file system specific conventions. The name field uses the slash
character (“/”) as path separator, regardless of the implementations
character ("/") as path separator, regardless of the implementation\(aqs
operating system conventions. The combination of folder and name uniquely
identifies each file in a cluster.
.sp
@@ -532,7 +532,7 @@ symlink type. It is empty for all other entry types.
.SS Request
.sp
The Request message expresses the desire to receive a data block
corresponding to a part of a certain file in the peers folder.
corresponding to a part of a certain file in the peer\(aqs folder.
.SS Protocol Buffer Schema
.INDENT 0.0
.INDENT 3.5
@@ -569,7 +569,7 @@ requested hash. The other device MAY reuse a block from a different file and
offset having the same size and hash, if one exists.
.sp
The \fBfrom temporary\fP field is set to indicate that the read should be
performed from the temporary file (converting name to its temporary form)
performed from the temporary file (converting name to it\(aqs temporary form)
and falling back to the non temporary file if any error occurs. Knowledge of
contents of temporary files comes from DownloadProgress messages.
.SS Response
@@ -812,7 +812,7 @@ index data.
For situations with large indexes or frequent reconnects this can be quite
inefficient. A mechanism can then be used to retain index data between
connections and only transmit any changes since that data on connection
start. This is called delta indexes. To enable this mechanism the
start. This is called "delta indexes". To enable this mechanism the
\fBsequence\fP and \fBindex ID\fP fields are used.
.INDENT 0.0
.TP
@@ -861,7 +861,7 @@ Update messages rather than sending a very large Index message.
The Syncthing implementation imposes a hard limit of 500,000,000 bytes on
all messages. Attempting to send or receive a larger message will result in
a connection close. This size was chosen to accommodate Index messages
containing a large block list. Its intended that the limit may be further
containing a large block list. It\(aqs intended that the limit may be further
reduced in a future protocol update supporting variable block sizes (and
thus shorter block lists for large files).
.SH SELECTION OF BLOCK SIZE
@@ -1049,7 +1049,7 @@ T} T{
T}
_
T{
\&...
T} T{
T} T{
T}
@@ -1161,6 +1161,6 @@ _
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+16 -16
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.
@@ -58,7 +58,7 @@ directory the following files are located:
The configuration file, in XML format.
.TP
.B \fBcert.pem\fP, \fBkey.pem\fP
The devices RSA public and private key. These form the basis for the
The device\(aqs RSA public and private key. These form the basis for the
device ID. The key must be kept private.
.TP
.B \fBhttps\-cert.pem\fP, \fBhttps\-key.pem\fP
@@ -257,17 +257,17 @@ Controls how the folder is handled by Syncthing. Possible values are:
.TP
.B sendreceive
The folder is in default mode. Sending local and accepting remote changes.
Note that this type was previously called readwrite which is deprecated
Note that this type was previously called "readwrite" which is deprecated
but still accepted in incoming configs.
.TP
.B sendonly
The folder is in send only mode it will not be modified by
The folder is in "send only" mode \-\- it will not be modified by
Syncthing on this device.
Note that this type was previously called readonly which is deprecated
Note that this type was previously called "readonly" which is deprecated
but still accepted in incoming configs.
.TP
.B receiveonly
The folder is in receive only mode it will not propagate
The folder is in "receive only" mode \-\- it will not propagate
changes to other devices.
.UNINDENT
.TP
@@ -325,7 +325,7 @@ versioning
.B copiers, pullers, hashers
The number of copier, puller and hasher routines to use, or zero for the
system determined optimum. These are low level performance options for
advanced users only; do not change unless requested to or youve actually
advanced users only; do not change unless requested to or you\(aqve actually
read and understood the code yourself. :)
.TP
.B order
@@ -362,8 +362,8 @@ The interval with which scan progress information is sent to the GUI. Zero
means the default value (two seconds).
.TP
.B pullerPauseS
Tweak for rate limiting the puller when it retries pulling files. Dont
change these unless you know what youre doing.
Tweak for rate limiting the puller when it retries pulling files. Don\(aqt
change these unless you know what you\(aqre doing.
.TP
.B maxConflicts
The maximum number of conflict copies to keep around for any given file.
@@ -389,7 +389,7 @@ to \-1 to always use weak hash. Default value is 25.
.TP
.B markerName
Name of a directory or file in the folder root to be used as
marker\-faq\&. Default is .stfolder.
marker\-faq\&. Default is ".stfolder".
.TP
.B fsync
Deprecated since version v0.14.37.
@@ -402,8 +402,8 @@ committing the changes to the internal database.
Deprecated since version v0.14.41.
.sp
Tweak for rate limiting the puller. Dont change these unless you know
what youre doing.
Tweak for rate limiting the puller. Don\(aqt change these unless you know
what you\(aqre doing.
.UNINDENT
.SH DEVICE ELEMENT
.INDENT 0.0
@@ -478,7 +478,7 @@ to even if the original introducer is no longer listing the remote device as kno
Defines which device has introduced us to this device. Used only for following de\-introductions.
.TP
.B certName
The device certificate common name, if it is not the default syncthing.
The device certificate common name, if it is not the default "syncthing".
.UNINDENT
.sp
From following child elements at least one \fBaddress\fP child must exist.
@@ -967,13 +967,13 @@ that the files you are backing up are in a folder\-sendonly to prevent other
devices from overwriting the per device configuration. The folder on the remote
device(s) should not be used as configuration for the remote devices.
.sp
If youd like to sync your home folder in non\-send only mode, you may add the
If you\(aqd like to sync your home folder in non\-send only mode, you may add the
folder that stores the configuration files to the ignore list\&.
If youd also like to backup your configuration files, add another folder in
If you\(aqd also like to backup your configuration files, add another folder in
send only mode for just the configuration folder.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+24 -24
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.
@@ -32,8 +32,8 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
..
.sp
Every device is identified by a device ID. The device ID is used for address
resolution, authentication and authorization. The term device ID could
interchangeably have been key ID since the device ID is a direct property of
resolution, authentication and authorization. The term "device ID" could
interchangeably have been "key ID" since the device ID is a direct property of
the public key in use.
.SH KEYS
.sp
@@ -43,7 +43,7 @@ startup, Syncthing will create a public/private keypair.
Currently this is a 384 bit ECDSA key (3072 bit RSA prior to v0.12.5,
which is what is used as an example in this article). The keys are saved in
the form of the private key (\fBkey.pem\fP) and a self signed certificate
(\fBcert.pem\fP). The self signing part doesnt actually add any security or
(\fBcert.pem\fP). The self signing part doesn\(aqt actually add any security or
functionality as far as Syncthing is concerned but it enables the use of the
keys in a standard TLS exchange.
.sp
@@ -94,7 +94,7 @@ Certificate:
.sp
We can see here that the certificate is little more than a container for the
public key; the serial number is zero and the Issuer and Subject are both
syncthing where a qualified name might otherwise be expected.
"syncthing" where a qualified name might otherwise be expected.
.sp
An advanced user could replace the \fBkey.pem\fP and \fBcert.pem\fP files with a
keypair generated directly by the \fBopenssl\fP utility or other mechanism.
@@ -138,7 +138,7 @@ MFZWI3D\-BONSGYC\-YLTMRWG\-C43ENR5\-QXGZDMM\-FZWI3DP\-BONSGYY\-LTMRWAD
.UNINDENT
.SS Connection Establishment
.sp
Now we know what device IDs are, heres how they are used in Syncthing. When
Now we know what device IDs are, here\(aqs how they are used in Syncthing. When
you add a device ID to the configuration, Syncthing will attempt to
connect to that device. The first thing we need to do is figure out the IP and
port to connect to. There are three possibilities here:
@@ -150,13 +150,13 @@ dynamic DNS setup this might be a good option.
.IP \(bu 2
Using local discovery, if enabled. Every Syncthing instance on a LAN
periodically broadcasts information about itself (device ID, address,
port number). If weve seen one of these broadcasts for a given
device ID thats where we try to connect.
port number). If we\(aqve seen one of these broadcasts for a given
device ID that\(aqs where we try to connect.
.IP \(bu 2
Using global discovery, if enabled. Every Syncthing instance
announces itself to the global discovery service (device ID and
external port number \- the internal address is not announced to the
global server). If we dont have a static address and havent seen
global server). If we don\(aqt have a static address and haven\(aqt seen
any local announcements the global discovery server will be queried
for an address.
.UNINDENT
@@ -188,11 +188,11 @@ The SHA\-256 hash is cryptographically collision resistant. This means
that there is no way that we know of to create two different messages
with the same hash.
.sp
You can argue that of course there are collisions \- theres an infinite
You can argue that of course there are collisions \- there\(aqs an infinite
amount of inputs and a finite amount of outputs \- so by definition there
are infinitely many messages that result in the same hash.
.sp
Im going to quote \fI\%stack
I\(aqm going to quote \fI\%stack
overflow\fP <\fBhttps://stackoverflow.com/questions/4014090/is-it-safe-to-ignore-the-possibility-of-sha-collisions-in-practice\fP>
here:
.INDENT 0.0
@@ -203,28 +203,28 @@ civilization\-as\-we\- know\-it, and killing off a few billion people ?
It can be argued that any unlucky event with a probability lower
than that is not actually very important.
.sp
If we have a perfect hash function with output size n, and we have
If we have a "perfect" hash function with output size n, and we have
p messages to hash (individual message length is not important),
then probability of collision is about p2/2n+1 (this is an
approximation which is valid for small p, i.e. substantially
approximation which is valid for "small" p, i.e. substantially
smaller than 2n/2). For instance, with SHA\-256 (n=256) and one
billion messages (p=10^9) then the probability is about 4.3*10^\-60.
.sp
A mass\-murderer space rock happens about once every 30 million years
on average. This leads to a probability of such an event occurring
in the next second to about 10^\-15. Thats 45 orders of magnitude
in the next second to about 10^\-15. That\(aqs 45 orders of magnitude
more probable than the SHA\-256 collision. Briefly stated, if you
find SHA\-256 collisions scary then your priorities are wrong.
.UNINDENT
.UNINDENT
.sp
Its also worth noting that the property of SHA\-256 that we are using is not
It\(aqs also worth noting that the property of SHA\-256 that we are using is not
simply collision resistance but resistance to a preimage attack, i.e. even if
you can find two messages that result in a hash collision that doesnt help you
you can find two messages that result in a hash collision that doesn\(aqt help you
attack Syncthing (or TLS in general). You need to create a message that hashes
to exactly the hash that my certificate already has or you wont get in.
to exactly the hash that my certificate already has or you won\(aqt get in.
.sp
Note also that its not good enough to find a random blob of bits that happen to
Note also that it\(aqs not good enough to find a random blob of bits that happen to
have the same hash as my certificate. You need to create a valid DER\-encoded,
signed certificate that has the same hash as mine. The difficulty of this is
staggeringly far beyond the already staggering difficulty of finding a SHA\-256
@@ -235,22 +235,22 @@ As far as I know, these are the issues or potential issues with the
above mechanism.
.SS Discovery Spoofing
.sp
Currently, the local discovery mechanism isnt protected by crypto. This
Currently, the local discovery mechanism isn\(aqt protected by crypto. This
means that any device can in theory announce itself for any device ID and
potentially receive connections for that device from the local network.
.SS Long Device IDs are Painful
.sp
Its a mouthful to read over the phone, annoying to type into an SMS or even
It\(aqs a mouthful to read over the phone, annoying to type into an SMS or even
into a computer. And it needs to be done twice, once for each side.
.sp
This isnt a vulnerability as such, but a user experience problem. There are
This isn\(aqt a vulnerability as such, but a user experience problem. There are
various possible solutions:
.INDENT 0.0
.IP \(bu 2
Use shorter device IDs with verification based on the full ID (You
Use shorter device IDs with verification based on the full ID ("You
entered MFZWI3; I found and connected to a device with the ID
MFZWI3\-DBONSG\-YYLTMR\-WGC43E\-NRQXGZ\-DMMFZW\-I3DBON\-SGYYLT\-MRWA, please
confirm that this is correct).
confirm that this is correct").
.IP \(bu 2
Use shorter device IDs with an out of band authentication, a la
Bluetooth pairing. You enter a one time PIN into Syncthing and give
@@ -261,6 +261,6 @@ accepting the connection.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+4 -4
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.
@@ -162,8 +162,8 @@ Generated each time a connection to a device has been terminated.
.INDENT 0.0
.INDENT 3.5
The error key contains the cause for disconnection, which might not
necessarily be an error as such. Specifically, EOF and unexpected
EOF both signify TCP connection termination, either due to the other
necessarily be an error as such. Specifically, "EOF" and "unexpected
EOF" both signify TCP connection termination, either due to the other
device restarting or going offline or due to a network change.
.UNINDENT
.UNINDENT
@@ -876,6 +876,6 @@ seconds and is now in state \fBidle\fP\&.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+46 -49
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.
@@ -38,10 +38,10 @@ machine will automatically be replicated to your other devices. We believe your
data is your data alone and you deserve to choose where it is stored. Therefore
Syncthing does not upload your data to the cloud but exchanges your data across
your machines as soon as they are online at the same time.
.SH IS IT SYNCTHING, SYNCTHING OR SYNCTHING?
.SH IS IT "SYNCTHING", "SYNCTHING" OR "SYNCTHING"?
.sp
Its \fBSyncthing\fP, although the command and source repository is spelled
\fBsyncthing\fP so it may be referred to in that way as well. Its definitely not
It\(aqs \fBSyncthing\fP, although the command and source repository is spelled
\fBsyncthing\fP so it may be referred to in that way as well. It\(aqs definitely not
SyncThing, even though the abbreviation \fBst\fP is used in some
circumstances and file names.
.SH HOW DOES SYNCTHING DIFFER FROM BITTORRENT/RESILIO SYNC?
@@ -116,9 +116,9 @@ in the configuration file (24 hours by default).
.sp
When troubleshooting a slow sync, there are a number of things to check.
.sp
First of all, verify that you are not connected via a relay. In the Remote
Devices list on the right side of the GUI, double check that you see
Address: <some address> and \fInot\fP Relay: <some address>.
First of all, verify that you are not connected via a relay. In the "Remote
Devices" list on the right side of the GUI, double check that you see
"Address: <some address>" and \fInot\fP "Relay: <some address>".
[image]
.sp
If you are connected via a relay, this is because a direct connection could
@@ -150,8 +150,9 @@ compressed) decompressed.
There is a certain amount of housekeeping that must be done to track the
current and available versions of each file in the index database.
.IP 4. 3
By default Syncthing uses periodic scanning every 60 seconds to detect
file changes. This means checking every files modification time and
By default Syncthing uses periodic scanning every hour when watching for
changes or every minute if that\(aqs disabled to detect
file changes. This means checking every file\(aqs modification time and
comparing it to the database. This can cause spikes of CPU usage for large
folders.
.UNINDENT
@@ -168,20 +169,16 @@ To further limit the amount of CPU used when syncing and scanning, set the
environment variable \fBGOMAXPROCS\fP to the maximum number of CPU cores
Syncthing should use at any given moment. For example, \fBGOMAXPROCS=2\fP on a
machine with four cores will limit Syncthing to no more than half the
systems CPU power.
.sp
To reduce CPU spikes from scanning activity, use a filesystem notifications
plugin. This is delivered by default via Synctrayzor, Syncthing\-GTK and on
Android. For other setups, consider using \fI\%syncthing\-inotify\fP <\fBhttps://github.com/syncthing/syncthing-inotify\fP>\&.
system\(aqs CPU power.
.SH SHOULD I KEEP MY DEVICE IDS SECRET?
.sp
No. The IDs are not sensitive. Given a device ID its possible to find the IP
No. The IDs are not sensitive. Given a device ID it\(aqs possible to find the IP
address for that device, if global discovery is enabled on it. Knowing the device
ID doesnt help you actually establish a connection to that device or get a list
ID doesn\(aqt help you actually establish a connection to that device or get a list
of files, etc.
.sp
For a connection to be established, both devices need to know about the others
device ID. Its not possible (in practice) to forge a device ID. (To forge a
For a connection to be established, both devices need to know about the other\(aqs
device ID. It\(aqs not possible (in practice) to forge a device ID. (To forge a
device ID you need to create a TLS certificate with that specific SHA\-256 hash.
If you can do that, you can spoof any TLS certificate. The world is your
oyster!)
@@ -196,7 +193,7 @@ device\-ids
.sp
Syncthing does recognize conflicts. When a file has been modified on two devices
simultaneously and the content actually differs, one of the files will be
renamed to \fB<filename>.sync\-conflict\-<date>\-<time>.<ext>\fP\&. The file with the
renamed to \fB<filename>.sync\-conflict\-<date>\-<time>\-<modifiedBy>.<ext>\fP\&. The file with the
older modification time will be marked as the conflicting file and thus be
renamed. If the modification times are equal, the file originating from the
device which has the larger value of the first 63 bits for his device ID will be
@@ -205,19 +202,17 @@ If the conflict is between a modification and a deletion of the file, the
modified file always wins and is resurrected without renaming on the
device where it was deleted.
.sp
Beware that the \fB<filename>.sync\-conflict\-<date>\-<time>.<ext>\fP files are
Beware that the \fB<filename>.sync\-conflict\-<date>\-<time>\-<modifiedBy>.<ext>\fP files are
treated as normal files after they are created, so they are propagated between
devices. We do this because the conflict is detected and resolved on one device,
creating the \fBsync\-conflict\fP file, but its just as much of a conflict
everywhere else and we dont know which of the conflicting files is the best
from the user point of view. Moreover, if theres something that automatically
causes a conflict on change youll end up with \fBsync\-conflict\-...sync\-conflict
\-...\-sync\-conflict\fP files.
creating the \fBsync\-conflict\fP file, but it\(aqs just as much of a conflict
everywhere else and we don\(aqt know which of the conflicting files is the "best"
from the user point of view.
.SH HOW DO I SERVE A FOLDER FROM A READ ONLY FILESYSTEM?
.sp
Syncthing requires a folder marker to indicate that the folder is present
Syncthing requires a "folder marker" to indicate that the folder is present
and healthy. By default this is a directory called \fB\&.stfolder\fP that is
created by Syncthing when the folder is added. If this folder cant be
created by Syncthing when the folder is added. If this folder can\(aqt be
created (you are serving files from a CD or something) you can instead set
the advanced config \fBMarker Name\fP to the name of some file or folder that
you know will always exist in the folder.
@@ -226,13 +221,15 @@ you know will always exist in the folder.
See the previous question.
.SH AM I ABLE TO NEST SHARED FOLDERS IN SYNCTHING?
.sp
Do not share a folder which is inside another shared folder. This behaviour
is in no way supported, recommended or coded for in any way, and comes with
many pitfalls.
Sharing a folder that is within an already shared folder is possible, but it has
its caveats. What you must absolutely avoid are circular shares. This is just
one example, there may be other undesired effects. Nesting shared folders is not
supported, recommended or coded for, but it can be done successfully when you
know what you\(aqre doing \- you have been warned.
.SH HOW DO I RENAME/MOVE A SYNCED FOLDER?
.sp
Syncthing doesnt have a direct way to do this, as its potentially
dangerous to do so if youre not careful \- it may result in data loss if
Syncthing doesn\(aqt have a direct way to do this, as it\(aqs potentially
dangerous to do so if you\(aqre not careful \- it may result in data loss if
something goes wrong during the move and is synchronized to your other
devices.
.sp
@@ -240,8 +237,8 @@ The easy way to rename or move a synced folder on the local system is to
remove the folder in the Syncthing UI, move it on disk, then re\-add it using
the new path.
.sp
Its best to do this when the folder is already in sync between your
devices, as it is otherwise unpredictable which changes will win after the
It\(aqs best to do this when the folder is already in sync between your
devices, as it is otherwise unpredictable which changes will "win" after the
move. Changes made on other devices may be overwritten, or changes made
locally may be overwritten by those on other devices.
.sp
@@ -255,7 +252,7 @@ to configure listening ports such that they do not overlap (see config).
.SH DOES SYNCTHING SUPPORT SYNCING BETWEEN FOLDERS ON THE SAME SYSTEM?
.sp
No. Syncthing is not designed to sync locally and the overhead involved in
doing so using Syncthings method would be wasteful. There are better
doing so using Syncthing\(aqs method would be wasteful. There are better
programs to achieve this such as rsync or Unison.
.SH WHEN I DO HAVE TWO DISTINCT SYNCTHING-MANAGED FOLDERS ON TWO HOSTS, HOW DOES SYNCTHING HANDLE MOVING FILES BETWEEN THEM?
.sp
@@ -292,7 +289,7 @@ The patterns in .stignore are glob patterns, where brackets are used to
denote character ranges. That is, the pattern \fBq[abc]x\fP will match the
files \fBqax\fP, \fBqbx\fP and \fBqcx\fP\&.
.sp
To match an actual file \fIcalled\fP \fBq[abc]x\fP the pattern needs to escape
To match an actual file \fIcalled\fP \fBq[abc]x\fP the pattern needs to "escape"
the brackets, like so: \fBq\e[abc\e]x\fP\&.
.sp
On Windows, escaping special characters is not supported as the \fB\e\fP
@@ -301,7 +298,7 @@ such as \fB[\fP and \fB?\fP are not allowed in file names on Windows.
.SH WHY IS THE SETUP MORE COMPLICATED THAN BITTORRENT/RESILIO SYNC?
.sp
Security over convenience. In Syncthing you have to setup both sides to
connect two devices. An attacker cant do much with a stolen device ID, because
connect two devices. An attacker can\(aqt do much with a stolen device ID, because
you have to add the device on the other side too. You have better control
where your files are transferred.
.sp
@@ -357,7 +354,7 @@ $ ssh \-L 9090:127.0.0.1:8384 user@othercomputer.example.com
will log you into othercomputer.example.com, and present the \fIremote\fP
Syncthing GUI on \fI\%http://localhost:9090\fP on your \fIlocal\fP computer.
.sp
If you only want to access the remote gui and dont want the terminal
If you only want to access the remote gui and don\(aqt want the terminal
session, use this example,
.INDENT 0.0
.INDENT 3.5
@@ -382,7 +379,7 @@ Another Windows way to run ssh is to install gow.
.sp
The easiest way to install gow is with chocolatey.
\fI\%https://chocolatey.org/\fP
.SH WHY DO I GET HOST CHECK ERROR IN THE GUI/API?
.SH WHY DO I GET "HOST CHECK ERROR" IN THE GUI/API?
.sp
Since version 0.14.6 Syncthing does an extra security check when the GUI/API
is bound to localhost \- namely that the browser is talking to localhost.
@@ -407,8 +404,8 @@ Bind the GUI/API to a non\-localhost listen port.
In all cases, username/password authentication and HTTPS should be used.
.SH MY SYNCTHING DATABASE IS CORRUPT
.sp
This is almost always a result of bad RAM, storage device or other hardware. When the index database is found to be corrupt Syncthing cannot operate and will note this in the logs and exit. To overcome this delete the \fI\%database folder\fP <\fBhttps://docs.syncthing.net/users/config.html#description\fP> inside Syncthings home directory and re\-start Syncthing. It will then need to perform a full re\-hashing of all shared folders. You should check your system in case the underlying cause is indeed faulty hardware which may put the system at risk of further data loss.
.SH I DONT LIKE THE GUI OR THE THEME. CAN IT BE CHANGED?
This is almost always a result of bad RAM, storage device or other hardware. When the index database is found to be corrupt Syncthing cannot operate and will note this in the logs and exit. To overcome this delete the \fI\%database folder\fP <\fBhttps://docs.syncthing.net/users/config.html#description\fP> inside Syncthing\(aqs home directory and re\-start Syncthing. It will then need to perform a full re\-hashing of all shared folders. You should check your system in case the underlying cause is indeed faulty hardware which may put the system at risk of further data loss.
.SH I DON'T LIKE THE GUI OR THE THEME. CAN IT BE CHANGED?
.sp
You can change the theme in the settings. Syncthing ships with other themes
than the default.
@@ -419,7 +416,7 @@ By default, Syncthing will look for a directory \fBgui\fP inside the Syncthing
home folder. To change the directory to look for themes, you need to set the
STGUIASSETS environment variable. To get the concrete directory, run
syncthing with the \fB\-paths\fP parameter. It will print all the relevant paths,
including the GUI override directory.
including the "GUI override directory".
.sp
To add e.g. a red theme, you can create the file \fBred/assets/css/theme.css\fP
inside the GUI override directory to override the default CSS styles.
@@ -436,7 +433,7 @@ crashes and other bugs.
.SH WHERE DO SYNCTHING LOGS GO TO?
.sp
Syncthing logs to stdout by default. On Windows Syncthing by default also
creates \fBsyncthing.log\fP in Syncthings home directory (run \fBsyncthing
creates \fBsyncthing.log\fP in Syncthing\(aqs home directory (run \fBsyncthing
\-paths\fP to see where that is). Command line option \fB\-logfile\fP can be used
to specify a user\-defined logfile.
.SH HOW CAN I VIEW THE HISTORY OF CHANGES?
@@ -463,7 +460,7 @@ it initiates the conflict resolution procedure, which in the end results in a co
up\-to\-date state with all the neighbours.
.SH HOW DO I UPGRADE SYNCTHING?
.sp
If you use a package manager such as Debians apt\-get, you should upgrade
If you use a package manager such as Debian\(aqs apt\-get, you should upgrade
using the package manager. If you use the binary packages linked from
Syncthing.net, you can use Syncthing built in automatic upgrades.
.INDENT 0.0
@@ -491,14 +488,14 @@ version. We suggest to use the GitHub API at
the JSON response.
.SH HOW DO I RUN SYNCTHING AS A DAEMON PROCESS ON LINUX?
.sp
If youre using systemd, runit, or upstart, we already ship examples, check
If you\(aqre using systemd, runit, or upstart, we already ship examples, check
\fI\%https://github.com/syncthing/syncthing/tree/master/etc\fP for example
configurations.
.sp
If however youre not using one of these tools, you have a couple of options.
If your system has a tool called \fBstart\-stop\-daemon\fP installed (thats the name
If however you\(aqre not using one of these tools, you have a couple of options.
If your system has a tool called \fBstart\-stop\-daemon\fP installed (that\(aqs the name
of the command, not the package), look into the local documentation for that, it
will almost certainly cover 100% of what you want to do. If you dont have
will almost certainly cover 100% of what you want to do. If you don\(aqt have
\fBstart\-stop\-daemon\fP, there are a bunch of other software packages you could use
to do this. The most well known is called daemontools, and can be found in the
standard package repositories for almost every modern Linux distribution.
@@ -558,6 +555,6 @@ sudo sh \-c \(aqecho 204800 > /proc/sys/fs/inotify/max_user_watches\(aq
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+11 -11
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.
@@ -34,7 +34,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.sp
A device should announce itself at startup. It does this by an HTTPS POST to
the announce server URL. Standard discovery currently requires the path to be
/v2/, yet this can be up to the discovery server. The POST has a JSON payload
"/v2/", yet this can be up to the discovery server. The POST has a JSON payload
listing connection addresses (if any):
.INDENT 0.0
.INDENT 3.5
@@ -49,9 +49,9 @@ listing connection addresses (if any):
.UNINDENT
.UNINDENT
.sp
Its OK for the addresses field to be either the empty list (\fB[]\fP),
It\(aqs OK for the "addresses" field to be either the empty list (\fB[]\fP),
\fBnull\fP, or missing entirely. An announcement with the field missing
or empty is however not useful
or empty is however not useful...
.sp
Any empty or unspecified IP addresses (i.e. addresses like \fBtcp://:22000\fP,
\fBtcp://0.0.0.0:22000\fP, \fBtcp://[::]:22000\fP) are interpreted as referring to
@@ -63,7 +63,7 @@ authentication. The device ID is deduced from the presented certificate.
.sp
The server response is empty, with code \fB204\fP (No Content) on success. If no
certificate was presented, status \fB403\fP (Forbidden) is returned. If the
posted data doesnt conform to the expected format, \fB400\fP (Bad Request) is
posted data doesn\(aqt conform to the expected format, \fB400\fP (Bad Request) is
returned.
.sp
In successful responses, the server may return a \fBReannounce\-After\fP header
@@ -80,14 +80,14 @@ Many Requests).
.SH QUERIES
.sp
Queries are performed as HTTPS GET requests to the announce server URL. The
requested device ID is passed as the query parameter device, in canonical
requested device ID is passed as the query parameter "device", in canonical
string form, i.e. \fBhttps://discovery.syncthing.net/?device=ABC12345\-....\fP
.sp
Successful responses will have status code \fB200\fP (OK) and carry a JSON payload
of the same format as the announcement above. The response will not contain
empty or unspecified addresses.
.sp
If the device query parameter is missing or malformed, the status code 400
If the "device" query parameter is missing or malformed, the status code 400
(Bad Request) is returned.
.sp
If the device ID is of a valid format but not found in the registry, 404 (Not
@@ -109,9 +109,9 @@ signed certificate, Syncthing often runs in environments with outdated or
simply nonexistent root CA bundles. Instead, Syncthing can verify the
discovery server certificate fingerprint using the device ID mechanism. This
is certificate pinning and conveyed in the Syncthing configuration as a
synthetic id parameter on the discovery server URL:
\fBhttps://discovery.syncthing.net/?id=...\fP\&. The id parameter is not, in
fact, sent to the discovery server \- its used by Syncthing itself to know
synthetic "id" parameter on the discovery server URL:
\fBhttps://discovery.syncthing.net/?id=...\fP\&. The "id" parameter is not, in
fact, sent to the discovery server \- it\(aqs used by Syncthing itself to know
which certificate to expect on the server side.
.sp
The public discovery network uses this authentication mechanism instead of
@@ -121,6 +121,6 @@ The discovery server prints its certificate ID in this manner on startup.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+2 -2
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.
@@ -118,6 +118,6 @@ between two announces and conclude that the announcing device has restarted.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+3 -3
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.
@@ -72,7 +72,7 @@ Port \fB21027/UDP\fP (for discovery broadcasts on IPv4 and multicasts on IPv6)
.UNINDENT
.SS Uncomplicated Firewall (ufw)
.sp
If youre using \fBufw\fP on Linux and have installed the \fI\%Syncthing package\fP <\fBhttps://apt.syncthing.net/\fP>, you can allow the necessary ports by running:
If you\(aqre using \fBufw\fP on Linux and have installed the \fI\%Syncthing package\fP <\fBhttps://apt.syncthing.net/\fP>, you can allow the necessary ports by running:
.INDENT 0.0
.INDENT 3.5
.sp
@@ -142,6 +142,6 @@ Syncthing can use a SOCKS5 proxy for outbound connections. Please see proxying\&
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+8 -8
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-RELAY" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.
@@ -37,7 +37,7 @@ connect to each other directly otherwise. This is usually due to both devices
being behind a NAT and neither side being able to open a port which would
be directly accessible from the internet.
.sp
A relay was designed to relay BEP protocol, hence the reliance on device IDs
A relay was designed to relay BEP protocol, hence the reliance on device ID\(aqs
in the protocol spec, but at the same time it is general enough that could be
reused by other protocols or applications, as the data transferred between two
devices which use a relay is completely obscure and does not affect the
@@ -92,14 +92,14 @@ exists.
After the client has joined, no more messages are exchanged apart from
Ping/Pong messages for general connection keep alive checking.
.sp
From this point onwards, the client stand\-bys and waits for SessionInvitation
From this point onwards, the client stand\-by\(aqs and waits for SessionInvitation
messages from the relay, which implies that some other device is trying to
connect with you. SessionInvitation message contains the unique session key
which then can be used to establish a connection in session mode.
.sp
If the client fails to send a JoinRelayRequest message within the first ping
interval, the connection is terminated.
If the client fails to send a message (even if its a ping message) every minute
If the client fails to send a message (even if it\(aqs a ping message) every minute
(by default), the connection is terminated.
.SS Temporary protocol submode
.sp
@@ -574,7 +574,7 @@ identify which session you are trying to connect to.
.B : Address
An optional IP address on which the relay server is expecting you to
connect, in order to start a connection in session mode.
Empty/all zero IP should be replaced with the relays public IP address that
Empty/all zero IP should be replaced with the relay\(aqs public IP address that
was used when establishing the protocol mode connection.
.TP
.B : Port
@@ -585,14 +585,14 @@ in order to start a connection in session mode.
Because both sides connecting to the relay use the client side of the socket,
and some protocols behave differently depending if the connection starts on
the server side or the client side, this boolean indicates which side of the
connection this client should assume its getting. The value is inverted in
connection this client should assume it\(aqs getting. The value is inverted in
the invitation which is sent to the other device, so that there is always
one client socket, and one server socket.
.UNINDENT
.SH HOW SYNCTHING USES RELAYS, AND GENERAL SECURITY
.sp
In the case of Syncthing and BEP, when two devices connect via relay, they
start their standard TLS connection encapsulated within the relays plain\-text
start their standard TLS connection encapsulated within the relay\(aqs plain\-text
session connection, effectively upgrading the plain\-text connection to a TLS
connection.
.sp
@@ -694,6 +694,6 @@ _
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+7 -7
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "Jan 26, 2019" "v1" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Mar 05, 2019" "v1" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.
@@ -47,7 +47,7 @@ with \fBcurl\fP\&.
.SS GET /rest/system/browse
.sp
Returns a list of directories matching the path given by the optional parameter
\fBcurrent\fP\&. The path can use \fI\%patterns as described in Gos filepath package\fP <\fBhttps://golang.org/pkg/path/filepath/#Match\fP>\&. A * will always be appended
\fBcurrent\fP\&. The path can use \fI\%patterns as described in Go\(aqs filepath package\fP <\fBhttps://golang.org/pkg/path/filepath/#Match\fP>\&. A \(aq*\(aq will always be appended
to the given path (e.g. \fB/tmp/\fP matches all its subdirectories). If the option
\fBcurrent\fP is not given, filesystem root paths are returned.
.INDENT 0.0
@@ -904,8 +904,8 @@ curl \-X POST \-H X\-API\-Key:... http://127.0.0.1:8384/rest/db/revert?folder=de
Request immediate scan. Takes the optional parameters \fBfolder\fP (folder ID),
\fBsub\fP (path relative to the folder root) and \fBnext\fP (time in seconds). If
\fBfolder\fP is omitted or empty all folders are scanned. If \fBsub\fP is given,
only this path (and children, in case its a directory) is scanned. The \fBnext\fP
argument delays Syncthings automated rescan interval for a given amount of
only this path (and children, in case it\(aqs a directory) is scanned. The \fBnext\fP
argument delays Syncthing\(aqs automated rescan interval for a given amount of
seconds.
.sp
Requesting scan of a path that no longer exists, but previously did, is
@@ -980,7 +980,7 @@ Data in the cluster latest version.
Data that is locally the same as the cluster latest version.
.TP
.B local*:
Data that is locally present, regardless of whether its the same or different version as the cluster latest version.
Data that is locally present, regardless of whether it\(aqs the same or different version as the cluster latest version.
.TP
.B need*:
Data that is needed to become up to date with the cluster latest version (i.e., data that is out of sync).
@@ -1023,7 +1023,7 @@ only the desired event types, add a parameter
\fBevents=EventTypeA,EventTypeB,...\fP where the event types are any of the event\-types\&.
.sp
The optional parameter \fBsince=<lastSeenID>\fP sets the ID of the last event
youve already seen. Syncthing returns a JSON encoded array of event objects,
you\(aqve already seen. Syncthing returns a JSON encoded array of event objects,
starting at the event just after the one with this last seen ID. The default
value is 0, which returns all events. There is a limit to the number of events
buffered, so if the rate of events is high or the time between polling calls is
@@ -1202,6 +1202,6 @@ Returns the data sent in the anonymous usage report.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2014-2018, The Syncthing Authors
2014-2019, The Syncthing Authors
.\" Generated by docutils manpage writer.
.

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